From 7fa143b5b0497202e80522952871623396ed157b Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Mon, 13 Apr 2026 10:53:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20assistant=E6=B6=88=E6=81=AF=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E7=89=88=E6=9C=AC=E5=8E=86=E5=8F=B2=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=94=9F=E6=88=90=E4=BF=9D=E7=95=99=E6=97=A7?= =?UTF-8?q?=E7=89=88=E6=9C=AC+=E7=89=88=E6=9C=AC=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/index.html | 222 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 211 insertions(+), 11 deletions(-) diff --git a/templates/index.html b/templates/index.html index 803177c..003823a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -58,6 +58,23 @@ .action-btn.copied { color: #10a37f; border-color: #10a37f; background: #e8f5e9; } .action-btn.regenerate:hover { color: #667eea; border-color: #667eea; } + /* 版本切换控件 */ + .version-controls { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 8px; padding: 8px 0; } + .version-btn { width: 28px; height: 28px; border-radius: 6px; background: #f0f0f0; border: 1px solid #ddd; cursor: pointer; display: flex; align-items: center; justify-content: center; color: #666; transition: all 0.2s; } + .version-btn:hover { background: #e8e8e8; border-color: #667eea; color: #667eea; } + .version-btn:disabled { opacity: 0.5; cursor: not-allowed; } + .version-indicator { font-size: 12px; color: #666; min-width: 60px; text-align: center; } + + /* 加载动画 */ + .loading-indicator { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 16px; color: #667eea; } + .loading-spinner { width: 20px; height: 20px; border: 2px solid #667eea; border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; } + @keyframes spin { to { transform: rotate(360deg); } } + + /* 消息内容容器 */ + .response-container { position: relative; } + .response-version { display: none; } + .response-version.active { display: block; } + /* 思考内容样式 */ .thinking-block { background: #f8f9fa; border-left: 3px solid #667eea; padding: 12px 16px; margin: 8px 0 16px 0; font-size: 14px; color: #666; border-radius: 4px; position: relative; } .thinking-header { display: flex; align-items: center; justify-content: space-between; cursor: pointer; } @@ -172,6 +189,9 @@ let quickPhrases = []; let lastUserMessage = null; // 存储最后一条用户消息,用于重新生成 let isRegenerating = false; // 标志:正在重新生成,跳过用户消息显示 + let regeneratingMessageId = null; // 正在重新生成的消息ID + let messageVersionCounter = 0; // 消息版本计数器 + let messageVersions = {}; // 存储每个assistant消息的多个版本 { messageId: [{content, thinking}] } document.addEventListener('DOMContentLoaded', () => { loadAgents(); @@ -246,7 +266,17 @@ } isRegenerating = false; // 重置标志 break; - case 'assistant_message': appendMessage('assistant', data.message.content, data.message.thinking_content, data.message.agent_name); document.getElementById('sendBtn').disabled = false; break; + case 'assistant_message': + if (isRegenerating && regeneratingMessageId) { + // 添加新版本到现有消息 + addResponseVersion(regeneratingMessageId, data.message.content, data.message.thinking_content); + hideLoadingIndicator(regeneratingMessageId); + regeneratingMessageId = null; + } else { + appendMessage('assistant', data.message.content, data.message.thinking_content, data.message.agent_name); + } + document.getElementById('sendBtn').disabled = false; + break; case 'error': showError(data.message); document.getElementById('sendBtn').disabled = false; break; } } @@ -260,6 +290,17 @@ div.className = `message ${role}`; const avatar = role === 'user' ? '👤' : '🤖'; + let messageId = null; + + if (role === 'assistant') { + // 为assistant消息生成唯一ID + messageId = `msg_${++messageVersionCounter}`; + div.id = messageId; + div.dataset.messageId = messageId; + // 初始化版本存储 + messageVersions[messageId] = [{ content, thinking }]; + } + let html = `
${avatar}
`; // 思考内容 @@ -273,10 +314,14 @@
`; } - // 消息内容 + // 消息内容 - assistant使用版本容器 html += `
`; if (role === 'assistant') { - html += `
${marked.parse(content)}
`; + html += `
+
+
${marked.parse(content)}
+
+
`; } else { html += `
${escapeHtml(content)}
`; } @@ -287,10 +332,19 @@ html += `
`; html += ``; if (role === 'assistant') { - html += ``; + html += ``; } html += `
`; + // assistant消息的版本切换控件(初始隐藏,有多个版本时显示) + if (role === 'assistant') { + html += ``; + } + // Agent信息 if (role === 'assistant' && agentName) { html += `
${agentName}
`; @@ -302,6 +356,153 @@ container.scrollTop = container.scrollHeight; } + // 添加新版本到现有消息 + function addResponseVersion(messageId, content, thinking) { + const versions = messageVersions[messageId]; + if (!versions) return; + + // 添加新版本 + versions.push({ content, thinking }); + const newVersionIndex = versions.length - 1; + + // 更新容器 + const container = document.getElementById(`${messageId}_container`); + if (container) { + // 添加思考块(如果有) + const messageBody = container.closest('.message-body'); + if (thinking && messageBody) { + // 先移除旧的思考块 + const oldThinking = messageBody.querySelector('.thinking-block'); + if (oldThinking) oldThinking.remove(); + + // 添加新的思考块 + const thinkingHtml = `
+
+ 思考过程 + 点击展开 +
+
${escapeHtml(thinking)}
+
`; + messageBody.insertAdjacentHTML('afterbegin', thinkingHtml); + } + + // 隐藏所有旧版本,添加新版本 + container.querySelectorAll('.response-version').forEach(v => v.classList.remove('active')); + const newVersionHtml = `
+
${marked.parse(content)}
+
`; + container.insertAdjacentHTML('beforeend', newVersionHtml); + } + + // 更新复制源 + const messageDiv = document.getElementById(messageId); + if (messageDiv) { + const copySource = messageDiv.querySelector('.copy-source'); + if (copySource) copySource.value = content; + } + + // 显示版本切换控件 + showVersionControls(messageId); + } + + // 显示loading动画 + function showLoadingIndicator(messageId) { + const container = document.getElementById(`${messageId}_container`); + if (container) { + // 隐藏所有版本 + container.querySelectorAll('.response-version').forEach(v => v.classList.remove('active')); + // 显示loading + const loadingHtml = `
+
+ 正在生成... +
`; + container.insertAdjacentHTML('beforeend', loadingHtml); + } + } + + // 隐藏loading动画 + function hideLoadingIndicator(messageId) { + const loading = document.getElementById(`${messageId}_loading`); + if (loading) loading.remove(); + } + + // 显示版本切换控件 + function showVersionControls(messageId) { + const controls = document.getElementById(`${messageId}_version_controls`); + const versions = messageVersions[messageId]; + if (controls && versions && versions.length > 1) { + controls.style.display = 'flex'; + updateVersionIndicator(messageId); + } + } + + // 更新版本指示器 + function updateVersionIndicator(messageId) { + const container = document.getElementById(`${messageId}_container`); + const indicator = document.getElementById(`${messageId}_version_indicator`); + const versions = messageVersions[messageId]; + if (!container || !indicator || !versions) return; + + // 找到当前激活的版本 + const activeVersion = container.querySelector('.response-version.active'); + const currentIndex = activeVersion ? parseInt(activeVersion.dataset.version) : 0; + + indicator.textContent = `${currentIndex + 1}/${versions.length}`; + + // 更新按钮状态 + const prevBtn = indicator.parentElement.querySelector('[data-dir="prev"]'); + const nextBtn = indicator.parentElement.querySelector('[data-dir="next"]'); + if (prevBtn) prevBtn.disabled = currentIndex === 0; + if (nextBtn) nextBtn.disabled = currentIndex === versions.length - 1; + } + + // 切换版本 + function switchVersion(messageId, direction) { + const container = document.getElementById(`${messageId}_container`); + const versions = messageVersions[messageId]; + if (!container || !versions) return; + + // 找到当前激活的版本 + const activeVersion = container.querySelector('.response-version.active'); + const currentIndex = activeVersion ? parseInt(activeVersion.dataset.version) : 0; + const newIndex = currentIndex + direction; + + if (newIndex < 0 || newIndex >= versions.length) return; + + // 切换显示 + container.querySelectorAll('.response-version').forEach(v => { + v.classList.remove('active'); + if (parseInt(v.dataset.version) === newIndex) v.classList.add('active'); + }); + + // 更新复制源 + const messageDiv = document.getElementById(messageId); + if (messageDiv) { + const copySource = messageDiv.querySelector('.copy-source'); + if (copySource) copySource.value = versions[newIndex].content; + } + + // 更新思考块 + const messageBody = container.closest('.message-body'); + if (messageBody && versions[newIndex].thinking) { + // 移除旧的思考块 + const oldThinking = messageBody.querySelector('.thinking-block'); + if (oldThinking) oldThinking.remove(); + + // 添加新版本的思考块 + const thinkingHtml = `
+
+ 思考过程 + 点击展开 +
+
${escapeHtml(versions[newIndex].thinking)}
+
`; + messageBody.insertAdjacentHTML('afterbegin', thinkingHtml); + } + + updateVersionIndicator(messageId); + } + function toggleThinking(header) { const block = header.parentElement; const content = block.querySelector('.thinking-content'); @@ -357,19 +558,18 @@ document.body.removeChild(textarea); } - function regenerateMessage() { + function regenerateMessage(messageId) { if (!lastUserMessage) { alert('没有可重新生成的消息'); return; } // 设置重新生成标志,避免再次显示用户消息 isRegenerating = true; - // 移除最后一条助手消息 - const container = document.getElementById('messagesContainer'); - const messages = container.querySelectorAll('.message.assistant'); - if (messages.length > 0) { - messages[messages.length - 1].remove(); - } + regeneratingMessageId = messageId; + + // 显示loading动画 + showLoadingIndicator(messageId); + // 重新发送最后一条用户消息 document.getElementById('sendBtn').disabled = true; if (ws?.readyState === WebSocket.OPEN) {