|
{% extends "base.html" %} |
|
|
|
{% block title %}Chat - PatChat{% endblock %} |
|
|
|
{% block content %} |
|
<div class="row"> |
|
<div class="col-md-3"> |
|
|
|
<div class="system-prompt-container"> |
|
<h6 class="mb-3"> |
|
<i class="bi bi-gear"></i> System Prompt |
|
</h6> |
|
|
|
<div class="mb-3"> |
|
<label for="promptSelect" class="form-label">Choose prompt type:</label> |
|
<select class="form-select" id="promptSelect"> |
|
<option value="default">Default</option> |
|
<option value="creative">Creative</option> |
|
<option value="professional">Professional</option> |
|
<option value="educational">Educational</option> |
|
<option value="coding">Coding</option> |
|
<option value="writing">Writing</option> |
|
<option value="custom">Custom</option> |
|
</select> |
|
</div> |
|
|
|
<div class="mb-3" id="customPromptContainer" style="display: none;"> |
|
<label for="customPrompt" class="form-label">Custom prompt:</label> |
|
<textarea class="form-control" id="customPrompt" rows="3" placeholder="Enter your custom system prompt..."></textarea> |
|
</div> |
|
|
|
<div class="mb-3"> |
|
<label for="modelSelect" class="form-label">AI Model:</label> |
|
<select class="form-select" id="modelSelect"> |
|
{% for model_id, model_info in ai_models.items() %} |
|
<option value="{{ model_id }}" data-description="{{ model_info.description }}" data-cost="{{ model_info.cost_per_1k_tokens }}"> |
|
{{ model_info.name }} |
|
</option> |
|
{% endfor %} |
|
</select> |
|
<small class="form-text text-muted" id="modelDescription"> |
|
{{ ai_models['gpt-3.5-turbo'].description }} |
|
</small> |
|
<small class="form-text text-muted d-block" id="modelCost"> |
|
Cost: ${{ "%.4f"|format(ai_models['gpt-3.5-turbo'].cost_per_1k_tokens) }} per 1K tokens |
|
</small> |
|
</div> |
|
|
|
<button class="btn btn-primary btn-sm" id="newConversationBtn"> |
|
<i class="bi bi-plus-circle"></i> New Conversation |
|
</button> |
|
</div> |
|
|
|
|
|
{% if conversation %} |
|
<div class="card mt-3"> |
|
<div class="card-body"> |
|
<h6 class="card-title"> |
|
<i class="bi bi-chat-text"></i> Current Conversation |
|
</h6> |
|
<p class="card-text small">{{ conversation.title }}</p> |
|
<p class="card-text small text-muted"> |
|
Created: {{ conversation.created_at.strftime('%Y-%m-%d %H:%M') }} |
|
</p> |
|
</div> |
|
</div> |
|
{% endif %} |
|
</div> |
|
|
|
<div class="col-md-9"> |
|
|
|
<div class="chat-container"> |
|
|
|
<div class="chat-messages" id="chatMessages"> |
|
{% if conversation and conversation.messages %} |
|
{% for message in conversation.messages %} |
|
<div class="message {{ message.role }}"> |
|
<div class="message-avatar"> |
|
{% if message.role == 'user' %} |
|
<i class="bi bi-person"></i> |
|
{% else %} |
|
<i class="bi bi-robot"></i> |
|
{% endif %} |
|
</div> |
|
<div class="message-content"> |
|
{{ message.content | nl2br }} |
|
</div> |
|
</div> |
|
{% endfor %} |
|
{% else %} |
|
<div class="text-center text-muted mt-5"> |
|
<i class="bi bi-chat-dots" style="font-size: 3rem;"></i> |
|
<p class="mt-3">Start a conversation with AI</p> |
|
</div> |
|
{% endif %} |
|
</div> |
|
|
|
|
|
<div class="loading" id="loadingIndicator"> |
|
<div class="spinner-border text-primary" role="status"> |
|
<span class="visually-hidden">Loading...</span> |
|
</div> |
|
<p class="mt-2 text-muted">AI is thinking...</p> |
|
</div> |
|
|
|
|
|
<div class="chat-input-container"> |
|
<form id="messageForm"> |
|
<div class="input-group"> |
|
<textarea |
|
class="form-control" |
|
id="messageInput" |
|
rows="2" |
|
placeholder="Type your message here..." |
|
style="resize: none;" |
|
></textarea> |
|
<button class="btn btn-primary" type="submit" id="sendButton" aria-label="Send message"> |
|
<i class="bi bi-send"></i> |
|
</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
{% endblock %} |
|
|
|
{% block extra_js %} |
|
<script> |
|
|
|
let currentConversationId = {% if conversation %}{{ conversation.id }}{% else %}null{% endif %}; |
|
const systemPrompts = {{ system_prompts | tojson }}; |
|
const aiModels = {{ ai_models | tojson }}; |
|
|
|
|
|
const messageForm = document.getElementById('messageForm'); |
|
const messageInput = document.getElementById('messageInput'); |
|
const chatMessages = document.getElementById('chatMessages'); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
const promptSelect = document.getElementById('promptSelect'); |
|
const customPromptContainer = document.getElementById('customPromptContainer'); |
|
const customPrompt = document.getElementById('customPrompt'); |
|
const modelSelect = document.getElementById('modelSelect'); |
|
const newConversationBtn = document.getElementById('newConversationBtn'); |
|
|
|
|
|
messageForm.addEventListener('submit', handleMessageSubmit); |
|
promptSelect.addEventListener('change', handlePromptChange); |
|
modelSelect.addEventListener('change', handleModelChange); |
|
newConversationBtn.addEventListener('submit', handleNewConversation); |
|
|
|
|
|
async function handleMessageSubmit(e) { |
|
e.preventDefault(); |
|
|
|
const content = messageInput.value.trim(); |
|
if (!content) return; |
|
|
|
|
|
const modelType = modelSelect.value; |
|
const promptType = promptSelect.value; |
|
const systemPrompt = promptType === 'custom' ? customPrompt.value : systemPrompts[promptType]; |
|
|
|
|
|
messageInput.value = ''; |
|
|
|
|
|
addMessage(content, 'user'); |
|
|
|
|
|
showLoading(true); |
|
|
|
try { |
|
const response = await fetch('/send_message', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
conversation_id: currentConversationId, |
|
content: content, |
|
model_type: modelType, |
|
system_prompt: systemPrompt |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
|
|
if (!currentConversationId) { |
|
currentConversationId = data.conversation_id; |
|
|
|
window.history.pushState({}, '', `/conversation/${currentConversationId}`); |
|
} |
|
|
|
|
|
addMessage(data.response, 'assistant'); |
|
} else { |
|
throw new Error(data.error || 'Failed to send message'); |
|
} |
|
} catch (error) { |
|
console.error('Error:', error); |
|
addMessage('Sorry, there was an error processing your message. Please try again.', 'assistant'); |
|
} finally { |
|
showLoading(false); |
|
} |
|
} |
|
|
|
|
|
async function handleNewConversation(e) { |
|
e.preventDefault(); |
|
|
|
const modelType = modelSelect.value; |
|
const promptType = promptSelect.value; |
|
const systemPrompt = promptType === 'custom' ? customPrompt.value : systemPrompts[promptType]; |
|
|
|
try { |
|
const response = await fetch('/new_conversation', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
model_type: modelType, |
|
system_prompt: systemPrompt |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
|
|
window.location.href = data.redirect_url; |
|
} else { |
|
throw new Error(data.error || 'Failed to create conversation'); |
|
} |
|
} catch (error) { |
|
console.error('Error:', error); |
|
alert('Failed to create new conversation. Please try again.'); |
|
} |
|
} |
|
|
|
|
|
function handlePromptChange() { |
|
const promptType = promptSelect.value; |
|
if (promptType === 'custom') { |
|
customPromptContainer.style.display = 'block'; |
|
} else { |
|
customPromptContainer.style.display = 'none'; |
|
customPrompt.value = ''; |
|
} |
|
} |
|
|
|
|
|
function handleModelChange() { |
|
const modelId = modelSelect.value; |
|
const modelInfo = aiModels[modelId]; |
|
|
|
if (modelInfo) { |
|
document.getElementById('modelDescription').textContent = modelInfo.description; |
|
document.getElementById('modelCost').textContent = `Cost: $${modelInfo.cost_per_1k_tokens.toFixed(4)} per 1K tokens`; |
|
} |
|
} |
|
|
|
|
|
function addMessage(content, role) { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message ${role}`; |
|
|
|
const avatar = document.createElement('div'); |
|
avatar.className = 'message-avatar'; |
|
avatar.innerHTML = role === 'user' ? '<i class="bi bi-person"></i>' : '<i class="bi bi-robot"></i>'; |
|
|
|
const messageContent = document.createElement('div'); |
|
messageContent.className = 'message-content'; |
|
messageContent.textContent = content; |
|
|
|
messageDiv.appendChild(avatar); |
|
messageDiv.appendChild(messageContent); |
|
|
|
chatMessages.appendChild(messageDiv); |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
} |
|
|
|
|
|
function showLoading(show) { |
|
loadingIndicator.style.display = show ? 'block' : 'none'; |
|
document.getElementById('sendButton').disabled = show; |
|
} |
|
|
|
|
|
messageInput.addEventListener('input', function() { |
|
this.style.height = 'auto'; |
|
this.style.height = Math.min(this.scrollHeight, 120) + 'px'; |
|
}); |
|
|
|
|
|
messageInput.addEventListener('keydown', function(e) { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
messageForm.dispatchEvent(new Event('submit')); |
|
} |
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
handlePromptChange(); |
|
|
|
|
|
handleModelChange(); |
|
|
|
|
|
messageInput.focus(); |
|
}); |
|
</script> |
|
{% endblock %} |