From 691b7f0e08649e13fcce932b33725f5dfc6f2749 Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Sun, 26 Apr 2026 17:54:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=E7=94=9F=E6=88=90=E5=9B=9E=E5=A4=8D?= =?UTF-8?q?=E6=97=B6=E6=98=BE=E7=A4=BA=E5=81=9C=E6=AD=A2=E7=94=9F=E6=88=90?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- www/app.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ www/index.html | 6 +++--- www/style.css | 32 ++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/www/app.js b/www/app.js index c8041b3..b9d74fd 100644 --- a/www/app.js +++ b/www/app.js @@ -781,6 +781,9 @@ async function streamGenerate(userMsgIndex) { } contentEl.innerHTML = ''; + + // 显示停止生成按钮 + showStopGenerateBtn(); try { // 构建请求体 - 统一使用 glm-4.5-air,通过 thinking 参数控制 @@ -814,8 +817,30 @@ async function streamGenerate(userMsgIndex) { const decoder = new TextDecoder(); let buffer = ''; let thinkingOutputStarted = false; // 正式内容是否开始输出 + let abortController = new AbortController(); // 用于中断流 + + // 绑定停止按钮事件 + const stopBtn = document.getElementById('stopGenerateBtn'); + if (stopBtn) { + stopBtn.onclick = () => { + abortController.abort(); + isLoading = false; + sendBtn.disabled = false; + hideStopGenerateBtn(); + // 更新最终内容 + if (thinkingEl && enableThinking && currentConversation.messages[aiMessageIndex].thinking) { + thinkingEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].thinking); + } + contentEl.innerHTML = renderMarkdown(currentConversation.messages[aiMessageIndex].content); + currentConversation.updatedAt = Date.now(); + saveConversations(); + renderMessages(); + }; + } while (true) { + if (abortController.signal.aborted) break; // 检查是否已停止 + const { done, value } = await reader.read(); if (done) break; @@ -877,6 +902,7 @@ async function streamGenerate(userMsgIndex) { } finally { isLoading = false; sendBtn.disabled = false; + hideStopGenerateBtn(); currentConversation.updatedAt = Date.now(); saveConversations(); renderMessages(); @@ -889,6 +915,33 @@ async function streamGenerate(userMsgIndex) { } } +// 显示停止生成按钮 +function showStopGenerateBtn() { + // 检查是否已存在 + if (document.getElementById('stopGenerateBtn')) return; + + const stopBtn = document.createElement('button'); + stopBtn.id = 'stopGenerateBtn'; + stopBtn.className = 'stop-generate-btn'; + stopBtn.innerHTML = ` + + 停止生成 + `; + + // 插入到消息容器底部 + if (messagesContainer) { + messagesContainer.appendChild(stopBtn); + } +} + +// 隐藏停止生成按钮 +function hideStopGenerateBtn() { + const stopBtn = document.getElementById('stopGenerateBtn'); + if (stopBtn) { + stopBtn.remove(); + } +} + // 生成对话标题 async function generateConversationTitle() { if (!currentConversation) return; diff --git a/www/index.html b/www/index.html index b1b3d3c..5a064ae 100644 --- a/www/index.html +++ b/www/index.html @@ -8,12 +8,12 @@ AI助手 - +
- - + + \ No newline at end of file diff --git a/www/style.css b/www/style.css index e7a0624..96a0a67 100644 --- a/www/style.css +++ b/www/style.css @@ -1055,4 +1055,36 @@ body { .input-area { padding-bottom: calc(12px + env(safe-area-inset-bottom)); } +} + +/* 停止生成按钮 */ +.stop-generate-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 12px 24px; + margin: 16px auto; + background: linear-gradient(135deg, #e53e3e 0%, #c53030 100%); + color: white; + border: none; + border-radius: 24px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + box-shadow: 0 2px 8px rgba(229, 62, 62, 0.3); +} + +.stop-generate-btn:hover { + transform: scale(1.05); + box-shadow: 0 4px 16px rgba(229, 62, 62, 0.4); +} + +.stop-generate-btn:active { + transform: scale(0.98); +} + +.stop-generate-btn svg { + flex-shrink: 0; } \ No newline at end of file