Spaces:
Running
Running
Update public/index.html
Browse files- public/index.html +198 -1
public/index.html
CHANGED
|
@@ -234,4 +234,201 @@
|
|
| 234 |
document.getElementById('current-chat-title').innerText = 'Select or create a chat';
|
| 235 |
document.getElementById('edit-title-btn').classList.add('hidden');
|
| 236 |
updateTokenDisplay(0, 0, 0);
|
| 237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
document.getElementById('current-chat-title').innerText = 'Select or create a chat';
|
| 235 |
document.getElementById('edit-title-btn').classList.add('hidden');
|
| 236 |
updateTokenDisplay(0, 0, 0);
|
| 237 |
+
loadSidebar();
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
async function selectChat(id) {
|
| 242 |
+
currentChatId = id;
|
| 243 |
+
clearInterval(pollingInterval);
|
| 244 |
+
const res = await fetch(`/api/chats/${id}`);
|
| 245 |
+
const chat = await res.json();
|
| 246 |
+
|
| 247 |
+
document.getElementById('current-chat-title').innerText = chat.title;
|
| 248 |
+
document.getElementById('edit-title-btn').classList.remove('hidden');
|
| 249 |
+
|
| 250 |
+
updateTokenDisplay(chat.totalTokens, chat.inputTokens, chat.outputTokens);
|
| 251 |
+
|
| 252 |
+
cancelTitleEdit();
|
| 253 |
+
renderMessages(chat.messages);
|
| 254 |
+
loadSidebar();
|
| 255 |
+
|
| 256 |
+
if(window.innerWidth < 1024 && !document.getElementById('sidebar').classList.contains('-translate-x-full')) {
|
| 257 |
+
toggleSidebar();
|
| 258 |
+
}
|
| 259 |
+
if (chat.isGenerating) pollGeneratingChat(id);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
function renderMessages(messages) {
|
| 263 |
+
const container = document.getElementById('chat-window');
|
| 264 |
+
container.innerHTML = messages.map(m => {
|
| 265 |
+
let html = '';
|
| 266 |
+
if (m.reasoning) {
|
| 267 |
+
html += `<div class="reasoning-block"><i>Thinking Process</i><br/>${marked.parse(m.reasoning)}</div>`;
|
| 268 |
+
}
|
| 269 |
+
if (m.content) {
|
| 270 |
+
html += DOMPurify.sanitize(marked.parse(m.content));
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
return `
|
| 274 |
+
<div class="flex ${m.role === 'user' ? 'justify-end' : 'justify-start'} w-full">
|
| 275 |
+
<div class="max-w-[95%] lg:max-w-[85%] rounded-2xl p-4 shadow-sm ${m.role === 'user' ? 'bg-blue-600 text-white rounded-br-sm' : 'bg-gray-850 border border-gray-800 text-gray-200 markdown-body rounded-bl-sm'}">
|
| 276 |
+
${m.role === 'user' ? `<div class="whitespace-pre-wrap">${m.content}</div>` : html}
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
`;
|
| 280 |
+
}).join('');
|
| 281 |
+
|
| 282 |
+
document.querySelectorAll('.markdown-body pre').forEach(pre => {
|
| 283 |
+
if (!pre.querySelector('.copy-btn')) {
|
| 284 |
+
const btn = document.createElement('button');
|
| 285 |
+
btn.className = 'copy-btn';
|
| 286 |
+
btn.innerText = 'Copy';
|
| 287 |
+
btn.onclick = () => {
|
| 288 |
+
const codeText = pre.querySelector('code')?.innerText || pre.innerText.replace('Copy', '');
|
| 289 |
+
navigator.clipboard.writeText(codeText.trim());
|
| 290 |
+
btn.innerText = 'Copied!';
|
| 291 |
+
setTimeout(() => btn.innerText = 'Copy', 2000);
|
| 292 |
+
};
|
| 293 |
+
pre.appendChild(btn);
|
| 294 |
+
}
|
| 295 |
+
});
|
| 296 |
+
container.scrollTop = container.scrollHeight;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
function pollGeneratingChat(id) {
|
| 300 |
+
pollingInterval = setInterval(async () => {
|
| 301 |
+
const res = await fetch(`/api/chats/${id}`);
|
| 302 |
+
const chat = await res.json();
|
| 303 |
+
renderMessages(chat.messages);
|
| 304 |
+
updateTokenDisplay(chat.totalTokens, chat.inputTokens, chat.outputTokens);
|
| 305 |
+
|
| 306 |
+
if (chat.title !== document.getElementById('current-chat-title').innerText && document.getElementById('title-display').classList.contains('hidden') === false) {
|
| 307 |
+
document.getElementById('current-chat-title').innerText = chat.title;
|
| 308 |
+
loadSidebar();
|
| 309 |
+
}
|
| 310 |
+
if (!chat.isGenerating) clearInterval(pollingInterval);
|
| 311 |
+
}, 1500);
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
function handleImageSelect(event) {
|
| 315 |
+
const files = event.target.files;
|
| 316 |
+
const container = document.getElementById('image-preview-container');
|
| 317 |
+
container.classList.remove('hidden');
|
| 318 |
+
|
| 319 |
+
Array.from(files).forEach(file => {
|
| 320 |
+
const reader = new FileReader();
|
| 321 |
+
reader.onload = (e) => {
|
| 322 |
+
attachedImages.push(e.target.result);
|
| 323 |
+
container.innerHTML += `
|
| 324 |
+
<div class="relative group">
|
| 325 |
+
<img src="${e.target.result}" class="h-14 w-14 object-cover rounded-lg border border-gray-600 shadow-sm">
|
| 326 |
+
</div>`;
|
| 327 |
+
};
|
| 328 |
+
reader.readAsDataURL(file);
|
| 329 |
+
});
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
async function sendMessage() {
|
| 333 |
+
const input = document.getElementById('message-input');
|
| 334 |
+
const text = input.value.trim();
|
| 335 |
+
if (!text || !currentChatId) return;
|
| 336 |
+
|
| 337 |
+
const payload = {
|
| 338 |
+
model: document.getElementById('model-select').value,
|
| 339 |
+
prompt: text,
|
| 340 |
+
images: attachedImages
|
| 341 |
+
};
|
| 342 |
+
|
| 343 |
+
const container = document.getElementById('chat-window');
|
| 344 |
+
container.innerHTML += `
|
| 345 |
+
<div class="flex justify-end w-full">
|
| 346 |
+
<div class="max-w-[95%] lg:max-w-[85%] rounded-2xl rounded-br-sm p-4 bg-blue-600 text-white shadow-sm whitespace-pre-wrap">${text}</div>
|
| 347 |
+
</div>
|
| 348 |
+
<div class="flex justify-start w-full" id="temp-ai-wrapper">
|
| 349 |
+
<div class="max-w-[95%] lg:max-w-[85%] rounded-2xl rounded-bl-sm p-4 bg-gray-850 border border-gray-800 text-gray-200 markdown-body shadow-sm" id="temp-ai-msg">
|
| 350 |
+
<span class="flex items-center gap-2 text-gray-400">
|
| 351 |
+
<svg class="animate-spin h-4 w-4" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
| 352 |
+
Thinking...
|
| 353 |
+
</span>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
`;
|
| 357 |
+
container.scrollTop = container.scrollHeight;
|
| 358 |
+
|
| 359 |
+
input.value = '';
|
| 360 |
+
input.style.height = 'auto';
|
| 361 |
+
attachedImages =[];
|
| 362 |
+
document.getElementById('image-preview-container').innerHTML = '';
|
| 363 |
+
document.getElementById('image-preview-container').classList.add('hidden');
|
| 364 |
+
document.getElementById('send-btn').disabled = true;
|
| 365 |
+
|
| 366 |
+
try {
|
| 367 |
+
const response = await fetch(`/api/chats/${currentChatId}/stream`, {
|
| 368 |
+
method: 'POST',
|
| 369 |
+
headers: { 'Content-Type': 'application/json' },
|
| 370 |
+
body: JSON.stringify(payload)
|
| 371 |
+
});
|
| 372 |
+
|
| 373 |
+
const reader = response.body.getReader();
|
| 374 |
+
const decoder = new TextDecoder("utf-8");
|
| 375 |
+
let aiContent = "";
|
| 376 |
+
let aiReasoning = "";
|
| 377 |
+
|
| 378 |
+
while (true) {
|
| 379 |
+
const { value, done } = await reader.read();
|
| 380 |
+
if (done) break;
|
| 381 |
+
|
| 382 |
+
const chunkText = decoder.decode(value, { stream: true });
|
| 383 |
+
const chunks = chunkText.split(/(__THINK__|__USAGE__)/);
|
| 384 |
+
let i = 0;
|
| 385 |
+
|
| 386 |
+
while (i < chunks.length) {
|
| 387 |
+
if (chunks[i] === '__THINK__') {
|
| 388 |
+
aiReasoning += chunks[i+1];
|
| 389 |
+
i += 2;
|
| 390 |
+
} else if (chunks[i] === '__USAGE__') {
|
| 391 |
+
const usageData = JSON.parse(chunks[i+1]);
|
| 392 |
+
// Update the live token state globally
|
| 393 |
+
updateTokenDisplay(
|
| 394 |
+
currentTokens.total + usageData.totalTokens,
|
| 395 |
+
currentTokens.in + usageData.inputTokens,
|
| 396 |
+
currentTokens.out + usageData.outputTokens
|
| 397 |
+
);
|
| 398 |
+
i += 2;
|
| 399 |
+
} else {
|
| 400 |
+
aiContent += chunks[i];
|
| 401 |
+
i++;
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
let tempHtml = "";
|
| 406 |
+
if (aiReasoning) tempHtml += `<div class="reasoning-block"><i>Thinking Process</i><br/>${marked.parse(aiReasoning)}</div>`;
|
| 407 |
+
if (aiContent) tempHtml += DOMPurify.sanitize(marked.parse(aiContent));
|
| 408 |
+
|
| 409 |
+
document.getElementById('temp-ai-msg').innerHTML = tempHtml || "<span class='animate-pulse text-gray-400'>Thinking...</span>";
|
| 410 |
+
container.scrollTop = container.scrollHeight;
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
// Force final refresh of states from db
|
| 414 |
+
selectChat(currentChatId);
|
| 415 |
+
|
| 416 |
+
} catch (error) {
|
| 417 |
+
console.error(error);
|
| 418 |
+
document.getElementById('temp-ai-msg').innerHTML = "<span class='text-red-400'>Error connecting to API.</span>";
|
| 419 |
+
} finally {
|
| 420 |
+
document.getElementById('send-btn').disabled = false;
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
document.getElementById('message-input').addEventListener('keydown', (e) => {
|
| 425 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 426 |
+
e.preventDefault();
|
| 427 |
+
sendMessage();
|
| 428 |
+
}
|
| 429 |
+
});
|
| 430 |
+
|
| 431 |
+
loadSidebar();
|
| 432 |
+
</script>
|
| 433 |
+
</body>
|
| 434 |
+
</html>
|