|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI Chat Interface</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script> |
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css" rel="stylesheet"> |
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet"> |
|
<style> |
|
.chat-area { |
|
height: calc(100vh - 200px); |
|
overflow-y: auto; |
|
background-color: #1a1a1a; |
|
color: #e6e6e6; |
|
padding: 20px; |
|
gap: 15px; |
|
display: flex; |
|
flex-direction: column; |
|
} |
|
.message-bubble { |
|
max-width: 80%; |
|
margin: 10px; |
|
padding: 15px; |
|
border-radius: 10px; |
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); |
|
transition: all 0.2s ease; |
|
} |
|
.assistant-message { |
|
background-color: #797D7F; |
|
margin-right: auto; |
|
max-width: 80%; |
|
margin: 10px; |
|
padding: 15px; |
|
border-radius: 10px; |
|
border: 1px solid #4a5568; |
|
color: #e5e7eb; |
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1); |
|
} |
|
|
|
|
|
.user-message { |
|
background-color: #283747; |
|
margin-left: auto; |
|
max-width: 80%; |
|
margin: 10px; |
|
padding: 15px; |
|
border-radius: 10px; |
|
border: 1px solid #4a5568; |
|
color: #e5e7eb; |
|
box-shadow: 0 1px 2px rgba(0,0,0,0.1); |
|
} |
|
.chat-list { |
|
max-height: calc(100vh - 100px); |
|
overflow-y: auto; |
|
background-color: #1a1a1a; |
|
border-right: 1px solid #4a5568; |
|
color: #e6e6e6; |
|
} |
|
.chat-item { |
|
padding: 10px 15px; |
|
border-bottom: 1px solid #2d3748; |
|
color: #e6e6e6; |
|
} |
|
.chat-item:hover { |
|
background-color: #2d3748; |
|
transition: background-color 0.2s ease; |
|
} |
|
.active-chat { |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
} |
|
.copy-button { |
|
position: absolute; |
|
right: 10px; |
|
top: 5px; |
|
padding: 4px 8px; |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
opacity: 0.9; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
border: 1px solid #60a5fa; |
|
} |
|
.copy-button:hover { |
|
opacity: 1; |
|
background-color: #60a5fa; |
|
} |
|
.code-block-wrapper { |
|
position: relative; |
|
margin: 1em 0; |
|
background-color: #1e293b; |
|
border: 1px solid #4a5568; |
|
border-radius: 6px; |
|
} |
|
.copy-tooltip { |
|
position: absolute; |
|
background-color: #2d3748; |
|
color: #ffffff; |
|
padding: 4px 8px; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
right: 10px; |
|
top: -25px; |
|
opacity: 0; |
|
transition: opacity 0.2s; |
|
border: 1px solid #4a5568; |
|
} |
|
.copy-tooltip.show { |
|
opacity: 1; |
|
} |
|
.message-footer { |
|
display: flex; |
|
justify-content: flex-end; |
|
margin-top: 10px; |
|
padding-top: 8px; |
|
border-top: 1px solid #4a5568; |
|
} |
|
.retry-button { |
|
display: inline-flex; |
|
align-items: center; |
|
padding: 6px 12px; |
|
background-color: #2d3748; |
|
color: #e6e6e6; |
|
border: 1px solid #4a5568; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
} |
|
.retry-button:hover { |
|
background-color: #4a5568; |
|
color: #ffffff; |
|
} |
|
.retry-button svg { |
|
width: 14px; |
|
height: 14px; |
|
margin-right: 4px; |
|
} |
|
.retry-button.loading { |
|
opacity: 0.7; |
|
cursor: not-allowed; |
|
} |
|
.code-block-wrapper { |
|
position: relative; |
|
margin: 1em 0; |
|
padding-top: 40px; |
|
background-color: #1e293b; |
|
border: 1px solid #4a5568; |
|
border-radius: 6px; |
|
} |
|
.test-button { |
|
position: absolute; |
|
top: 5px; |
|
right: 120px; |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
padding: 6px 12px; |
|
border-radius: 4px; |
|
font-size: 12px; |
|
border: 1px solid #60a5fa; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
} |
|
.test-button:hover { |
|
background-color: #60a5fa; |
|
} |
|
.test-results { |
|
margin-top: 8px; |
|
padding: 12px; |
|
background-color: #1e293b; |
|
border-left: 4px solid #4a5568; |
|
font-family: monospace; |
|
white-space: pre-wrap; |
|
display: none; |
|
color: #e6e6e6; |
|
} |
|
.test-results.show { |
|
display: block; |
|
} |
|
.test-results.error { |
|
border-left-color: #ef4444; |
|
background-color: #2d1f1f; |
|
} |
|
.test-results.success { |
|
border-left-color: #10b981; |
|
background-color: #1a2e1f; |
|
} |
|
.loading-spinner { |
|
display: inline-block; |
|
width: 12px; |
|
height: 12px; |
|
border: 2px solid #ffffff; |
|
border-radius: 50%; |
|
border-top-color: transparent; |
|
animation: spin 1s linear infinite; |
|
} |
|
@keyframes spin { |
|
to { |
|
transform: rotate(360deg); |
|
} |
|
} |
|
.upload-button { |
|
position: relative; |
|
overflow: hidden; |
|
display: inline-block; |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
border: 1px solid #60a5fa; |
|
border-radius: 4px; |
|
transition: all 0.2s ease; |
|
padding: 6px 12px; |
|
} |
|
.upload-button:hover { |
|
background-color: #60a5fa; |
|
} |
|
.upload-button input[type=file] { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
min-width: 100%; |
|
min-height: 100%; |
|
opacity: 0; |
|
cursor: pointer; |
|
} |
|
|
|
|
|
.new-chat-button { |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
padding: 8px 16px; |
|
border-radius: 6px; |
|
border: none; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
font-weight: 500; |
|
} |
|
.new-chat-button:hover { |
|
background-color: #60a5fa; |
|
} |
|
|
|
|
|
.chat-title { |
|
color: #e6e6e6; |
|
font-weight: 600; |
|
margin-bottom: 4px; |
|
} |
|
|
|
|
|
.chat-date { |
|
color: #9ca3af; |
|
font-size: 0.875rem; |
|
} |
|
|
|
|
|
.message-input { |
|
background-color: #1e293b; |
|
border: 1px solid #4a5568; |
|
color: #e6e6e6; |
|
border-radius: 6px; |
|
padding: 12px; |
|
width: 100%; |
|
margin-bottom: 10px; |
|
} |
|
.message-input:focus { |
|
outline: none; |
|
border-color: #60a5fa; |
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); |
|
} |
|
|
|
|
|
.send-button { |
|
background-color: #3b82f6; |
|
color: #ffffff; |
|
padding: 8px 16px; |
|
border-radius: 6px; |
|
border: none; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
font-weight: 500; |
|
} |
|
.send-button:hover { |
|
background-color: #60a5fa; |
|
} |
|
.header-container { |
|
text-align: center; |
|
padding: 1rem 0; |
|
margin-bottom: 2rem; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.title-wrapper { |
|
display: inline-flex; |
|
align-items: center; |
|
gap: 0.75rem; |
|
padding: 0.5rem 1.5rem; |
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(147, 197, 253, 0.1)); |
|
border-radius: 12px; |
|
border: 1px solid rgba(59, 130, 246, 0.2); |
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
} |
|
|
|
.title { |
|
font-size: 1.75rem; |
|
font-weight: 700; |
|
background: linear-gradient(135deg, #60A5FA, #3B82F6); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
position: relative; |
|
} |
|
|
|
.logo-icon { |
|
width: 32px; |
|
height: 32px; |
|
animation: pulse 2s infinite; |
|
} |
|
|
|
.status-dot { |
|
width: 8px; |
|
height: 8px; |
|
background-color: #10B981; |
|
border-radius: 50%; |
|
position: relative; |
|
display: inline-block; |
|
margin-left: 0.5rem; |
|
animation: blink 2s infinite; |
|
} |
|
|
|
|
|
.particle { |
|
position: absolute; |
|
width: 4px; |
|
height: 4px; |
|
background: rgba(59, 130, 246, 0.2); |
|
border-radius: 50%; |
|
pointer-events: none; |
|
} |
|
|
|
.particle:nth-child(1) { animation: float-1 8s infinite; } |
|
.particle:nth-child(2) { animation: float-2 10s infinite; } |
|
.particle:nth-child(3) { animation: float-3 7s infinite; } |
|
.particle:nth-child(4) { animation: float-4 9s infinite; } |
|
|
|
@keyframes pulse { |
|
0% { transform: scale(1); } |
|
50% { transform: scale(1.1); } |
|
100% { transform: scale(1); } |
|
} |
|
|
|
@keyframes blink { |
|
0% { opacity: 1; } |
|
50% { opacity: 0.4; } |
|
100% { opacity: 1; } |
|
} |
|
|
|
@keyframes float-1 { |
|
0%, 100% { transform: translate(0, 0); } |
|
50% { transform: translate(20px, -20px); } |
|
} |
|
|
|
@keyframes float-2 { |
|
0%, 100% { transform: translate(0, 0); } |
|
50% { transform: translate(-15px, -25px); } |
|
} |
|
|
|
@keyframes float-3 { |
|
0%, 100% { transform: translate(0, 0); } |
|
50% { transform: translate(25px, -15px); } |
|
} |
|
|
|
@keyframes float-4 { |
|
0%, 100% { transform: translate(0, 0); } |
|
50% { transform: translate(-20px, -10px); } |
|
} |
|
.model-label { |
|
position: fixed; |
|
top: 1rem; |
|
right: 1rem; |
|
background-color: #f3f4f6; |
|
color: #4b5563; |
|
padding: 0.25rem 0.75rem; |
|
border-radius: 9999px; |
|
font-size: 0.875rem; |
|
font-weight: 500; |
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); |
|
z-index: 50; |
|
} |
|
.loading-indicator { |
|
position: sticky; |
|
bottom: 0; |
|
left: 0; |
|
right: 0; |
|
background: rgba(17, 24, 39, 0.95); |
|
padding: 1rem; |
|
border-top: 1px solid #4b5563; |
|
z-index: 50; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
gap: 0.5rem; |
|
} |
|
|
|
.loading-indicator.hidden { |
|
display: none; |
|
} |
|
|
|
.loading-bar { |
|
width: 200px; |
|
height: 4px; |
|
background: #374151; |
|
border-radius: 2px; |
|
overflow: hidden; |
|
} |
|
|
|
.loading-progress { |
|
width: 40%; |
|
height: 100%; |
|
background: #3b82f6; |
|
border-radius: 2px; |
|
animation: moveProgress 1.5s infinite ease-in-out; |
|
} |
|
|
|
.loading-text { |
|
color: #9ca3af; |
|
font-size: 0.875rem; |
|
font-weight: 500; |
|
} |
|
|
|
@keyframes moveProgress { |
|
0% { |
|
transform: translateX(-100%); |
|
} |
|
50% { |
|
transform: translateX(100%); |
|
} |
|
100% { |
|
transform: translateX(-100%); |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900"> |
|
<div class="model-label">Mistral:7b</div> |
|
<div class="container mx-auto px-4 py-8"> |
|
<h1 class="text-2xl font-bold text-blue-500 text-center mb-6">Figr AI Code Assistant</h1> |
|
<div class="flex gap-4"> |
|
|
|
<div class="w-1/4 bg-gray-800 rounded-lg shadow-lg p-4"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-xl font-bold text-gray-100">Chats</h2> |
|
<button onclick="createNewChat()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"> |
|
New Chat |
|
</button> |
|
</div> |
|
<div id="chatList" class="chat-list"> |
|
|
|
</div> |
|
</div> |
|
|
|
|
|
<div class="w-3/4 bg-gray-800 rounded-lg shadow-lg p-4"> |
|
<div id="chatArea" class="chat-area mb-4"> |
|
|
|
</div> |
|
|
|
|
|
<div class="flex gap-2"> |
|
<textarea |
|
id="userInput" |
|
class="w-full p-2 border rounded-lg resize-none bg-gray-700 text-gray-100" |
|
rows="3" |
|
placeholder="Type your message here..." |
|
></textarea> |
|
<div class="flex flex-col gap-2"> |
|
<label class="relative cursor-pointer bg-gray-700 hover:bg-gray-600 p-2 rounded-lg flex items-center justify-center"> |
|
<input type="file" |
|
id="fileInput" |
|
accept=".py" |
|
class="hidden" |
|
onchange="handleFileUpload(event)"/> |
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-400"> |
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
|
<polyline points="17 8 12 3 7 8"></polyline> |
|
<line x1="12" y1="3" x2="12" y2="15"></line> |
|
</svg> |
|
</label> |
|
<button |
|
onclick="sendMessage()" |
|
class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600" |
|
> |
|
Send |
|
</button> |
|
</div> |
|
</div> |
|
<div id="loadingIndicator" class="loading-indicator hidden"> |
|
<div class="loading-bar"> |
|
<div class="loading-progress"></div> |
|
</div> |
|
<div class="loading-text">Generating...</div> |
|
</div> |
|
|
|
|
|
<div class="mt-4"> |
|
<h3 class="text-lg font-bold mb-2 text-gray-100">Important Information</h3> |
|
<div id="importantInfo" class="bg-gray-800 p-4 rounded-lg text-gray-100 border border-gray-700"> |
|
|
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
let currentSessionId = null; |
|
|
|
|
|
marked.setOptions({ |
|
highlight: function(code, lang) { |
|
return hljs.highlight(code, {language: lang || 'plaintext'}).value; |
|
} |
|
}); |
|
const renderer = new marked.Renderer(); |
|
renderer.code = function(code, language) { |
|
const highlightedCode = language ? hljs.highlight(code, {language}).value : hljs.highlightAuto(code).value; |
|
return ` |
|
<div class="code-block-wrapper"> |
|
<button class="test-button">Test Code</button> |
|
<button class="copy-button">Copy Code</button> |
|
<pre><code class="hljs ${language || ''}">${highlightedCode}</code></pre> |
|
<div class="test-results"></div> |
|
</div> |
|
`; |
|
}; |
|
marked.setOptions({ renderer }); |
|
|
|
async function createNewChat() { |
|
try { |
|
const response = await fetch('/api/new-chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
} |
|
}); |
|
const data = await response.json(); |
|
if (data.success) { |
|
currentSessionId = data.chat.id; |
|
await loadChatList(); |
|
clearChatArea(); |
|
} |
|
} catch (error) { |
|
console.error('Error creating new chat:', error); |
|
} |
|
} |
|
|
|
|
|
async function loadChatList() { |
|
try { |
|
const response = await fetch('/api/chat-list'); |
|
const data = await response.json(); |
|
const chatListElement = document.getElementById('chatList'); |
|
chatListElement.innerHTML = ''; |
|
|
|
data.chats.forEach(chat => { |
|
const chatElement = document.createElement('div'); |
|
chatElement.className = `chat-item p-3 cursor-pointer rounded ${ |
|
chat.id === currentSessionId ? 'active-chat' : '' |
|
}`; |
|
chatElement.onclick = () => loadChat(chat.id); |
|
chatElement.innerHTML = ` |
|
<div class="font-medium">${chat.title || 'New Chat'}</div> |
|
<div class="text-sm text-gray-500">${new Date(chat.date).toLocaleDateString()}</div> |
|
`; |
|
chatListElement.appendChild(chatElement); |
|
}); |
|
} catch (error) { |
|
console.error('Error loading chat list:', error); |
|
} |
|
} |
|
|
|
|
|
async function loadChat(sessionId) { |
|
currentSessionId = sessionId; |
|
try { |
|
const response = await fetch(`/api/chat-history?sessionId=${sessionId}`); |
|
const data = await response.json(); |
|
displayChatHistory(data.history); |
|
displayImportantInfo(data.important_info); |
|
await loadChatList(); |
|
} catch (error) { |
|
console.error('Error loading chat:', error); |
|
} |
|
} |
|
|
|
|
|
function displayChatHistory(history) { |
|
const chatArea = document.getElementById('chatArea'); |
|
chatArea.innerHTML = ''; |
|
history.forEach(message => { |
|
displayMessage(message.role, message.content); |
|
}); |
|
scrollToBottom(); |
|
} |
|
|
|
|
|
function displayImportantInfo(info) { |
|
const infoElement = document.getElementById('importantInfo'); |
|
infoElement.innerHTML = info.length > 0 |
|
? info.map(item => `<p>• ${item}</p>`).join('') |
|
: '<p>No important information yet</p>'; |
|
} |
|
|
|
|
|
async function sendMessage() { |
|
const userInput = document.getElementById('userInput'); |
|
const message = userInput.value.trim(); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
|
|
if (!message) return; |
|
if (!currentSessionId) { |
|
await createNewChat(); |
|
} |
|
|
|
displayMessage('user', message); |
|
userInput.value = ''; |
|
scrollToBottom(); |
|
|
|
|
|
loadingIndicator.classList.remove('hidden'); |
|
|
|
try { |
|
const response = await fetch('/api/chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
message: message, |
|
sessionId: currentSessionId |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
|
|
loadingIndicator.classList.add('hidden'); |
|
|
|
if (data.success) { |
|
displayMessage('assistant', data.response); |
|
displayImportantInfo(data.important_info); |
|
await loadChatList(); |
|
} else { |
|
displayMessage('assistant', 'Error: ' + data.response); |
|
} |
|
} catch (error) { |
|
|
|
loadingIndicator.classList.add('hidden'); |
|
console.error('Error sending message:', error); |
|
displayMessage('assistant', 'Error sending message'); |
|
} |
|
scrollToBottom(); |
|
} |
|
async function handleFileUpload(event) { |
|
const file = event.target.files[0]; |
|
if (!file) return; |
|
|
|
if (!file.name.endsWith('.py')) { |
|
alert('Please upload only Python (.py) files'); |
|
return; |
|
} |
|
|
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
try { |
|
const response = await fetch('/api/upload', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
|
|
const message = `I've uploaded a Python file named "${data.filename}". Here's the code:\n\n\`\`\`python\n${data.content}\n\`\`\``; |
|
document.getElementById('userInput').value = message; |
|
await sendMessage(); |
|
|
|
|
|
const analysisMessage = data.analysis; |
|
displayMessage('assistant', analysisMessage); |
|
|
|
|
|
event.target.value = ''; |
|
} else { |
|
alert(data.error || 'Error uploading file'); |
|
} |
|
} catch (error) { |
|
console.error('Error:', error); |
|
alert('Error uploading file'); |
|
} |
|
} |
|
|
|
|
|
|
|
function displayMessage(role, content) { |
|
const chatArea = document.getElementById('chatArea'); |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message-bubble ${role}-message`; |
|
|
|
|
|
const contentDiv = document.createElement('div'); |
|
contentDiv.className = 'message-content'; |
|
|
|
|
|
const renderer = new marked.Renderer(); |
|
renderer.code = function(code, language) { |
|
const highlightedCode = language ? hljs.highlight(code, {language}).value : hljs.highlightAuto(code).value; |
|
|
|
|
|
const buttonsHtml = ` |
|
<button class="test-button">Test Code</button> |
|
<button class="copy-button">Copy Code</button> |
|
`; |
|
|
|
return ` |
|
<div class="code-block-wrapper"> |
|
${buttonsHtml} |
|
<pre><code class="hljs ${language || ''}">${highlightedCode}</code></pre> |
|
<div class="test-results"></div> |
|
</div> |
|
`; |
|
}; |
|
|
|
marked.setOptions({ renderer }); |
|
|
|
|
|
contentDiv.innerHTML = marked.parse(content); |
|
messageDiv.appendChild(contentDiv); |
|
|
|
|
|
messageDiv.querySelectorAll('.code-block-wrapper').forEach(wrapper => { |
|
|
|
if (!wrapper.querySelector('.test-button') || !wrapper.querySelector('.copy-button')) { |
|
const buttonsDiv = document.createElement('div'); |
|
buttonsDiv.innerHTML = ` |
|
<button class="test-button">Test Code</button> |
|
<button class="copy-button">Copy Code</button> |
|
`; |
|
wrapper.insertBefore(buttonsDiv, wrapper.firstChild); |
|
} |
|
|
|
const copyButton = wrapper.querySelector('.copy-button'); |
|
const testButton = wrapper.querySelector('.test-button'); |
|
const codeElement = wrapper.querySelector('code'); |
|
const resultsElement = wrapper.querySelector('.test-results') || (() => { |
|
const div = document.createElement('div'); |
|
div.className = 'test-results'; |
|
wrapper.appendChild(div); |
|
return div; |
|
})(); |
|
|
|
|
|
copyButton.addEventListener('click', async () => { |
|
try { |
|
await navigator.clipboard.writeText(codeElement.textContent); |
|
copyButton.textContent = 'Copied!'; |
|
setTimeout(() => { |
|
copyButton.textContent = 'Copy Code'; |
|
}, 2000); |
|
} catch (err) { |
|
console.error('Failed to copy:', err); |
|
} |
|
}); |
|
|
|
|
|
testButton.addEventListener('click', async () => { |
|
testButton.textContent = 'Testing...'; |
|
testButton.disabled = true; |
|
|
|
try { |
|
const response = await fetch('/api/test-code', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
code: codeElement.textContent |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
resultsElement.textContent = data.output || 'Code executed successfully with no output'; |
|
resultsElement.className = `test-results show ${data.success ? 'success' : 'error'}`; |
|
|
|
} catch (error) { |
|
resultsElement.textContent = `Error: ${error.message}`; |
|
resultsElement.className = 'test-results show error'; |
|
} finally { |
|
testButton.textContent = 'Test Code'; |
|
testButton.disabled = false; |
|
} |
|
}); |
|
}); |
|
|
|
|
|
|
|
if (role === 'assistant') { |
|
const footer = document.createElement('div'); |
|
footer.className = 'message-footer'; |
|
footer.innerHTML = ` |
|
<button class="retry-button"> |
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"> |
|
<path d="M2.5 2v6h6M21.5 22v-6h-6"/> |
|
<path d="M22 11.5A10 10 0 003.2 7.2M2 12.5a10 10 0 0018.8 4.2"/> |
|
</svg> |
|
Retry |
|
</button> |
|
`; |
|
messageDiv.appendChild(footer); |
|
|
|
|
|
const retryButton = footer.querySelector('.retry-button'); |
|
const lastUserMessage = findLastUserMessage(); |
|
if (lastUserMessage) { |
|
retryButton.addEventListener('click', () => retryMessage(lastUserMessage, messageDiv)); |
|
} |
|
} |
|
|
|
|
|
addCopyButtons(contentDiv); |
|
|
|
chatArea.appendChild(messageDiv); |
|
} |
|
|
|
|
|
function findLastUserMessage() { |
|
const chatArea = document.getElementById('chatArea'); |
|
const messages = chatArea.querySelectorAll('.message-bubble'); |
|
let lastUserMessage = null; |
|
|
|
for (let i = messages.length - 1; i >= 0; i--) { |
|
if (messages[i].classList.contains('user-message')) { |
|
const contentDiv = messages[i].querySelector('.message-content'); |
|
if (contentDiv) { |
|
lastUserMessage = contentDiv.textContent; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return lastUserMessage; |
|
} |
|
|
|
|
|
function clearChatArea() { |
|
document.getElementById('chatArea').innerHTML = ''; |
|
document.getElementById('importantInfo').innerHTML = '<p>No important information yet</p>'; |
|
document.getElementById('userInput').value = ''; |
|
} |
|
|
|
|
|
function scrollToBottom() { |
|
const chatArea = document.getElementById('chatArea'); |
|
chatArea.scrollTop = chatArea.scrollHeight; |
|
} |
|
|
|
|
|
document.getElementById('userInput').addEventListener('keydown', function(e) { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
sendMessage(); |
|
} |
|
}); |
|
|
|
|
|
window.onload = async function() { |
|
await loadChatList(); |
|
}; |
|
async function copyCode(codeElement, tooltipElement) { |
|
try { |
|
await navigator.clipboard.writeText(codeElement.textContent); |
|
tooltipElement.textContent = 'Copied!'; |
|
tooltipElement.classList.add('show'); |
|
setTimeout(() => { |
|
tooltipElement.classList.remove('show'); |
|
}, 1500); |
|
} catch (err) { |
|
console.error('Failed to copy code:', err); |
|
tooltipElement.textContent = 'Failed to copy'; |
|
tooltipElement.classList.add('show'); |
|
setTimeout(() => { |
|
tooltipElement.classList.remove('show'); |
|
}, 1500); |
|
} |
|
} |
|
async function retryMessage(originalMessage, messageElement) { |
|
if (!currentSessionId) return; |
|
|
|
const retryButton = messageElement.querySelector('.retry-button'); |
|
|
|
|
|
|
|
retryButton.classList.add('loading'); |
|
|
|
retryButton.innerHTML = ` |
|
<svg class="animate-spin" viewBox="0 0 24 24"> |
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path> |
|
</svg> |
|
Regenerating... |
|
`; |
|
|
|
try { |
|
const response = await fetch('/api/chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
message: originalMessage, |
|
sessionId: currentSessionId |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
if (data.success) { |
|
|
|
const contentDiv = messageElement.querySelector('.message-content'); |
|
contentDiv.innerHTML = marked.parse(data.response); |
|
|
|
|
|
contentDiv.querySelectorAll('pre code').forEach((block) => { |
|
hljs.highlightBlock(block); |
|
}); |
|
|
|
|
|
addCopyButtons(contentDiv); |
|
|
|
|
|
displayImportantInfo(data.important_info); |
|
} else { |
|
alert('Error regenerating response: ' + data.response); |
|
} |
|
} catch (error) { |
|
console.error('Error regenerating message:', error); |
|
alert('Error regenerating response'); |
|
} finally { |
|
|
|
retryButton.classList.remove('loading'); |
|
retryButton.innerHTML = ` |
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"> |
|
<path d="M2.5 2v6h6M21.5 22v-6h-6"/> |
|
<path d="M22 11.5A10 10 0 003.2 7.2M2 12.5a10 10 0 0018.8 4.2"/> |
|
</svg> |
|
Retry |
|
`; |
|
} |
|
} |
|
|
|
|
|
function addCopyButtons(element) { |
|
element.querySelectorAll('.code-block-wrapper').forEach(wrapper => { |
|
const copyButton = wrapper.querySelector('.copy-button'); |
|
const codeElement = wrapper.querySelector('code'); |
|
const tooltipElement = wrapper.querySelector('.copy-tooltip'); |
|
|
|
copyButton.addEventListener('click', () => copyCode(codeElement, tooltipElement)); |
|
|
|
copyButton.addEventListener('mouseenter', () => { |
|
tooltipElement.textContent = 'Copy'; |
|
tooltipElement.classList.add('show'); |
|
}); |
|
copyButton.addEventListener('mouseleave', () => { |
|
if (tooltipElement.textContent === 'Copy') { |
|
tooltipElement.classList.remove('show'); |
|
} |
|
}); |
|
}); |
|
} |
|
</script> |
|
</body> |
|
</html> |