2025-11-20 19:43:30 +00:00

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 %}