// 页面加载完成后执行 document.addEventListener('DOMContentLoaded', function() { // 加载机器人角色列表 loadRobotCharacters(); // 初始化图片上传表单 initUploadImageForm(); // 初始化聊天表单 initChatForm(); }); // 加载机器人角色列表 async function loadRobotCharacters() { const robotSelect = document.getElementById('robotSelect'); try { const response = await fetch('/imgsearcherApi/api/robots'); const data = await response.json(); if (data.robots && data.robots.length > 0) { // 添加机器人选项 data.robots.forEach(robot => { const option = document.createElement('option'); option.value = robot.robot_id; option.textContent = robot.name; robotSelect.appendChild(option); }); } } catch (error) { console.error('加载机器人角色失败:', error); } } // 初始化图片上传表单 function initUploadImageForm() { const form = document.getElementById('uploadImageForm'); const fileInput = document.getElementById('chatImageFile'); const previewContainer = document.getElementById('uploadPreview'); const previewImage = document.getElementById('previewImage'); const chatContainer = document.getElementById('chatContainer'); const imageTypeElement = document.getElementById('imageType'); const imageDescriptionElement = document.getElementById('imageDescription'); // 图片选择预览 fileInput.addEventListener('change', function() { if (this.files && this.files[0]) { const reader = new FileReader(); reader.onload = function(e) { previewImage.src = e.target.result; previewContainer.classList.remove('d-none'); }; reader.readAsDataURL(this.files[0]); } }); // 表单提交 form.addEventListener('submit', async function(e) { e.preventDefault(); const formData = new FormData(this); // 显示上传中状态 const submitButton = form.querySelector('button[type="submit"]'); const originalButtonText = submitButton.innerHTML; submitButton.disabled = true; submitButton.innerHTML = ' 上传中...'; try { const response = await fetch('/imgsearcherApi/api/upload-chat-image', { method: 'POST', body: formData }); const data = await response.json(); if (data.error) { alert(`上传失败: ${data.error}`); return; } // 显示图片信息 if (data.image_type) { imageTypeElement.textContent = data.image_type; imageTypeElement.classList.remove('d-none'); } else { imageTypeElement.classList.add('d-none'); } if (data.description) { imageDescriptionElement.textContent = data.description; imageDescriptionElement.classList.remove('d-none'); } else { imageDescriptionElement.classList.add('d-none'); } // 显示聊天界面 chatContainer.classList.remove('d-none'); // 显示欢迎消息 const chatMessages = document.getElementById('chatMessages'); chatMessages.innerHTML = ''; const welcomeMessage = document.createElement('div'); welcomeMessage.className = 'message message-assistant'; let welcomeText = ''; // 如果选择了机器人角色,使用机器人的自我介绍 if (data.robot) { welcomeText = `你好!我是${data.robot.name},`; // 存储机器人信息到页面数据中 document.getElementById('chatContainer').dataset.robotName = data.robot.name; // 显示机器人信息在界面上 const robotInfoElement = document.createElement('div'); robotInfoElement.className = 'alert alert-info mt-2 mb-3'; robotInfoElement.innerHTML = `当前角色:${data.robot.name}
${data.robot.background}`; document.getElementById('chatContainer').prepend(robotInfoElement); } else { welcomeText = '你好!我是AI助手,'; } welcomeText += '可以帮你分析这张图片并回答问题。'; if (data.image_type && data.description) { welcomeText += `我看到这是一张${data.image_type}的图片,${data.description}。你有什么想问的吗?`; } else { welcomeText += '你有什么想问的吗?'; } welcomeMessage.innerHTML = `
${welcomeText}
${getCurrentTime()}
`; chatMessages.appendChild(welcomeMessage); // 滚动到底部 chatMessages.scrollTop = chatMessages.scrollHeight; } catch (error) { alert(`上传失败: ${error.message}`); } finally { // 恢复按钮状态 submitButton.disabled = false; submitButton.innerHTML = originalButtonText; } }); } // 初始化聊天表单 function initChatForm() { const form = document.getElementById('chatForm'); const messageInput = document.getElementById('messageInput'); const chatMessages = document.getElementById('chatMessages'); form.addEventListener('submit', async function(e) { e.preventDefault(); const message = messageInput.value.trim(); if (!message) return; // 添加用户消息到聊天界面 addMessage('user', message); // 清空输入框 messageInput.value = ''; // 显示正在输入指示器 const typingIndicator = document.createElement('div'); typingIndicator.className = 'typing-indicator'; typingIndicator.innerHTML = ''; chatMessages.appendChild(typingIndicator); // 滚动到底部 chatMessages.scrollTop = chatMessages.scrollHeight; try { // 使用流式接口 const response = await fetch('/imgsearcherApi/api/chat-stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, voice: document.getElementById('voiceSelect') ? document.getElementById('voiceSelect').value : '' }) }); if (!response.ok) { chatMessages.removeChild(typingIndicator); alert('对话失败'); return; } // 创建助手消息占位 const assistantElement = document.createElement('div'); assistantElement.className = 'message message-assistant'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; assistantElement.appendChild(contentDiv); const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = getCurrentTime(); assistantElement.appendChild(timeDiv); chatMessages.appendChild(assistantElement); chatMessages.scrollTop = chatMessages.scrollHeight; const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buf = ''; let streamEnded = false; while (!streamEnded) { const { value, done } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); let pos; while ((pos = buf.indexOf('\n\n')) !== -1) { const raw = buf.slice(0, pos).trim(); buf = buf.slice(pos + 2); if (!raw) continue; if (raw.startsWith('event: end')) { chatMessages.removeChild(typingIndicator); streamEnded = true; break; } if (!raw.startsWith('data:')) continue; const payload = raw.slice(5).trim(); if (!payload) continue; try { const obj = JSON.parse(payload); if (obj.type === 'text') { contentDiv.innerHTML += formatMessage(obj.content); chatMessages.scrollTop = chatMessages.scrollHeight; } else if (obj.type === 'audio') { const audioBytes = Uint8Array.from(atob(obj.content), c => c.charCodeAt(0)); const audioBlob = new Blob([audioBytes], { type: 'audio/wav' }); new Audio(URL.createObjectURL(audioBlob)).play(); } } catch (e) { console.error('解析事件失败', e, payload); } } } } catch (error) { // 移除正在输入指示器 if (typingIndicator.parentNode === chatMessages) { chatMessages.removeChild(typingIndicator); } alert(`对话失败: ${error.message}`); } }); } // 添加消息到聊天界面 async function addMessage(role, content) { const chatMessages = document.getElementById('chatMessages'); const messageElement = document.createElement('div'); messageElement.className = `message message-${role}`; messageElement.innerHTML = `
${formatMessage(content)}
${getCurrentTime()}
`; chatMessages.appendChild(messageElement); // 滚动到底部 chatMessages.scrollTop = chatMessages.scrollHeight; } // 格式化消息内容,支持简单的markdown function formatMessage(content) { // 转义HTML特殊字符 let formatted = content .replace(/&/g, '&') .replace(//g, '>'); // 支持加粗 formatted = formatted.replace(/\*\*(.*?)\*\*/g, '$1'); // 支持斜体 formatted = formatted.replace(/\*(.*?)\*/g, '$1'); // 支持代码 formatted = formatted.replace(/`(.*?)`/g, '$1'); // 支持换行 formatted = formatted.replace(/\n/g, '
'); return formatted; } // 获取当前时间 function getCurrentTime() { const now = new Date(); const hours = now.getHours().toString().padStart(2, '0'); const minutes = now.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes}`; }