// app/static/js/chat.js - 聊天功能相关逻辑 import { AUTH_TOKEN_STORAGE_KEY, CURRENT_SESSION_KEY } from './constants.js'; import { fileToBase64, extractContent } from './utils.js'; /** * 将消息添加到聊天窗口。 * @param {string} sender - 消息发送者 ('user', 'bot', 'system')。 * @param {string} text - 消息文本内容。 * @param {string|null} userProvidedImageUrl - 用户提供的图片URL(可选)。 * @param {string[]} botImageUrls - Bot 返回的图片URL数组(可选)。 */ export function addMessageToChat(sender, text, userProvidedImageUrl = null, botImageUrls = []) { const chatWindow = document.getElementById('chat-window'); const messageElement = document.createElement('div'); messageElement.classList.add('message', sender); // 如果是用户发送的图片,直接显示 if (userProvidedImageUrl) { const imgElement = document.createElement('img'); imgElement.src = userProvidedImageUrl; imgElement.style.maxWidth = '200px'; imgElement.style.display = 'block'; imgElement.style.marginTop = '5px'; imgElement.alt = '用户上传图片'; messageElement.appendChild(imgElement); } let processedText = text; let extractedMermaidCodes = []; // 如果是 bot 消息,先添加图片,再处理文本和 Mermaid if (sender === 'bot') { // 提取 Mermaid 代码、清理文本 const result = extractContent(text); processedText = result.cleanedText; extractedMermaidCodes = result.mermaidCodes; const allImageUrls = [...botImageUrls]; // 添加所有提取到的图片 if (allImageUrls.length > 0) { const authToken = localStorage.getItem(AUTH_TOKEN_STORAGE_KEY); allImageUrls.forEach(imgUrl => { const imgElement = document.createElement('img'); imgElement.style.maxWidth = '200px'; imgElement.style.display = 'block'; imgElement.style.marginTop = '5px'; imgElement.alt = 'Gemini 输出图片'; // 添加错误处理,如果图片加载失败则显示替代文本 imgElement.onerror = () => { console.error(`图片加载失败: ${imgUrl}`); imgElement.alt = `图片加载失败: ${imgUrl}`; imgElement.src = ''; }; // 检查是否是代理 URL if (imgUrl.startsWith('/v1/image_proxy?image_url=')) { if (authToken) { // 使用 fetch API 发送带有认证头的请求 fetch(imgUrl, { headers: { 'X-Auth-Token': authToken } }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.blob(); }) .then(imageBlob => { imgElement.src = URL.createObjectURL(imageBlob); messageElement.appendChild(imgElement); }) .catch(error => { console.error(`代理图片加载失败: ${imgUrl}`, error); imgElement.onerror(); }); } else { console.error('未找到认证令牌,无法加载代理图片。'); imgElement.onerror(); } } else { // 直接使用 imgUrl,因为后端现在可能返回 Base64 Data URL imgElement.src = imgUrl; messageElement.appendChild(imgElement); } }); } } // 渲染剩余的文本内容(包括 Markdown 和非图片/Mermaid 文本) if (processedText) { const textElement = document.createElement('div'); textElement.innerHTML = marked.parse(processedText); messageElement.appendChild(textElement); } chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; // 在消息元素添加到 DOM 后,再渲染 Mermaid 图表 extractedMermaidCodes.forEach((mermaidCode, index) => { const mermaidDiv = document.createElement('div'); mermaidDiv.classList.add('mermaid'); // 为每个 Mermaid 图表生成一个唯一的 ID const chartId = `mermaid-chart-${Date.now()}-${index}`; mermaidDiv.id = chartId; messageElement.appendChild(mermaidDiv); // 使用 mermaid.render 方法直接渲染 Mermaid 代码 mermaid.render(chartId, mermaidCode) .then(({ svg, bindFunctions }) => { const renderedMermaidDiv = document.getElementById(chartId); if (renderedMermaidDiv) { renderedMermaidDiv.innerHTML = svg; if (bindFunctions) { bindFunctions(renderedMermaidDiv); } } }) .catch(error => { console.error(`Mermaid 渲染失败 (ID: ${chartId}):`, error); const renderedMermaidDiv = document.getElementById(chartId); if (renderedMermaidDiv) { renderedMermaidDiv.innerHTML = `