pooling in chat!

This commit is contained in:
Flatlogic Bot 2025-11-20 19:43:30 +00:00
parent 4c1df17a46
commit 63cf731d1f
9 changed files with 205 additions and 72 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-11-20 19:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_setting'),
]
operations = [
migrations.AddField(
model_name='conversation',
name='is_generating',
field=models.BooleanField(default=False),
),
]

View File

@ -27,6 +27,7 @@ class TodoItem(models.Model):
class Conversation(models.Model):
title = models.CharField(max_length=200)
is_generating = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):

View File

@ -81,95 +81,203 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
const messagesContainer = document.getElementById('chat-messages');
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
const textarea = document.getElementById('chat-textarea');
if (textarea) {
textarea.addEventListener('input', () => {
textarea.style.height = 'auto';
textarea.style.height = (textarea.scrollHeight) + 'px';
});
}
const chatTextarea = document.getElementById('chat-textarea');
const chatForm = document.getElementById('chat-form');
const loaderOverlay = document.getElementById('loader-overlay');
const chatTextarea = document.getElementById('chat-textarea');
const submitButton = chatForm ? chatForm.querySelector('button[type="submit"]') : null;
let pollingInterval;
if (chatForm) {
chatForm.addEventListener('submit', function() {
if (loaderOverlay) {
loaderOverlay.style.display = 'flex';
}
// Function to scroll to the bottom of the messages
function scrollToBottom() {
if (messagesContainer) {
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
}
// Auto-resize textarea
if (chatTextarea) {
chatTextarea.addEventListener('input', () => {
chatTextarea.style.height = 'auto';
chatTextarea.style.height = (chatTextarea.scrollHeight) + 'px';
});
}
// Handle form submission
if (chatForm && submitButton) {
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(chatForm);
const messageText = formData.get('text').trim();
if (!messageText) {
return;
}
submitButton.disabled = true;
submitButton.textContent = 'Sending...';
// Manually append the user's message to the UI immediately
appendMessage('user', messageText);
chatTextarea.value = '';
chatTextarea.style.height = 'auto';
fetch(chatForm.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
startPolling(data.conversation_id);
} else {
console.error('Error submitting message:', data.error);
// Re-enable button on error
submitButton.disabled = false;
submitButton.textContent = 'Send';
}
})
.catch(error => {
console.error('Fetch error:', error);
// Re-enable button on error
submitButton.disabled = false;
submitButton.textContent = 'Send';
});
});
}
// Allow Enter to submit, Shift+Enter for new line
if (chatTextarea && chatForm) {
chatTextarea.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
chatForm.submit();
chatForm.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
}
});
}
function appendMessage(sender, content) {
const messageEl = document.createElement('div');
messageEl.classList.add('message', sender);
const messageContentEl = document.createElement('div');
messageContentEl.classList.add('message-content');
const authorEl = document.createElement('div');
authorEl.classList.add('message-author');
if (sender === 'user') {
authorEl.innerText = 'You';
} else if (sender === 'ai') {
authorEl.innerText = 'AI';
} else {
authorEl.innerText = sender.charAt(0).toUpperCase() + sender.slice(1);
}
messageContentEl.appendChild(authorEl);
const pEl = document.createElement('p');
pEl.innerHTML = content.replace(/\n/g, '<br>');
messageContentEl.appendChild(pEl);
messageEl.appendChild(messageContentEl);
messagesContainer.appendChild(messageEl);
scrollToBottom();
}
function updateMessageList(messages) {
messagesContainer.innerHTML = ''; // Clear existing messages
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);
});
scrollToBottom();
}
function startPolling(conversationId) {
if (!conversationId) return;
// Clear any existing polling interval
if (pollingInterval) {
clearInterval(pollingInterval);
}
pollingInterval = setInterval(() => {
fetch(`/get_conversation_messages/${conversationId}/`)
.then(response => response.json())
.then(data => {
updateMessageList(data.messages);
if (!data.is_generating) {
clearInterval(pollingInterval);
if (submitButton) {
submitButton.disabled = false;
submitButton.textContent = 'Send';
}
} else {
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = 'Sending...';
}
}
})
.catch(error => {
console.error('Polling error:', error);
clearInterval(pollingInterval); // Stop polling on error
if (submitButton) {
submitButton.disabled = false;
submitButton.textContent = 'Send';
}
});
}, 5000);
}
// Initial state check on page load
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);
if (submitButton) {
submitButton.disabled = true;
submitButton.textContent = 'Sending...';
}
startPolling(conversationId);
}
// Initial scroll to bottom
scrollToBottom();
});
</script>

View File

@ -13,4 +13,5 @@ urlpatterns = [
path('chat/<int:conversation_id>/', views.chat_view, name='chat_detail'),
path('cleanup_tasks/', views.cleanup_tasks, name='cleanup_tasks'),
path('settings/', views.settings_view, name='settings'),
path('get_conversation_messages/<int:conversation_id>/', views.get_conversation_messages, name='get_conversation_messages'),
]

View File

@ -352,12 +352,17 @@ def chat_view(request, conversation_id=None):
conversation = get_object_or_404(Conversation, id=conversation_id)
Message.objects.create(conversation=conversation, content=text, sender='user')
# Set is_generating to True
conversation.is_generating = True
conversation.save()
# 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 JsonResponse({'status': 'success', 'conversation_id': conversation_id})
return JsonResponse({'status': 'error', 'message': 'Text is required.'}, status=400)
conversations = Conversation.objects.order_by('-created_at')
selected_conversation = None