async AI job

This commit is contained in:
Flatlogic Bot 2025-11-20 19:22:48 +00:00
parent 1566d6c207
commit 4c1df17a46
5 changed files with 204 additions and 107 deletions

View File

@ -113,6 +113,63 @@
} }
}); });
} }
const isGenerating = {{ selected_conversation.is_generating|yesno:"true,false" }};
const conversationId = {{ selected_conversation.id|default:"null" }};
if (isGenerating && conversationId) {
const pollingInterval = setInterval(() => {
fetch(`/get_conversation_messages/${conversationId}/`)
.then(response => response.json())
.then(data => {
const messagesContainer = document.getElementById('chat-messages');
messagesContainer.innerHTML = ''; // Clear existing messages
data.messages.forEach(message => {
const messageEl = document.createElement('div');
messageEl.classList.add('message', message.sender);
const messageContentEl = document.createElement('div');
messageContentEl.classList.add('message-content');
const authorEl = document.createElement('div');
authorEl.classList.add('message-author');
if (message.sender === 'user') {
authorEl.innerText = 'You';
} else if (message.sender === 'ai') {
authorEl.innerText = 'AI';
} else if (message.sender === 'system') {
authorEl.innerText = 'System';
} else if (message.sender === 'ai_command') {
authorEl.innerText = 'AI Command';
}
messageContentEl.appendChild(authorEl);
if (message.sender === 'ai_command') {
const preEl = document.createElement('pre');
const codeEl = document.createElement('code');
codeEl.innerHTML = message.content.replace(/\n/g, '<br>');
preEl.appendChild(codeEl);
messageContentEl.appendChild(preEl);
} else {
const pEl = document.createElement('p');
pEl.innerHTML = message.content.replace(/\n/g, '<br>');
messageContentEl.appendChild(pEl);
}
messageEl.appendChild(messageContentEl);
messagesContainer.appendChild(messageEl);
});
messagesContainer.scrollTop = messagesContainer.scrollHeight;
if (!data.is_generating) {
clearInterval(pollingInterval);
if (loaderOverlay) {
loaderOverlay.style.display = 'none';
}
}
});
}, 2000);
}
}); });
</script> </script>

View File

@ -3,6 +3,7 @@ from django.http import JsonResponse
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
import json import json
import logging import logging
import threading
from .models import Article, TodoItem, Conversation, Message, Setting from .models import Article, TodoItem, Conversation, Message, Setting
from .forms import TodoItemForm from .forms import TodoItemForm
import time import time
@ -11,6 +12,7 @@ from ai.local_ai_api import LocalAIApi
# Get an instance of a logger # Get an instance of a logger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def index(request): def index(request):
if request.method == 'POST': if request.method == 'POST':
form = TodoItemForm(request.POST) form = TodoItemForm(request.POST)
@ -31,6 +33,7 @@ def index(request):
} }
return render(request, "core/index.html", context) return render(request, "core/index.html", context)
def kanban_board(request): def kanban_board(request):
tasks = TodoItem.objects.all().order_by('created_at') tasks = TodoItem.objects.all().order_by('created_at')
tasks_by_status = { tasks_by_status = {
@ -45,10 +48,12 @@ def kanban_board(request):
} }
return render(request, "core/kanban.html", context) return render(request, "core/kanban.html", context)
def article_detail(request, article_id): def article_detail(request, article_id):
article = Article.objects.get(pk=article_id) article = Article.objects.get(pk=article_id)
return render(request, "core/article_detail.html", {"article": article}) return render(request, "core/article_detail.html", {"article": article})
@require_POST @require_POST
def update_task_status(request): def update_task_status(request):
try: try:
@ -76,11 +81,13 @@ def delete_task(request, task_id):
return redirect(referer) return redirect(referer)
return redirect('core:index') return redirect('core:index')
@require_POST @require_POST
def cleanup_tasks(request): def cleanup_tasks(request):
TodoItem.objects.all().delete() TodoItem.objects.all().delete()
return redirect('core:index') return redirect('core:index')
def execute_command(command_data): def execute_command(command_data):
command_name = command_data.get('name') command_name = command_data.get('name')
args = command_data.get('args', {}) args = command_data.get('args', {})
@ -145,32 +152,25 @@ def execute_command(command_data):
logger.error(f"Error executing command '{command_name}': {e}", exc_info=True) logger.error(f"Error executing command '{command_name}': {e}", exc_info=True)
return f"[SYSTEM] Error executing command '{command_name}': {e}" return f"[SYSTEM] Error executing command '{command_name}': {e}"
def chat_view(request, conversation_id=None):
if request.method == 'POST':
if 'title' in request.POST:
title = request.POST.get('title', 'New Conversation')
conversation = Conversation.objects.create(title=title)
return redirect('core:chat_detail', conversation_id=conversation.id)
elif 'text' in request.POST and conversation_id: def run_ai_process_in_background(conversation_id):
text = request.POST.get('text') """This function runs in a separate thread."""
selected_conversation = get_object_or_404(Conversation, id=conversation_id) try:
conversation = get_object_or_404(Conversation, id=conversation_id)
if text: conversation.is_generating = True
command_name = None # Initialize command_name conversation.save()
Message.objects.create(conversation=selected_conversation, content=text, sender='user')
history = [] history = []
for msg in selected_conversation.messages.order_by('created_at'): for msg in conversation.messages.order_by('created_at'):
role = msg.sender role = msg.sender
if role == 'ai': if role == 'ai':
role = 'assistant' role = 'assistant'
# User messages are already 'user', system messages are 'user' for the model
elif role == 'system': elif role == 'system':
role = 'user' role = 'user'
history.append({"role": role, "content": msg.content}) history.append({"role": role, "content": msg.content})
try: custom_instructions, _ = Setting.objects.get_or_create(
custom_instructions, created = Setting.objects.get_or_create(
key='custom_instructions', key='custom_instructions',
defaults={'value': ''} defaults={'value': ''}
) )
@ -279,19 +279,16 @@ def chat_view(request, conversation_id=None):
}) })
if not response.get("success"): if not response.get("success"):
logger.error(f"AI API request failed with status {response.get('status')}. Full error: {response.get('response')}") logger.error(f"AI API request failed. Full error: {response.get('error')}")
ai_text = "I couldn't process that. Please try again." ai_text = "I couldn't process that. Please try again."
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai') Message.objects.create(conversation=conversation, content=ai_text, sender='ai')
break break
logger.info(f"AI raw response: {response}")
ai_text = LocalAIApi.extract_text(response) ai_text = LocalAIApi.extract_text(response)
logger.info(f"Extracted AI text: {ai_text}")
if not ai_text: if not ai_text:
logger.warning("AI response was empty.") logger.warning("AI response was empty.")
ai_text = "I couldn't process that. Please try again." ai_text = "I couldn't process that. Please try again."
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai') Message.objects.create(conversation=conversation, content=ai_text, sender='ai')
break break
try: try:
@ -301,38 +298,71 @@ def chat_view(request, conversation_id=None):
command_result = execute_command(command_json['command']) command_result = execute_command(command_json['command'])
sender = 'ai' if command_name == 'send_message' else 'system' sender = 'ai' if command_name == 'send_message' else 'system'
Message.objects.create(conversation=selected_conversation, content=command_result, sender=sender) Message.objects.create(conversation=conversation, content=command_result, sender=sender)
if command_name == 'send_message': if command_name == 'send_message':
break # Exit loop if send_message is called break
# Add system message with command result to history for next iteration
history.append({"role": "user", "content": command_result}) history.append({"role": "user", "content": command_result})
else: else:
# If it's a JSON but not a command, save it as a message and break Message.objects.create(conversation=conversation, content=ai_text, sender='ai')
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai')
break break
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):
# Not a JSON command, treat as a raw message and break Message.objects.create(conversation=conversation, content=ai_text, sender='ai')
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai')
break break
else: else:
# This block executes if the loop completes without a 'break'
logger.warning("AI loop finished after 7 iterations without sending a message.") logger.warning("AI loop finished after 7 iterations without sending a message.")
final_message = "I seem to be stuck in a loop. Could you clarify what you'd like me to do?" final_message = "I seem to be stuck in a loop. Could you clarify what you'd like me to do?"
Message.objects.create(conversation=selected_conversation, content=final_message, sender='ai') Message.objects.create(conversation=conversation, content=final_message, sender='ai')
except Exception as e: except Exception as e:
logger.error(f"An unexpected error occurred: {e}", exc_info=True) logger.error(f"An unexpected error occurred in background AI process: {e}", exc_info=True)
ai_text = f"An error occurred: {str(e)}" try:
Message.objects.create(conversation=selected_conversation, content=ai_text, sender='ai') # Try to inform the user about the error
Message.objects.create(conversation_id=conversation_id, content=f"An internal error occurred: {str(e)}", sender='ai')
except Exception as e2:
logger.error(f"Could not even save the error message to the conversation: {e2}", exc_info=True)
finally:
# Ensure is_generating is always set to False
try:
conversation = Conversation.objects.get(id=conversation_id)
conversation.is_generating = False
conversation.save()
except Conversation.DoesNotExist:
logger.error(f"Conversation with ID {conversation_id} does not exist when trying to finalize background process.")
except Exception as e:
logger.error(f"Could not finalize background process for conversation {conversation_id}: {e}", exc_info=True)
def chat_view(request, conversation_id=None):
if request.method == 'POST':
# Create a new conversation
if 'title' in request.POST:
title = request.POST.get('title', 'New Conversation').strip()
if not title:
title = 'New Conversation'
conversation = Conversation.objects.create(title=title)
return redirect('core:chat_detail', conversation_id=conversation.id)
# Send a message in an existing conversation
elif 'text' in request.POST and conversation_id:
text = request.POST.get('text').strip()
if text:
conversation = get_object_or_404(Conversation, id=conversation_id)
Message.objects.create(conversation=conversation, content=text, sender='user')
# Start AI processing in a background thread
thread = threading.Thread(target=run_ai_process_in_background, args=(conversation_id,))
thread.daemon = True
thread.start()
return redirect('core:chat_detail', conversation_id=conversation_id) return redirect('core:chat_detail', conversation_id=conversation_id)
conversations = Conversation.objects.order_by('-created_at') conversations = Conversation.objects.order_by('-created_at')
selected_conversation = get_object_or_404(Conversation, id=conversation_id) if conversation_id else None selected_conversation = None
if conversation_id:
selected_conversation = get_object_or_404(Conversation, id=conversation_id)
return render(request, 'core/chat.html', { return render(request, 'core/chat.html', {
'conversation_list': conversations, 'conversation_list': conversations,
@ -340,13 +370,23 @@ def chat_view(request, conversation_id=None):
'timestamp': int(time.time()), 'timestamp': int(time.time()),
}) })
def conversation_list(request): def conversation_list(request):
conversations = Conversation.objects.order_by('-created_at') conversations = Conversation.objects.order_by('-created_at')
return render(request, 'core/conversation_list.html', {'conversation_list': conversations}) return render(request, 'core/conversation_list.html', {'conversation_list': conversations})
def get_conversation_messages(request, conversation_id):
conversation = get_object_or_404(Conversation, id=conversation_id)
messages = conversation.messages.order_by('created_at').values('sender', 'content', 'created_at')
return JsonResponse({
'messages': list(messages),
'is_generating': conversation.is_generating
})
def settings_view(request): def settings_view(request):
# Get or create the custom_instructions setting custom_instructions, _ = Setting.objects.get_or_create(
custom_instructions, created = Setting.objects.get_or_create(
key='custom_instructions', key='custom_instructions',
defaults={'value': ''} defaults={'value': ''}
) )