Spaces:
Sleeping
Sleeping
Update templates/index.html
Browse files- templates/index.html +282 -205
templates/index.html
CHANGED
|
@@ -211,6 +211,16 @@
|
|
| 211 |
</div>
|
| 212 |
<input id="file-upload-hidden" name="file" type="file" accept="image/*" class="hidden"/>
|
| 213 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
</div>
|
| 215 |
|
| 216 |
<!-- Submit Button -->
|
|
@@ -325,9 +335,14 @@
|
|
| 325 |
const chatSubmit = document.getElementById('chat-submit');
|
| 326 |
const chatForm = document.getElementById('chat-form');
|
| 327 |
const promptsContainer = document.getElementById('predefined-prompts-container');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
|
| 329 |
let predictionContext = null;
|
| 330 |
let chatHistory = [];
|
|
|
|
| 331 |
|
| 332 |
// File upload handling
|
| 333 |
uploadZone.addEventListener('click', () => fileUploadHidden.click());
|
|
@@ -362,209 +377,271 @@
|
|
| 362 |
uploadZone.style.display = 'block';
|
| 363 |
fileUploadHidden.value = '';
|
| 364 |
});
|
| 365 |
-
|
| 366 |
-
function handleFileSelect(file) {
|
| 367 |
-
if (!file.type.startsWith('image/')) {
|
| 368 |
-
alert('Please select an image file.');
|
| 369 |
-
return;
|
| 370 |
-
}
|
| 371 |
-
if (file.size > 10 * 1024 * 1024) {
|
| 372 |
-
alert('File size must be less than 10MB.');
|
| 373 |
-
return;
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
const reader = new FileReader();
|
| 377 |
-
reader.onload = (e) => {
|
| 378 |
-
imagePreview.src = e.target.result;
|
| 379 |
-
imagePreviewContainer.classList.remove('hidden');
|
| 380 |
-
uploadZone.style.display = 'none';
|
| 381 |
-
};
|
| 382 |
-
reader.readAsDataURL(file);
|
| 383 |
-
}
|
| 384 |
-
|
| 385 |
-
uploadForm.addEventListener('submit', async (e) => {
|
| 386 |
-
e.preventDefault();
|
| 387 |
-
if (!fileUploadHidden.files[0]) {
|
| 388 |
-
alert("Please select an image file first.");
|
| 389 |
-
return;
|
| 390 |
-
}
|
| 391 |
-
|
| 392 |
-
const submitButton = uploadForm.querySelector('button[type="submit"]');
|
| 393 |
-
const originalText = submitButton.innerHTML;
|
| 394 |
-
submitButton.disabled = true;
|
| 395 |
-
submitButton.innerHTML = `
|
| 396 |
-
<div class="flex items-center">
|
| 397 |
-
<div class="flex space-x-1 mr-2">
|
| 398 |
-
<div class="typing-dot"></div>
|
| 399 |
-
<div class="typing-dot"></div>
|
| 400 |
-
<div class="typing-dot"></div>
|
| 401 |
-
</div>
|
| 402 |
-
Analyzing...
|
| 403 |
-
</div>
|
| 404 |
-
`;
|
| 405 |
-
|
| 406 |
-
// Reset states
|
| 407 |
-
resultsSection.classList.add('hidden');
|
| 408 |
-
chatCard.classList.add('opacity-50', 'pointer-events-none');
|
| 409 |
-
chatStatus.textContent = 'Analyzing';
|
| 410 |
-
chatStatus.className = 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs';
|
| 411 |
-
chatInput.disabled = true;
|
| 412 |
-
chatSubmit.disabled = true;
|
| 413 |
-
|
| 414 |
-
chatMessages.innerHTML = `
|
| 415 |
-
<div class="bot-message p-3 rounded-xl max-w-[90%]">
|
| 416 |
-
<div class="flex items-center mb-2">
|
| 417 |
-
<div class="flex space-x-1 mr-2">
|
| 418 |
-
<div class="typing-dot"></div>
|
| 419 |
-
<div class="typing-dot"></div>
|
| 420 |
-
<div class="typing-dot"></div>
|
| 421 |
-
</div>
|
| 422 |
-
<span class="text-xs text-gray-500">ANALYZING</span>
|
| 423 |
-
</div>
|
| 424 |
-
Processing your medical image...
|
| 425 |
-
</div>
|
| 426 |
-
`;
|
| 427 |
-
|
| 428 |
-
predictionContext = null;
|
| 429 |
-
chatHistory = [];
|
| 430 |
-
promptsContainer.innerHTML = '';
|
| 431 |
-
|
| 432 |
-
try {
|
| 433 |
-
const formData = new FormData(uploadForm);
|
| 434 |
-
const response = await fetch('/predict', { method: 'POST', body: formData });
|
| 435 |
-
const data = await response.json();
|
| 436 |
-
|
| 437 |
-
if (data.error) {
|
| 438 |
-
alert(`Error: ${data.error}`);
|
| 439 |
-
return;
|
| 440 |
-
}
|
| 441 |
-
|
| 442 |
-
predictionContext = data;
|
| 443 |
-
resultsSection.classList.remove('hidden');
|
| 444 |
-
|
| 445 |
-
predictionOutput.innerHTML = `
|
| 446 |
-
<div class="bg-gradient-to-r from-indigo-50 to-purple-50 p-6 rounded-xl border border-indigo-200">
|
| 447 |
-
<div class="flex items-center justify-between mb-4">
|
| 448 |
-
<span class="text-sm font-semibold text-indigo-600 bg-indigo-100 px-3 py-1 rounded-full">${data.model_used}</span>
|
| 449 |
-
<span class="text-2xl font-bold text-gray-900">${(data.confidence * 100).toFixed(1)}%</span>
|
| 450 |
-
</div>
|
| 451 |
-
<h3 class="text-2xl font-bold text-gray-900 mb-3">${data.prediction}</h3>
|
| 452 |
-
<div class="bg-gray-200 rounded-full h-3 overflow-hidden">
|
| 453 |
-
<div class="progress-bar h-3 rounded-full" style="width: ${(data.confidence * 100).toFixed(0)}%"></div>
|
| 454 |
-
</div>
|
| 455 |
-
</div>
|
| 456 |
-
`;
|
| 457 |
-
|
| 458 |
-
originalImage.src = `${data.original_image}?t=${new Date().getTime()}`;
|
| 459 |
-
|
| 460 |
-
if (data.gradcam_image) {
|
| 461 |
-
gradcamImage.src = `${data.gradcam_image}?t=${new Date().getTime()}`;
|
| 462 |
-
gradcamImage.classList.remove('hidden');
|
| 463 |
-
gradcamPlaceholder.classList.add('hidden');
|
| 464 |
-
} else {
|
| 465 |
-
gradcamImage.classList.add('hidden');
|
| 466 |
-
gradcamPlaceholder.classList.remove('hidden');
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
// Activate chat
|
| 470 |
-
chatCard.classList.remove('opacity-50', 'pointer-events-none');
|
| 471 |
-
chatStatus.textContent = 'Active';
|
| 472 |
-
chatStatus.className = 'bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs';
|
| 473 |
-
|
| 474 |
-
chatMessages.innerHTML = '';
|
| 475 |
-
addMessage("Analysis complete! Ask me anything about the results.", 'bot');
|
| 476 |
-
chatInput.disabled = false;
|
| 477 |
-
chatSubmit.disabled = false;
|
| 478 |
-
|
| 479 |
-
if (data.predefined_prompts) {
|
| 480 |
-
data.predefined_prompts.forEach(prompt => {
|
| 481 |
-
const btn = document.createElement('button');
|
| 482 |
-
btn.textContent = prompt;
|
| 483 |
-
btn.className = 'bg-indigo-100 text-indigo-700 text-sm px-3 py-1 rounded-full hover:bg-indigo-200 transition-all';
|
| 484 |
-
btn.onclick = () => {
|
| 485 |
-
chatInput.value = prompt;
|
| 486 |
-
chatForm.requestSubmit();
|
| 487 |
-
};
|
| 488 |
-
promptsContainer.appendChild(btn);
|
| 489 |
-
});
|
| 490 |
-
}
|
| 491 |
-
|
| 492 |
-
} catch (error) {
|
| 493 |
-
alert('An unexpected error occurred.');
|
| 494 |
-
console.error("Prediction Error:", error);
|
| 495 |
-
} finally {
|
| 496 |
-
submitButton.disabled = false;
|
| 497 |
-
submitButton.innerHTML = originalText;
|
| 498 |
-
}
|
| 499 |
-
});
|
| 500 |
-
|
| 501 |
-
chatForm.addEventListener('submit', async (e) => {
|
| 502 |
-
e.preventDefault();
|
| 503 |
-
const message = chatInput.value.trim();
|
| 504 |
-
if (!message || !predictionContext) return;
|
| 505 |
-
|
| 506 |
-
addMessage(message, 'user');
|
| 507 |
-
chatHistory.push({ role: 'user', parts: [message] });
|
| 508 |
-
chatInput.value = '';
|
| 509 |
-
promptsContainer.innerHTML = '';
|
| 510 |
-
|
| 511 |
-
const thinkingMsg = addMessage('', 'bot');
|
| 512 |
-
thinkingMsg.innerHTML = `
|
| 513 |
-
<div class="flex items-center">
|
| 514 |
-
<div class="flex space-x-1 mr-2">
|
| 515 |
-
<div class="typing-dot"></div>
|
| 516 |
-
<div class="typing-dot"></div>
|
| 517 |
-
<div class="typing-dot"></div>
|
| 518 |
-
</div>
|
| 519 |
-
<span class="text-sm text-gray-500">Thinking...</span>
|
| 520 |
-
</div>
|
| 521 |
-
`;
|
| 522 |
-
|
| 523 |
-
try {
|
| 524 |
-
const response = await fetch('/chat', {
|
| 525 |
-
method: 'POST',
|
| 526 |
-
headers: { 'Content-Type': 'application/json' },
|
| 527 |
-
body: JSON.stringify({ message, context: predictionContext, history: chatHistory })
|
| 528 |
-
});
|
| 529 |
-
const data = await response.json();
|
| 530 |
-
|
| 531 |
-
thinkingMsg.remove();
|
| 532 |
-
if (data.error) {
|
| 533 |
-
addMessage(`Error: ${data.error}`, 'bot');
|
| 534 |
-
chatHistory.pop();
|
| 535 |
-
} else {
|
| 536 |
-
addMessage(data.response, 'bot');
|
| 537 |
-
chatHistory.push({ role: 'model', parts: [data.response] });
|
| 538 |
-
}
|
| 539 |
-
} catch (error) {
|
| 540 |
-
thinkingMsg.remove();
|
| 541 |
-
addMessage('Connection error. Please try again.', 'bot');
|
| 542 |
-
chatHistory.pop();
|
| 543 |
-
console.error("Chat Error:", error);
|
| 544 |
-
}
|
| 545 |
-
});
|
| 546 |
-
|
| 547 |
-
function addMessage(text, type) {
|
| 548 |
-
const msgDiv = document.createElement('div');
|
| 549 |
-
msgDiv.className = `p-3 rounded-xl max-w-[90%] ${type}-message mb-3`;
|
| 550 |
-
|
| 551 |
-
if (type === 'user') {
|
| 552 |
-
msgDiv.classList.add('ml-auto', 'text-right');
|
| 553 |
-
msgDiv.textContent = text;
|
| 554 |
-
} else {
|
| 555 |
-
msgDiv.classList.add('mr-auto');
|
| 556 |
-
if (text) {
|
| 557 |
-
msgDiv.innerHTML = marked.parse(text);
|
| 558 |
-
}
|
| 559 |
-
}
|
| 560 |
-
|
| 561 |
-
chatMessages.appendChild(msgDiv);
|
| 562 |
-
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 563 |
-
return msgDiv;
|
| 564 |
-
}
|
| 565 |
-
|
| 566 |
-
//
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
</html>
|
|
|
|
| 211 |
</div>
|
| 212 |
<input id="file-upload-hidden" name="file" type="file" accept="image/*" class="hidden"/>
|
| 213 |
</div>
|
| 214 |
+
|
| 215 |
+
<!-- Examples Gallery -->
|
| 216 |
+
<div class="mt-6">
|
| 217 |
+
<div class="flex items-center justify-between mb-3">
|
| 218 |
+
<h3 class="text-lg font-semibold text-gray-700">Or pick an example</h3>
|
| 219 |
+
<button type="button" id="refresh-examples" class="text-sm text-indigo-700 hover:underline">Refresh</button>
|
| 220 |
+
</div>
|
| 221 |
+
<div id="examples-container" class="grid grid-cols-2 sm:grid-cols-3 gap-3"></div>
|
| 222 |
+
<p id="examples-empty" class="text-sm text-gray-500 hidden">No examples found.</p>
|
| 223 |
+
</div>
|
| 224 |
</div>
|
| 225 |
|
| 226 |
<!-- Submit Button -->
|
|
|
|
| 335 |
const chatSubmit = document.getElementById('chat-submit');
|
| 336 |
const chatForm = document.getElementById('chat-form');
|
| 337 |
const promptsContainer = document.getElementById('predefined-prompts-container');
|
| 338 |
+
const modelSelect = document.getElementById('model-select');
|
| 339 |
+
const examplesContainer = document.getElementById('examples-container');
|
| 340 |
+
const examplesEmpty = document.getElementById('examples-empty');
|
| 341 |
+
const refreshExamplesBtn = document.getElementById('refresh-examples');
|
| 342 |
|
| 343 |
let predictionContext = null;
|
| 344 |
let chatHistory = [];
|
| 345 |
+
let cachedExamples = [];
|
| 346 |
|
| 347 |
// File upload handling
|
| 348 |
uploadZone.addEventListener('click', () => fileUploadHidden.click());
|
|
|
|
| 377 |
uploadZone.style.display = 'block';
|
| 378 |
fileUploadHidden.value = '';
|
| 379 |
});
|
| 380 |
+
|
| 381 |
+
function handleFileSelect(file) {
|
| 382 |
+
if (!file.type.startsWith('image/')) {
|
| 383 |
+
alert('Please select an image file.');
|
| 384 |
+
return;
|
| 385 |
+
}
|
| 386 |
+
if (file.size > 10 * 1024 * 1024) {
|
| 387 |
+
alert('File size must be less than 10MB.');
|
| 388 |
+
return;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
const reader = new FileReader();
|
| 392 |
+
reader.onload = (e) => {
|
| 393 |
+
imagePreview.src = e.target.result;
|
| 394 |
+
imagePreviewContainer.classList.remove('hidden');
|
| 395 |
+
uploadZone.style.display = 'none';
|
| 396 |
+
};
|
| 397 |
+
reader.readAsDataURL(file);
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
uploadForm.addEventListener('submit', async (e) => {
|
| 401 |
+
e.preventDefault();
|
| 402 |
+
if (!fileUploadHidden.files[0]) {
|
| 403 |
+
alert("Please select an image file first.");
|
| 404 |
+
return;
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
const submitButton = uploadForm.querySelector('button[type="submit"]');
|
| 408 |
+
const originalText = submitButton.innerHTML;
|
| 409 |
+
submitButton.disabled = true;
|
| 410 |
+
submitButton.innerHTML = `
|
| 411 |
+
<div class="flex items-center">
|
| 412 |
+
<div class="flex space-x-1 mr-2">
|
| 413 |
+
<div class="typing-dot"></div>
|
| 414 |
+
<div class="typing-dot"></div>
|
| 415 |
+
<div class="typing-dot"></div>
|
| 416 |
+
</div>
|
| 417 |
+
Analyzing...
|
| 418 |
+
</div>
|
| 419 |
+
`;
|
| 420 |
+
|
| 421 |
+
// Reset states
|
| 422 |
+
resultsSection.classList.add('hidden');
|
| 423 |
+
chatCard.classList.add('opacity-50', 'pointer-events-none');
|
| 424 |
+
chatStatus.textContent = 'Analyzing';
|
| 425 |
+
chatStatus.className = 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs';
|
| 426 |
+
chatInput.disabled = true;
|
| 427 |
+
chatSubmit.disabled = true;
|
| 428 |
+
|
| 429 |
+
chatMessages.innerHTML = `
|
| 430 |
+
<div class="bot-message p-3 rounded-xl max-w-[90%]">
|
| 431 |
+
<div class="flex items-center mb-2">
|
| 432 |
+
<div class="flex space-x-1 mr-2">
|
| 433 |
+
<div class="typing-dot"></div>
|
| 434 |
+
<div class="typing-dot"></div>
|
| 435 |
+
<div class="typing-dot"></div>
|
| 436 |
+
</div>
|
| 437 |
+
<span class="text-xs text-gray-500">ANALYZING</span>
|
| 438 |
+
</div>
|
| 439 |
+
Processing your medical image...
|
| 440 |
+
</div>
|
| 441 |
+
`;
|
| 442 |
+
|
| 443 |
+
predictionContext = null;
|
| 444 |
+
chatHistory = [];
|
| 445 |
+
promptsContainer.innerHTML = '';
|
| 446 |
+
|
| 447 |
+
try {
|
| 448 |
+
const formData = new FormData(uploadForm);
|
| 449 |
+
const response = await fetch('/predict', { method: 'POST', body: formData });
|
| 450 |
+
const data = await response.json();
|
| 451 |
+
|
| 452 |
+
if (data.error) {
|
| 453 |
+
alert(`Error: ${data.error}`);
|
| 454 |
+
return;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
predictionContext = data;
|
| 458 |
+
resultsSection.classList.remove('hidden');
|
| 459 |
+
|
| 460 |
+
predictionOutput.innerHTML = `
|
| 461 |
+
<div class="bg-gradient-to-r from-indigo-50 to-purple-50 p-6 rounded-xl border border-indigo-200">
|
| 462 |
+
<div class="flex items-center justify-between mb-4">
|
| 463 |
+
<span class="text-sm font-semibold text-indigo-600 bg-indigo-100 px-3 py-1 rounded-full">${data.model_used}</span>
|
| 464 |
+
<span class="text-2xl font-bold text-gray-900">${(data.confidence * 100).toFixed(1)}%</span>
|
| 465 |
+
</div>
|
| 466 |
+
<h3 class="text-2xl font-bold text-gray-900 mb-3">${data.prediction}</h3>
|
| 467 |
+
<div class="bg-gray-200 rounded-full h-3 overflow-hidden">
|
| 468 |
+
<div class="progress-bar h-3 rounded-full" style="width: ${(data.confidence * 100).toFixed(0)}%"></div>
|
| 469 |
+
</div>
|
| 470 |
+
</div>
|
| 471 |
+
`;
|
| 472 |
+
|
| 473 |
+
originalImage.src = `${data.original_image}?t=${new Date().getTime()}`;
|
| 474 |
+
|
| 475 |
+
if (data.gradcam_image) {
|
| 476 |
+
gradcamImage.src = `${data.gradcam_image}?t=${new Date().getTime()}`;
|
| 477 |
+
gradcamImage.classList.remove('hidden');
|
| 478 |
+
gradcamPlaceholder.classList.add('hidden');
|
| 479 |
+
} else {
|
| 480 |
+
gradcamImage.classList.add('hidden');
|
| 481 |
+
gradcamPlaceholder.classList.remove('hidden');
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
// Activate chat
|
| 485 |
+
chatCard.classList.remove('opacity-50', 'pointer-events-none');
|
| 486 |
+
chatStatus.textContent = 'Active';
|
| 487 |
+
chatStatus.className = 'bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs';
|
| 488 |
+
|
| 489 |
+
chatMessages.innerHTML = '';
|
| 490 |
+
addMessage("Analysis complete! Ask me anything about the results.", 'bot');
|
| 491 |
+
chatInput.disabled = false;
|
| 492 |
+
chatSubmit.disabled = false;
|
| 493 |
+
|
| 494 |
+
if (data.predefined_prompts) {
|
| 495 |
+
data.predefined_prompts.forEach(prompt => {
|
| 496 |
+
const btn = document.createElement('button');
|
| 497 |
+
btn.textContent = prompt;
|
| 498 |
+
btn.className = 'bg-indigo-100 text-indigo-700 text-sm px-3 py-1 rounded-full hover:bg-indigo-200 transition-all';
|
| 499 |
+
btn.onclick = () => {
|
| 500 |
+
chatInput.value = prompt;
|
| 501 |
+
chatForm.requestSubmit();
|
| 502 |
+
};
|
| 503 |
+
promptsContainer.appendChild(btn);
|
| 504 |
+
});
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
} catch (error) {
|
| 508 |
+
alert('An unexpected error occurred.');
|
| 509 |
+
console.error("Prediction Error:", error);
|
| 510 |
+
} finally {
|
| 511 |
+
submitButton.disabled = false;
|
| 512 |
+
submitButton.innerHTML = originalText;
|
| 513 |
+
}
|
| 514 |
+
});
|
| 515 |
+
|
| 516 |
+
chatForm.addEventListener('submit', async (e) => {
|
| 517 |
+
e.preventDefault();
|
| 518 |
+
const message = chatInput.value.trim();
|
| 519 |
+
if (!message || !predictionContext) return;
|
| 520 |
+
|
| 521 |
+
addMessage(message, 'user');
|
| 522 |
+
chatHistory.push({ role: 'user', parts: [message] });
|
| 523 |
+
chatInput.value = '';
|
| 524 |
+
promptsContainer.innerHTML = '';
|
| 525 |
+
|
| 526 |
+
const thinkingMsg = addMessage('', 'bot');
|
| 527 |
+
thinkingMsg.innerHTML = `
|
| 528 |
+
<div class="flex items-center">
|
| 529 |
+
<div class="flex space-x-1 mr-2">
|
| 530 |
+
<div class="typing-dot"></div>
|
| 531 |
+
<div class="typing-dot"></div>
|
| 532 |
+
<div class="typing-dot"></div>
|
| 533 |
+
</div>
|
| 534 |
+
<span class="text-sm text-gray-500">Thinking...</span>
|
| 535 |
+
</div>
|
| 536 |
+
`;
|
| 537 |
+
|
| 538 |
+
try {
|
| 539 |
+
const response = await fetch('/chat', {
|
| 540 |
+
method: 'POST',
|
| 541 |
+
headers: { 'Content-Type': 'application/json' },
|
| 542 |
+
body: JSON.stringify({ message, context: predictionContext, history: chatHistory })
|
| 543 |
+
});
|
| 544 |
+
const data = await response.json();
|
| 545 |
+
|
| 546 |
+
thinkingMsg.remove();
|
| 547 |
+
if (data.error) {
|
| 548 |
+
addMessage(`Error: ${data.error}`, 'bot');
|
| 549 |
+
chatHistory.pop();
|
| 550 |
+
} else {
|
| 551 |
+
addMessage(data.response, 'bot');
|
| 552 |
+
chatHistory.push({ role: 'model', parts: [data.response] });
|
| 553 |
+
}
|
| 554 |
+
} catch (error) {
|
| 555 |
+
thinkingMsg.remove();
|
| 556 |
+
addMessage('Connection error. Please try again.', 'bot');
|
| 557 |
+
chatHistory.pop();
|
| 558 |
+
console.error("Chat Error:", error);
|
| 559 |
+
}
|
| 560 |
+
});
|
| 561 |
+
|
| 562 |
+
function addMessage(text, type) {
|
| 563 |
+
const msgDiv = document.createElement('div');
|
| 564 |
+
msgDiv.className = `p-3 rounded-xl max-w-[90%] ${type}-message mb-3`;
|
| 565 |
+
|
| 566 |
+
if (type === 'user') {
|
| 567 |
+
msgDiv.classList.add('ml-auto', 'text-right');
|
| 568 |
+
msgDiv.textContent = text;
|
| 569 |
+
} else {
|
| 570 |
+
msgDiv.classList.add('mr-auto');
|
| 571 |
+
if (text) {
|
| 572 |
+
msgDiv.innerHTML = marked.parse(text);
|
| 573 |
+
}
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
chatMessages.appendChild(msgDiv);
|
| 577 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 578 |
+
return msgDiv;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
// Load example images when model changes
|
| 582 |
+
async function loadExamples() {
|
| 583 |
+
try {
|
| 584 |
+
const res = await fetch('/example_images');
|
| 585 |
+
const data = await res.json();
|
| 586 |
+
cachedExamples = Array.isArray(data.images) ? data.images : [];
|
| 587 |
+
renderExamples();
|
| 588 |
+
} catch (e) {
|
| 589 |
+
console.error('Failed to load examples', e);
|
| 590 |
+
cachedExamples = [];
|
| 591 |
+
renderExamples();
|
| 592 |
+
}
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
function renderExamples() {
|
| 596 |
+
examplesContainer.innerHTML = '';
|
| 597 |
+
if (!cachedExamples.length) {
|
| 598 |
+
examplesEmpty.classList.remove('hidden');
|
| 599 |
+
return;
|
| 600 |
+
}
|
| 601 |
+
examplesEmpty.classList.add('hidden');
|
| 602 |
+
// Show up to 9 examples
|
| 603 |
+
cachedExamples.slice(0, 9).forEach((url) => {
|
| 604 |
+
const card = document.createElement('button');
|
| 605 |
+
card.type = 'button';
|
| 606 |
+
card.className = 'relative group rounded-xl overflow-hidden shadow hover:shadow-lg transition focus:outline-none focus:ring-2 focus:ring-indigo-500';
|
| 607 |
+
card.title = 'Use this example';
|
| 608 |
+
card.onclick = () => selectExample(url);
|
| 609 |
+
card.innerHTML = `
|
| 610 |
+
<img src="${url}" alt="example" class="w-full h-24 object-cover">
|
| 611 |
+
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20"></div>
|
| 612 |
+
`;
|
| 613 |
+
examplesContainer.appendChild(card);
|
| 614 |
+
});
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
async function selectExample(url) {
|
| 618 |
+
try {
|
| 619 |
+
const resp = await fetch(url);
|
| 620 |
+
const blob = await resp.blob();
|
| 621 |
+
const inferredName = url.split('/').pop() || 'example.png';
|
| 622 |
+
const file = new File([blob], inferredName, { type: blob.type || 'image/png' });
|
| 623 |
+
|
| 624 |
+
// Put into the hidden file input
|
| 625 |
+
const dt = new DataTransfer();
|
| 626 |
+
dt.items.add(file);
|
| 627 |
+
fileUploadHidden.files = dt.files;
|
| 628 |
+
|
| 629 |
+
// Reuse existing preview logic
|
| 630 |
+
handleFileSelect(file);
|
| 631 |
+
} catch (e) {
|
| 632 |
+
console.error('Failed to select example', e);
|
| 633 |
+
alert('Unable to load example image.');
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
modelSelect.addEventListener('change', loadExamples);
|
| 638 |
+
refreshExamplesBtn.addEventListener('click', loadExamples);
|
| 639 |
+
|
| 640 |
+
// Initial load for default selection
|
| 641 |
+
loadExamples();
|
| 642 |
+
|
| 643 |
+
// Smooth scrolling
|
| 644 |
+
document.documentElement.style.scrollBehavior = 'smooth';
|
| 645 |
+
</script>
|
| 646 |
+
</body>
|
| 647 |
</html>
|