284 lines
11 KiB
HTML
284 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Chat{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="chat-container">
|
|
<!-- Sidebar -->
|
|
<aside class="chat-sidebar">
|
|
<form method="post" action="{% url 'core:chat' %}" class="new-chat-form">
|
|
{% csrf_token %}
|
|
<input type="text" name="title" placeholder="New conversation title" required>
|
|
<button type="submit" class="new-chat-btn">+ New Chat</button>
|
|
</form>
|
|
|
|
<ul class="conversation-list">
|
|
{% for conv in conversation_list %}
|
|
<a href="{% url 'core:chat_detail' conv.id %}" class="{% if selected_conversation.id == conv.id %}active{% endif %}">
|
|
{{ conv.title }}
|
|
</a>
|
|
{% empty %}
|
|
<li>No conversations yet.</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</aside>
|
|
|
|
<!-- Main Chat Area -->
|
|
<main class="chat-main">
|
|
{% if selected_conversation %}
|
|
<header class="chat-header">
|
|
<h3>{{ selected_conversation.title }}</h3>
|
|
</header>
|
|
|
|
<div class="chat-messages" id="chat-messages">
|
|
{% for message in selected_conversation.messages.all %}
|
|
<div class="message {{ message.sender }}">
|
|
<div class="message-content">
|
|
<div class="message-author">
|
|
{% if message.sender == 'user' %}
|
|
You
|
|
{% elif message.sender == 'ai' %}
|
|
AI
|
|
{% elif message.sender == 'system' %}
|
|
System
|
|
{% elif message.sender == 'ai_command' %}
|
|
AI Command
|
|
{% endif %}
|
|
</div>
|
|
{% if message.sender == 'ai_command' %}
|
|
<pre><code>{{ message.content|linebreaksbr }}</code></pre>
|
|
{% else %}
|
|
<p>{{ message.content|linebreaksbr }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="chat-form-container">
|
|
<form method="post" action="{% url 'core:chat_detail' selected_conversation.id %}" class="chat-form" id="chat-form">
|
|
{% csrf_token %}
|
|
<textarea name="text" placeholder="Send a message..." rows="1" id="chat-textarea"></textarea>
|
|
<button type="submit">Send</button>
|
|
</form>
|
|
</div>
|
|
|
|
{% else %}
|
|
<div class="no-conversation-selected">
|
|
<div>
|
|
<h2>Gemini Chat</h2>
|
|
<p>Select a conversation or start a new one.</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="loader-overlay" id="loader-overlay" style="display: none;">
|
|
<div class="loader"></div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const messagesContainer = document.getElementById('chat-messages');
|
|
const chatForm = document.getElementById('chat-form');
|
|
const chatTextarea = document.getElementById('chat-textarea');
|
|
const submitButton = chatForm ? chatForm.querySelector('button[type="submit"]') : null;
|
|
|
|
let pollingInterval;
|
|
|
|
// 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.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) {
|
|
if (submitButton) {
|
|
submitButton.disabled = true;
|
|
submitButton.textContent = 'Sending...';
|
|
}
|
|
startPolling(conversationId);
|
|
}
|
|
|
|
// Initial scroll to bottom
|
|
scrollToBottom();
|
|
});
|
|
</script>
|
|
|
|
{% endblock %} |