|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Agent Chat</title>
|
|
<link rel="icon" href="data:," />
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element"></script>
|
|
<emoji-picker id="emoji-picker" style="position: absolute; bottom: 60px; right: 20px; display: none;"></emoji-picker>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(135deg, rgb(44, 65, 112), #151f35);
|
|
color: #f0f0f0;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.container {
|
|
max-width: 900px;
|
|
margin: 40px auto;
|
|
padding: 20px;
|
|
border-radius: 20px;
|
|
backdrop-filter: blur(12px);
|
|
background: rgba(255, 255, 255, 0.04);
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
|
transition: all 0.3s ease-in-out;
|
|
width:90%;
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
font-size: 2.2rem;
|
|
margin-bottom: 30px;
|
|
color: #7ee8fa;
|
|
letter-spacing: 1px;
|
|
text-shadow: 0 2px 4px #000;
|
|
}
|
|
|
|
.chat-container {
|
|
height: 450px;
|
|
overflow-y: auto;
|
|
padding: 20px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 15px;
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.25);
|
|
backdrop-filter: blur(8px);
|
|
animation: fadeInUp 0.6s ease;
|
|
}
|
|
@keyframes fadeInUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(15px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.chat-container::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
.chat-container::-webkit-scrollbar-track {
|
|
background: #0f1b33;
|
|
border-radius: 4px;
|
|
}
|
|
.chat-container::-webkit-scrollbar-thumb {
|
|
background: #444;
|
|
border-radius: 4px;
|
|
}
|
|
.chat-container::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
.chat-container {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #444 #0f1b33;
|
|
}
|
|
.message {
|
|
margin-bottom: 15px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
opacity: 0;
|
|
animation: fadeIn 0.5s ease forwards;
|
|
}
|
|
.message.user {
|
|
justify-content: flex-end;
|
|
}
|
|
.message.agent {
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.bubble {
|
|
padding: 8px 14px;
|
|
font-size: 14px;
|
|
line-height: 1.2;
|
|
border-radius: 14px;
|
|
max-width: 70%;
|
|
word-wrap: break-word;
|
|
position: relative;
|
|
line-height: 1.5;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.bubble.user {
|
|
background: linear-gradient(145deg, #007bff, #0056b3);
|
|
color: white;
|
|
border-bottom-right-radius: 0;
|
|
}
|
|
|
|
.bubble.agent {
|
|
background: linear-gradient(145deg, #2d3a55, #1b2a3a);
|
|
color: #f3f3f3;
|
|
border-bottom-left-radius: 0;
|
|
}
|
|
|
|
.bubble.agent.thought-bubble {
|
|
background: rgba(16, 52, 74, 0.8);
|
|
font-style: italic;
|
|
border-left: 4px solid #00e0ff;
|
|
color: #7ee8fa;
|
|
}
|
|
|
|
.thought-toggle {
|
|
color: #ff8800;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
display: inline-block;
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.input-area {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
align-items: center;
|
|
}
|
|
|
|
.input-area textarea {
|
|
flex: 1;
|
|
padding: 12px;
|
|
border-radius: 10px;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: #eaeaea;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
resize: none;
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.input-area textarea:focus {
|
|
outline: none;
|
|
box-shadow: 0 0 8px #00e0ff;
|
|
}
|
|
|
|
.input-area button {
|
|
padding: 12px 20px;
|
|
border-radius: 10px;
|
|
border: none;
|
|
background: #00a8ff;
|
|
color: #fff;
|
|
cursor: pointer;
|
|
transition: background 0.3s;
|
|
}
|
|
#upload-db {
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
#upload-db img {
|
|
width: 28px;
|
|
height: 28px;
|
|
}
|
|
.input-area button:disabled {
|
|
background-color: #555;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.input-area button:hover:not(:disabled) {
|
|
background: #0077c2;
|
|
}
|
|
|
|
.toast {
|
|
position: fixed;
|
|
bottom: 25px;
|
|
right: 25px;
|
|
background: #2c3e50;
|
|
padding: 12px 22px;
|
|
border-radius: 12px;
|
|
font-size: 0.95rem;
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
|
opacity: 0.95;
|
|
display: none;
|
|
z-index: 1000;
|
|
transition: all 0.3s ease-in-out;
|
|
}
|
|
|
|
.dot {
|
|
animation: blink 1.2s infinite;
|
|
display: inline-block;
|
|
margin-left: 2px;
|
|
}
|
|
.dot:nth-child(2) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
.dot:nth-child(3) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
|
|
@keyframes blink {
|
|
0% {
|
|
opacity: 0.2;
|
|
}
|
|
20% {
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
opacity: 0.2;
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
.thought-toggle {
|
|
margin-top: 4px;
|
|
font-size: 0.8rem;
|
|
color: #ffbb00;
|
|
cursor: pointer;
|
|
transition: color 0.3s;
|
|
}
|
|
.thought-toggle:hover {
|
|
color: #fff000;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Agent Chat</h1>
|
|
<div class="chat-container" id="chat"></div>
|
|
<div class="input-area">
|
|
<textarea id="prompt" rows="2" placeholder="Type your message…"></textarea>
|
|
<button id="upload-db" title="Upload Database"><img src="/static/db_icon.svg" alt="DB" /></button>
|
|
<button id="send">Send</button>
|
|
|
|
<input type="file" id="file-input" accept=".db" style="display:none;" />
|
|
</div>
|
|
</div>
|
|
<div id="toast" class="toast"></div>
|
|
|
|
<script>
|
|
const socket = io()
|
|
const chat = document.getElementById('chat')
|
|
const toast = document.getElementById('toast')
|
|
const sendBtn = document.getElementById('send')
|
|
const promptTA = document.getElementById('prompt')
|
|
const uploadBtn = document.getElementById('upload-db')
|
|
const fileInput = document.getElementById('file-input')
|
|
|
|
let typingBubble = null
|
|
let agentBubble = null
|
|
|
|
function addMessage(sender, html) {
|
|
const msg = document.createElement('div')
|
|
msg.className = `message ${sender}`
|
|
const bubble = document.createElement('div')
|
|
bubble.className = `bubble ${sender}`
|
|
bubble.innerHTML = marked.parse(html)
|
|
msg.appendChild(bubble)
|
|
chat.appendChild(msg)
|
|
chat.scrollTop = chat.scrollHeight
|
|
}
|
|
|
|
function showToast(text, duration = 3000) {
|
|
toast.textContent = text
|
|
toast.style.display = 'block'
|
|
setTimeout(() => (toast.style.display = 'none'), duration)
|
|
}
|
|
|
|
|
|
uploadBtn.addEventListener('click', () => fileInput.click())
|
|
fileInput.addEventListener('change', e => handleFile(e.target.files[0]));
|
|
|
|
function handleFile(file) {
|
|
if (!file) return
|
|
showToast('Uploading database…')
|
|
const fd = new FormData()
|
|
fd.append('file', file)
|
|
fetch('/upload_db', { method: 'POST', body: fd })
|
|
.then((r) => r.json())
|
|
.then((data) => {
|
|
if (data.success || data.status === 'ok' || data.success === true) {
|
|
showToast('Database initialized!', 2000)
|
|
addMessage('agent', 'File uploaded and processed successfully!')
|
|
} else {
|
|
addMessage('agent', `Error: ${data.message}`)
|
|
}
|
|
})
|
|
.catch((err) => addMessage('agent', `Upload error: ${err}`))
|
|
}
|
|
|
|
|
|
function sendPrompt() {
|
|
const text = promptTA.value.trim()
|
|
if (!text || sendBtn.disabled) return
|
|
addMessage('user', text)
|
|
promptTA.value = ''
|
|
sendBtn.disabled = true
|
|
|
|
|
|
typingBubble = document.createElement('div')
|
|
typingBubble.className = 'message agent'
|
|
typingBubble.innerHTML = `<div class="bubble agent">Thinking<span class="dot">.</span><span class="dot">.</span><span class="dot">.</span></div>`
|
|
chat.appendChild(typingBubble)
|
|
chat.scrollTop = chat.scrollHeight
|
|
|
|
fetch('/generate', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ prompt: text })
|
|
}).catch((err) => {
|
|
addMessage('agent', `Error: ${err}`)
|
|
sendBtn.disabled = false
|
|
if (typingBubble) chat.removeChild(typingBubble)
|
|
})
|
|
}
|
|
|
|
|
|
sendBtn.addEventListener('click', () => {
|
|
const text = promptTA.value.trim()
|
|
if (!text) return
|
|
addMessage('user', text)
|
|
socket.emit('prompt', text)
|
|
promptTA.value = ''
|
|
})
|
|
|
|
socket.on('response', (text) => {
|
|
addMessage('agent', text)
|
|
})
|
|
|
|
socket.on('thought', (text) => {
|
|
if (agentBubble) agentBubble.remove()
|
|
|
|
const msg = document.createElement('div')
|
|
msg.className = 'message agent'
|
|
|
|
agentBubble = document.createElement('div')
|
|
agentBubble.className = 'bubble agent thought-bubble'
|
|
agentBubble.textContent = text
|
|
msg.appendChild(agentBubble)
|
|
chat.appendChild(msg)
|
|
chat.scrollTop = chat.scrollHeight
|
|
})
|
|
|
|
promptTA.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault()
|
|
sendPrompt()
|
|
}
|
|
})
|
|
|
|
|
|
socket.on('final_stream', (data) => {
|
|
if (!agentBubble) {
|
|
agentBubble = document.createElement('div')
|
|
agentBubble.className = 'message agent'
|
|
const b = document.createElement('div')
|
|
b.className = 'bubble agent'
|
|
agentBubble.appendChild(b)
|
|
chat.appendChild(agentBubble)
|
|
}
|
|
const b = agentBubble.querySelector('.bubble')
|
|
b.textContent += data.message
|
|
chat.scrollTop = chat.scrollHeight
|
|
})
|
|
|
|
socket.on('final', (data) => {
|
|
if (typingBubble) {
|
|
chat.removeChild(typingBubble)
|
|
typingBubble = null
|
|
}
|
|
agentBubble = null
|
|
sendBtn.disabled = false
|
|
|
|
|
|
const msg = document.createElement('div')
|
|
msg.className = 'message agent'
|
|
const bubble = document.createElement('div')
|
|
bubble.className = 'bubble agent'
|
|
msg.appendChild(bubble)
|
|
chat.appendChild(msg)
|
|
chat.scrollTop = chat.scrollHeight
|
|
|
|
const text = data.message
|
|
let index = 0
|
|
const typingInterval = setInterval(() => {
|
|
if (index < text.length) {
|
|
bubble.textContent += text.charAt(index)
|
|
index++
|
|
chat.scrollTop = chat.scrollHeight
|
|
} else {
|
|
clearInterval(typingInterval)
|
|
}
|
|
}, 50)
|
|
})
|
|
function scrollToBottom() {
|
|
chat.scrollTop = chat.scrollHeight
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|