pooling in chat!
This commit is contained in:
parent
4c1df17a46
commit
63cf731d1f
Binary file not shown.
Binary file not shown.
Binary file not shown.
18
core/migrations/0007_conversation_is_generating.py
Normal file
18
core/migrations/0007_conversation_is_generating.py
Normal 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),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -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):
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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'),
|
||||
]
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user