f1 / index.html
water-water's picture
Update index.html
abc9f86 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Qwen2-VL Chat</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
background: #f8fafc;
height: 100vh;
overflow: hidden;
color: #1e293b;
}
.app-container {
display: flex;
height: 100vh;
width: 100vw;
}
/* Left Sidebar - Chat List */
.chat-sidebar {
width: 280px;
background: #ffffff;
border-right: 1px solid #e2e8f0;
display: flex;
flex-direction: column;
position: relative;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid #e2e8f0;
background: #f8fafc;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.user-avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 16px;
}
.user-details h3 {
font-size: 16px;
font-weight: 600;
color: #1e293b;
}
.user-details p {
font-size: 12px;
color: #64748b;
margin-top: 2px;
}
.new-chat-btn {
width: 100%;
padding: 12px 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.new-chat-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.chat-list {
flex: 1;
overflow-y: auto;
padding: 12px;
}
.chat-item {
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
group: 1;
}
.chat-item:hover {
background: #f1f5f9;
}
.chat-item.active {
background: #e0e7ff;
border: 1px solid #c7d2fe;
}
.chat-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.chat-title {
font-size: 14px;
font-weight: 500;
color: #1e293b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 180px;
}
.chat-menu {
opacity: 0;
transition: opacity 0.2s ease;
position: relative;
}
.chat-item:hover .chat-menu {
opacity: 1;
}
.chat-menu-btn {
width: 24px;
height: 24px;
border: none;
background: none;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
}
.chat-menu-btn:hover {
background: #e2e8f0;
}
.chat-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #64748b;
}
/* Main Chat Area */
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
background: #ffffff;
}
.chat-header {
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
background: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-title-section h1 {
font-size: 20px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
}
.chat-subtitle {
font-size: 14px;
color: #64748b;
}
.header-actions {
display: flex;
gap: 12px;
}
.header-btn {
width: 40px;
height: 40px;
border: none;
background: #f8fafc;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #64748b;
}
.header-btn:hover {
background: #e2e8f0;
color: #1e293b;
}
.chat-messages {
flex: 1;
padding: 24px;
overflow-y: auto;
scroll-behavior: smooth;
}
.welcome-message {
text-align: center;
padding: 80px 20px;
color: #64748b;
}
.welcome-icon {
font-size: 64px;
margin-bottom: 24px;
}
.welcome-message h2 {
font-size: 28px;
font-weight: 600;
margin-bottom: 12px;
color: #1e293b;
}
.welcome-message p {
font-size: 16px;
line-height: 1.6;
}
.message {
margin-bottom: 24px;
display: flex;
gap: 16px;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.message.user .message-avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message.assistant .message-avatar {
background: #f1f5f9;
color: #64748b;
}
.message-content {
flex: 1;
background: #f8fafc;
padding: 16px 20px;
border-radius: 16px;
font-size: 15px;
line-height: 1.6;
word-wrap: break-word;
white-space: pre-wrap;
}
.message.user .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.message.assistant .message-content {
background: #f8fafc;
color: #1e293b;
border: 1px solid #e2e8f0;
}
.message-image {
max-width: 300px;
border-radius: 12px;
margin-bottom: 12px;
display: block;
}
.message-time {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
margin-top: 8px;
}
.message.assistant .message-time {
color: #94a3b8;
}
/* Input Area */
.chat-input-area {
padding: 24px;
background: #ffffff;
border-top: 1px solid #e2e8f0;
}
.input-container {
max-width: 800px;
margin: 0 auto;
display: flex;
align-items: flex-end;
gap: 12px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
padding: 16px;
transition: all 0.2s ease;
}
.input-container:focus-within {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.image-upload-section {
display: flex;
align-items: center;
gap: 12px;
}
.image-upload-btn {
width: 44px;
height: 44px;
border: none;
background: #ffffff;
border: 2px solid #e2e8f0;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: #64748b;
}
.image-upload-btn:hover {
border-color: #667eea;
color: #667eea;
}
.image-upload-btn.has-image {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-color: #667eea;
color: white;
}
.image-preview {
display: none;
}
.image-preview.has-image {
display: block;
width: 44px;
height: 44px;
border-radius: 12px;
overflow: hidden;
border: 2px solid #e2e8f0;
}
.image-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.text-input-section {
flex: 1;
display: flex;
align-items: flex-end;
gap: 12px;
}
#messageInput {
flex: 1;
border: none;
outline: none;
resize: none;
font-size: 15px;
line-height: 1.6;
padding: 8px 0;
background: transparent;
font-family: inherit;
max-height: 120px;
min-height: 24px;
color: #1e293b;
}
#messageInput::placeholder {
color: #94a3b8;
}
.send-btn {
width: 44px;
height: 44px;
border: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.send-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
}
.send-btn:disabled {
background: #e2e8f0;
color: #94a3b8;
cursor: not-allowed;
}
/* Right Sidebar - Settings */
.settings-sidebar {
width: 320px;
background: #ffffff;
border-left: 1px solid #e2e8f0;
display: flex;
flex-direction: column;
}
.settings-header {
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
background: #f8fafc;
}
.settings-header h2 {
font-size: 18px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
}
.settings-subtitle {
font-size: 14px;
color: #64748b;
}
.settings-content {
padding: 24px;
overflow-y: auto;
}
.setting-group {
margin-bottom: 24px;
}
.setting-group:last-child {
margin-bottom: 0;
}
.setting-label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 12px;
color: #374151;
}
.setting-description {
font-size: 12px;
color: #64748b;
margin-bottom: 12px;
line-height: 1.4;
}
.slider-container {
display: flex;
align-items: center;
gap: 16px;
}
.slider-container input[type="range"] {
flex: 1;
height: 6px;
border-radius: 3px;
background: #e2e8f0;
outline: none;
-webkit-appearance: none;
}
.slider-container input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.slider-container input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
cursor: pointer;
border: none;
}
.slider-value {
font-size: 14px;
font-weight: 600;
color: #1e293b;
min-width: 50px;
text-align: center;
background: #f1f5f9;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
/* Loading Indicator */
.loading-indicator {
display: none;
align-items: center;
gap: 12px;
padding: 16px 24px;
background: #f8fafc;
color: #64748b;
font-size: 14px;
border-top: 1px solid #e2e8f0;
}
.loading-indicator.active {
display: flex;
}
.loading-dots {
display: flex;
gap: 4px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #94a3b8;
animation: bounce 1.4s ease-in-out infinite both;
}
.dot:nth-child(1) { animation-delay: -0.32s; }
.dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
/* Custom scrollbar */
.chat-messages::-webkit-scrollbar,
.chat-list::-webkit-scrollbar,
.settings-content::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track,
.chat-list::-webkit-scrollbar-track,
.settings-content::-webkit-scrollbar-track {
background: transparent;
}
.chat-messages::-webkit-scrollbar-thumb,
.chat-list::-webkit-scrollbar-thumb,
.settings-content::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb:hover,
.chat-list::-webkit-scrollbar-thumb:hover,
.settings-content::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.3);
}
/* Responsive Design */
@media (max-width: 1024px) {
.settings-sidebar {
display: none;
}
}
@media (max-width: 768px) {
.chat-sidebar {
width: 260px;
}
.app-container {
overflow-x: auto;
}
}
</style>
</head>
<body>
<div class="app-container">
<!-- Left Sidebar - Chat List -->
<div class="chat-sidebar">
<div class="sidebar-header">
<div class="user-info">
<div class="user-avatar">AT</div>
<div class="user-details">
<h3>ayush-thakur02</h3>
<p>2025-06-03 10:57 UTC</p>
</div>
</div>
<button class="new-chat-btn" onclick="createNewChat()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
New Chat
</button>
</div>
<div class="chat-list" id="chatList">
<!-- Chat items will be loaded here -->
</div>
</div>
<!-- Main Chat Area -->
<div class="chat-main">
<div class="chat-header">
<div class="chat-title-section">
<h1 id="currentChatTitle">Qwen2-VL Assistant</h1>
<p class="chat-subtitle">Vision-Language AI Assistant</p>
</div>
<div class="header-actions">
<button class="header-btn" onclick="toggleSidebar('chat')" title="Toggle Chat List">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<button class="header-btn" onclick="toggleSidebar('settings')" title="Toggle Settings">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1 1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="welcome-message">
<div class="welcome-icon">🤖</div>
<h2>Welcome to Qwen2-VL</h2>
<p>Upload an image and ask me anything about it!<br>I can analyze, describe, and answer questions about visual content.</p>
</div>
</div>
<div class="loading-indicator" id="loadingIndicator">
<div class="loading-dots">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
<span>Generating response...</span>
</div>
<div class="chat-input-area">
<form id="chatForm" class="chat-form">
<div class="input-container">
<div class="image-upload-section">
<input type="file" id="imageInput" name="image" accept="image/*" hidden>
<button type="button" class="image-upload-btn" onclick="document.getElementById('imageInput').click()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<path d="M21 15l-5-5L5 21"></path>
</svg>
</button>
<div class="image-preview" id="imagePreview"></div>
</div>
<div class="text-input-section">
<textarea
id="messageInput"
name="prompt"
placeholder="Ask me anything about your image..."
rows="1"
required
></textarea>
<button type="submit" class="send-btn" id="sendBtn" disabled>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9"></polygon>
</svg>
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Right Sidebar - Settings -->
<div class="settings-sidebar">
<div class="settings-header">
<h2>Generation Settings</h2>
<p class="settings-subtitle">Fine-tune AI responses</p>
</div>
<div class="settings-content">
<div class="setting-group">
<label class="setting-label" for="temperature">Temperature</label>
<p class="setting-description">Controls randomness. Lower values make responses more focused and deterministic.</p>
<div class="slider-container">
<input type="range" id="temperature" min="0.1" max="1.5" step="0.1" value="0.7">
<span class="slider-value">0.7</span>
</div>
</div>
<div class="setting-group">
<label class="setting-label" for="top_p">Top-p (Nucleus Sampling)</label>
<p class="setting-description">Controls diversity. Lower values focus on more probable tokens.</p>
<div class="slider-container">
<input type="range" id="top_p" min="0.1" max="1.0" step="0.05" value="0.9">
<span class="slider-value">0.9</span>
</div>
</div>
<div class="setting-group">
<label class="setting-label" for="max_tokens">Max Tokens</label>
<p class="setting-description">Maximum length of the response. Higher values allow longer responses.</p>
<div class="slider-container">
<input type="range" id="max_tokens" min="32" max="2048" step="32" value="1024">
<span class="slider-value">1024</span>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let currentChatId = null;
let chats = [];
// DOM Elements
const chatForm = document.getElementById('chatForm');
const messageInput = document.getElementById('messageInput');
const imageInput = document.getElementById('imageInput');
const sendBtn = document.getElementById('sendBtn');
const chatMessages = document.getElementById('chatMessages');
const loadingIndicator = document.getElementById('loadingIndicator');
const imageUploadBtn = document.querySelector('.image-upload-btn');
const imagePreview = document.getElementById('imagePreview');
const chatList = document.getElementById('chatList');
const currentChatTitle = document.getElementById('currentChatTitle');
// Settings elements
const temperatureSlider = document.getElementById('temperature');
const topPSlider = document.getElementById('top_p');
const maxTokensSlider = document.getElementById('max_tokens');
// Initialize
document.addEventListener('DOMContentLoaded', function() {
setupEventListeners();
setupSliders();
loadChats();
createNewChat();
});
function setupEventListeners() {
chatForm.addEventListener('submit', handleSubmit);
messageInput.addEventListener('input', validateInput);
imageInput.addEventListener('change', handleImageSelect);
messageInput.addEventListener('input', autoResizeTextarea);
messageInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!sendBtn.disabled) {
chatForm.dispatchEvent(new Event('submit'));
}
}
});
}
function setupSliders() {
const sliders = [temperatureSlider, topPSlider, maxTokensSlider];
sliders.forEach(slider => {
const valueSpan = slider.parentElement.querySelector('.slider-value');
slider.addEventListener('input', function() {
valueSpan.textContent = this.value;
});
});
}
function validateInput() {
const hasText = messageInput.value.trim().length > 0;
const hasImage = imageInput.files.length > 0;
sendBtn.disabled = !(hasText && hasImage);
}
function handleImageSelect(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
imagePreview.innerHTML = `<img src="${e.target.result}" alt="Preview">`;
imagePreview.classList.add('has-image');
imageUploadBtn.classList.add('has-image');
};
reader.readAsDataURL(file);
} else {
imagePreview.innerHTML = '';
imagePreview.classList.remove('has-image');
imageUploadBtn.classList.remove('has-image');
}
validateInput();
}
function autoResizeTextarea() {
messageInput.style.height = 'auto';
messageInput.style.height = Math.min(messageInput.scrollHeight, 120) + 'px';
}
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData();
const messageText = messageInput.value.trim();
const imageFile = imageInput.files[0];
if (!messageText || !imageFile) return;
// Ensure we have a current chat
if (!currentChatId) {
await createNewChat();
}
formData.append('prompt', messageText);
formData.append('image', imageFile);
formData.append('temperature', temperatureSlider.value);
formData.append('top_p', topPSlider.value);
formData.append('max_tokens', maxTokensSlider.value);
formData.append('chat_id', currentChatId);
// Add user message to chat
addMessage('user', messageText, URL.createObjectURL(imageFile));
// Clear form
messageInput.value = '';
imageInput.value = '';
imagePreview.innerHTML = '';
imagePreview.classList.remove('has-image');
imageUploadBtn.classList.remove('has-image');
sendBtn.disabled = true;
autoResizeTextarea();
showLoading();
try {
const response = await fetch('/infer', {
method: 'POST',
body: formData
});
const data = await response.json();
hideLoading();
if (data.response) {
addMessage('assistant', data.response);
loadChats(); // Refresh chat list
} else {
addMessage('assistant', 'Sorry, I encountered an error processing your request.');
}
} catch (error) {
console.error('Error:', error);
hideLoading();
addMessage('assistant', 'Sorry, there was an error connecting to the server.');
}
}
function addMessage(sender, text, imageSrc = null) {
const welcomeMessage = chatMessages.querySelector('.welcome-message');
if (welcomeMessage) {
welcomeMessage.remove();
}
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = sender === 'user' ? '👤' : '🤖';
const content = document.createElement('div');
content.className = 'message-content';
if (imageSrc && sender === 'user') {
const img = document.createElement('img');
img.src = imageSrc;
img.className = 'message-image';
content.appendChild(img);
}
const textDiv = document.createElement('div');
textDiv.textContent = text;
content.appendChild(textDiv);
const timeDiv = document.createElement('div');
timeDiv.className = 'message-time';
timeDiv.textContent = new Date().toLocaleTimeString();
content.appendChild(timeDiv);
messageDiv.appendChild(avatar);
messageDiv.appendChild(content);
chatMessages.appendChild(messageDiv);
scrollToBottom();
}
function showLoading() {
loadingIndicator.classList.add('active');
scrollToBottom();
}
function hideLoading() {
loadingIndicator.classList.remove('active');
}
function scrollToBottom() {
setTimeout(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
}, 100);
}
// Chat management functions
async function createNewChat() {
try {
const response = await fetch('/api/chats', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const newChat = await response.json();
currentChatId = newChat.id;
// Clear current chat
chatMessages.innerHTML = `
<div class="welcome-message">
<div class="welcome-icon">🤖</div>
<h2>Welcome to Qwen2-VL</h2>
<p>Upload an image and ask me anything about it!<br>I can analyze, describe, and answer questions about visual content.</p>
</div>
`;
currentChatTitle.textContent = newChat.title;
loadChats();
} catch (error) {
console.error('Error creating new chat:', error);
}
}
async function loadChats() {
try {
const response = await fetch('/api/chats');
chats = await response.json();
renderChatList();
} catch (error) {
console.error('Error loading chats:', error);
}
}
function renderChatList() {
chatList.innerHTML = '';
chats.forEach(chat => {
const chatItem = document.createElement('div');
chatItem.className = `chat-item ${chat.id === currentChatId ? 'active' : ''}`;
chatItem.onclick = () => loadChat(chat.id);
chatItem.innerHTML = `
<div class="chat-item-header">
<div class="chat-title">${chat.title}</div>
<div class="chat-menu">
<button class="chat-menu-btn" onclick="event.stopPropagation(); deleteChat('${chat.id}')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<polyline points="3,6 5,6 21,6"></polyline>
<path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
</svg>
</button>
</div>
</div>
<div class="chat-meta">
<span>${chat.message_count} messages</span>
<span>${new Date(chat.updated_at).toLocaleDateString()}</span>
</div>
`;
chatList.appendChild(chatItem);
});
}
async function loadChat(chatId) {
try {
const response = await fetch(`/api/chats/${chatId}`);
const chat = await response.json();
currentChatId = chatId;
currentChatTitle.textContent = chat.title;
// Clear and load messages
chatMessages.innerHTML = '';
if (chat.messages.length === 0) {
chatMessages.innerHTML = `
<div class="welcome-message">
<div class="welcome-icon">🤖</div>
<h2>Welcome to Qwen2-VL</h2>
<p>Upload an image and ask me anything about it!<br>I can analyze, describe, and answer questions about visual content.</p>
</div>
`;
} else {
chat.messages.forEach(message => {
if (message.role === 'user') {
const imageUrl = `data:image/jpeg;base64,${message.image}`;
addMessage('user', message.content, imageUrl);
} else {
addMessage('assistant', message.content);
}
});
}
renderChatList();
scrollToBottom();
} catch (error) {
console.error('Error loading chat:', error);
}
}
async function deleteChat(chatId) {
if (!confirm('Are you sure you want to delete this chat?')) return;
try {
await fetch(`/api/chats/${chatId}`, {
method: 'DELETE'
});
if (chatId === currentChatId) {
await createNewChat();
}
loadChats();
} catch (error) {
console.error('Error deleting chat:', error);
}
}
function toggleSidebar(type) {
// This would handle mobile responsive behavior
// For now, it's just a placeholder
console.log(`Toggle ${type} sidebar`);
}
</script>
</body>
</html>