From c52331cd5e2ec2860bf0082278ea58bd8ae274d8 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 16 Aug 2026 09:57:29 +0800 Subject: [PATCH] =?UTF-8?q?gui+webui:=20=E8=81=8A=E5=A4=A9=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=8D=87=E7=BA=A7(PiDeck=E9=A3=8E=E6=A0=BC=E6=80=9D?= =?UTF-8?q?=E8=80=83/=E5=B7=A5=E5=85=B7=E5=8D=A1=E7=89=87+=E6=BB=B4?= =?UTF-8?q?=E5=85=A5=E5=8A=A8=E7=94=BB)=20&=20=E6=B5=81=E5=BC=8F=E5=8D=A1?= =?UTF-8?q?=E9=A1=BF=E4=BF=AE=E5=A4=8D(=E9=98=B2=E6=8A=96=E5=B1=80?= =?UTF-8?q?=E9=83=A8=E5=A2=9E=E9=87=8F=E6=B8=B2=E6=9F=93)=20&=20=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E7=B3=BB=E7=BB=9F=E4=BF=AE=E5=A4=8D(=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=81=A2=E5=A4=8D=E8=89=B2=E6=9D=BF/=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E6=BA=A2=E5=87=BAwrap)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/gui/renderer/app.js | 193 ++++++++++---- cmd/gui/renderer/style.css | 211 +++++++++++---- internal/plugins/webui/dashboard.html | 370 ++++++++++++++++++-------- 3 files changed, 566 insertions(+), 208 deletions(-) diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js index 2591365..794c488 100644 --- a/cmd/gui/renderer/app.js +++ b/cmd/gui/renderer/app.js @@ -289,6 +289,8 @@ function toggleAppearance() { (() => { var saved = localStorage.getItem("ha-theme"); setTheme(saved || "light"); + var savedColor = localStorage.getItem("ha-color"); + if (savedColor) setColor(savedColor); var finalSrc = localStorage.getItem("ha-bg-final"); var img = localStorage.getItem("ha-bg-img"); var blur = localStorage.getItem("ha-bg-blur"); @@ -963,6 +965,27 @@ function chanLetter(src) { return /[A-Za-z0-9]/.test(ch) ? ch : "C"; } +// PiDeck 风格思考卡片:Brain 图标 + 折叠时单行预览(流式中扫光)+ 展开/收起 +// PiDeck 风格思考卡片:Brain 图标 + 折叠单行预览(流式中扫光)+ 展开懒加载全文 +function renderReasoningCard(text, isStreaming, idx) { + var preview = + typeof marked === "undefined" + ? escHtml(text).replace(/<[^>]+>/g, " ").slice(0, 60) + : text.replace(/[\s\n]+/g, " ").slice(0, 60); + return ( + '
' + + '
' + + '' + + '' + (isStreaming ? __("思考中...","Thinking...") : __("思考","Thinking")) + '' + + '
' + + '
' + + (isStreaming + ? '
' + escHtml(preview) + '
' + : '
' + (typeof marked !== "undefined" ? marked.parse(text) : escHtml(text)) + '
') + + '
' + ); +} + function renderChat() { if (!_chatLayoutBuilt) { buildChatLayout(); @@ -1068,24 +1091,7 @@ function renderChat() { var isChan = !!(m.source && m.source !== "webui"); var rc = ""; if (m.reasoning_content) { - var rcBody = - typeof marked === "undefined" - ? escHtml(m.reasoning_content) - : marked.parse(m.reasoning_content); - rc = - '
' + - "
" + - __("展开思考", "Expand") + - "
" + - '
"; + rc = renderReasoningCard(m.reasoning_content, isStreamingLast, i); } var tcs = ""; if (m.tool_calls && m.tool_calls.length > 0) { @@ -1099,41 +1105,47 @@ function renderChat() { ? JSON.stringify(tc.result, null, 1) : String(tc.result) : ""; - var statusIcon = + var running = !resultStr && tc.status !== "denied"; + var error = tc.status === "error" || tc.status === "denied" || !!tc.error; + var drip = newlyDone.indexOf(tc.tool || tc.name || "") !== -1 ? " tool-drip-in" : ""; + var iconSvg = + error + ? '' + : running + ? '' + : ''; + var statusHtml = tc.status === "denied" - ? '' - : ''; + ? '' + __("已拒绝","Denied") + "" + : running + ? '' + __("调用中","Running") + "" + : '' + __("完成","Done") + ""; + var pluginHtml = tc.plugin + ? '' + escHtml(tc.plugin) + "" + : ""; tcs += - '
' + '
' + - statusIcon + + iconSvg + '' + escHtml(tc.tool || tc.name || "") + "" + - (tc.status === "denied" - ? '' + - __("已拒绝", "Denied") + - "" - : resultStr - ? '' + - __("完成", "Done") + - "" - : '' + - __("调用中", "Running") + - "") + + pluginHtml + + statusHtml + '
' + '
"; + ""; }); } var body = rc + tcs; @@ -1302,6 +1314,29 @@ function toggleToolCall(el) { } } +function toggleReasoning(el) { + var card = el.closest(".reasoning-card"); + if (!card) return; + var body = card.querySelector(".reasoning-body"); + if (!body) return; + var open = body.style.display !== "none"; + if (open) { + body.style.display = "none"; + card.classList.remove("open"); + return; + } + // 展开时懒加载全文(流式中只有 preview,未 parse 全文) + var content = card.querySelector(".reasoning-content"); + if (content && !content.childElementCount) { + var idx = parseInt(card.getAttribute("data-idx"), 10) || 0; + var text = (state.messages[idx] && state.messages[idx].reasoning_content) || ""; + content.innerHTML = + typeof marked !== "undefined" ? marked.parse(text) : escHtml(text); + } + body.style.display = "block"; + card.classList.add("open"); +} + function renderChatStarmap() { var cont = document.getElementById("sm-container-chat"); if (!cont) return; @@ -4487,10 +4522,78 @@ async function connectFetchSSE(url) { function rerenderChatIfActive() { var tab = document.getElementById("view-chat"); - if (tab && tab.classList.contains("active")) { - renderChat(); - renderChatStarmap(); - renderTerminals(); - renderCmdHistory(); + if (!tab || !tab.classList.contains("active")) return; + // 流式增量路径:防抖合并 + 只更新最后一条消息的正文/思考节点,避免全量重建 + var msgs = state.messages; + var last = msgs.length ? msgs[msgs.length - 1] : null; + var streamingLast = + !!last && last.role === "assistant" && !last._final && state.chatLoading; + if (streamingLast) { + if (state._streamTimer) clearTimeout(state._streamTimer); + state._streamTimer = setTimeout(function () { + state._streamTimer = null; + renderChatStreamChunk(); + }, 90); + return; } + // 非流式(完成/工具/历史变化):全量渲染 + if (state._streamTimer) { + clearTimeout(state._streamTimer); + state._streamTimer = null; + } + renderChat(); + renderChatStarmap(); + renderTerminals(); + renderCmdHistory(); } + +// 流式增量渲染:仅更新最后一条 assistant 消息的正文(渐进,节流 parse)与思考预览 +function renderChatStreamChunk() { + var msgsEl = document.getElementById("chat-msgs"); + var msgs = state.messages; + var last = msgs.length ? msgs[msgs.length - 1] : null; + if (!msgsEl || !last) return; + var el = msgsEl.lastElementChild; + if (!el) { + renderChat(); + return; + } + // 更新正文文本(节流 parse:内容变化 >200 字符或时间 >300ms 才 parse) + var textEl = el.querySelector(".msg-bubble .text"); + var c = last.content || ""; + if (textEl) { + var now = Date.now(); + var lastParse = el.__lastParse || 0; + var lastLen = el.__lastLen || 0; + if (c.length - lastLen > 200 || now - lastParse > 300) { + textEl.innerHTML = + typeof marked !== "undefined" ? marked.parse(c) : escHtml(c); + el.__lastParse = now; + el.__lastLen = c.length; + } else { + // 小增量:纯文本渐进,避免反复 parse + var tail = c.slice(lastLen); + if (tail) { + var tn = document.createTextNode(tail); + textEl.appendChild(tn); + } + el.__lastLen = c.length; + } + if (state.chatStick !== false) { + try { + msgsEl.scrollTop = msgsEl.scrollHeight; + } catch (e) {} + } + return; + } + // 思考预览更新(流式中折叠,只刷 preview + sweep) + var rc = el.querySelector(".reasoning-card.rc-streaming .reasoning-preview"); + if (rc && last.reasoning_content) { + var prev = last.reasoning_content.replace(/[\s\n]+/g, " ").slice(0, 60); + rc.textContent = prev; + return; + } + // 兜底:结构变化则全量 + renderChat(); +} + diff --git a/cmd/gui/renderer/style.css b/cmd/gui/renderer/style.css index 78e66e0..053fb3b 100644 --- a/cmd/gui/renderer/style.css +++ b/cmd/gui/renderer/style.css @@ -553,11 +553,15 @@ body.maximized .tb-max svg { filter: blur(calc(var(--bg-blur, 0) * 1px)); transition: filter 0.25s var(--ease-out); } + .palette-pop { position: fixed; bottom: 56px; left: 6px; - width: 210px; + width: 216px; + max-width: calc(100vw - 24px); + max-height: calc(100vh - 120px); + overflow-y: auto; padding: 12px; background: var(--glass-bg-strong); backdrop-filter: blur(12px); @@ -586,7 +590,7 @@ body.maximized .tb-max svg { margin: 3px; padding: 0; vertical-align: middle; - transition: transform 0.15s; + transition: transform 0.15s, box-shadow 0.15s; } .palette-pop button.cdot:hover { transform: scale(1.25); @@ -607,27 +611,33 @@ body.maximized .tb-max svg { margin-bottom: 6px; outline: none; } -.palette-pop input[type="range"] { - width: 80px; - margin: 0; - accent-color: var(--accent); - vertical-align: middle; -} .palette-pop .pp-row { display: flex; + flex-wrap: wrap; align-items: center; - gap: 6px; + gap: 5px; margin-top: 4px; } .palette-pop .pp-row .btn { - padding: 3px 10px; + padding: 3px 9px; font-size: 11px; + white-space: nowrap; } .palette-pop .pp-val { + display: inline-flex; + align-items: center; + gap: 4px; font-size: 10px; color: var(--text-muted); - min-width: 96px; + min-width: 0; text-align: right; + margin-left: auto; +} +.palette-pop input[type="range"] { + width: 72px; + margin: 0; + accent-color: var(--accent); + vertical-align: middle; } .lang-btn { font-size: 13px; @@ -1179,26 +1189,73 @@ code { max-width: 100%; border-radius: var(--radius-sm); } -.msg-bubble .reasoning { - border-left: 2px solid #dddddd; - padding-left: 8px; + +/* ===== 思考卡片(PiDeck 风格)===== */ +.reasoning-card { margin: 6px 0; - font-size: 11px; - opacity: 0.8; + border: 1px dashed var(--kv-border); + border-radius: var(--radius-sm); + background: var(--bg-panel, var(--bg-hover)); + overflow: hidden; } -.msg-bubble .reasoning-title { +.reasoning-card .reasoning-head { + display: flex; + align-items: center; + gap: 7px; + padding: 6px 10px; + font-size: 11px; cursor: pointer; - font-size: 10px; - font-weight: 600; user-select: none; - margin-bottom: 2px; + color: var(--text-secondary); +} +.reasoning-card .reasoning-head:hover { + background: var(--bg-hover); +} +.reasoning-card .rc-ico { + color: var(--accent); + flex-shrink: 0; +} +.reasoning-card .rc-title { + font-weight: 600; + color: var(--text-primary); +} +.reasoning-card .rc-chev { + margin-left: auto; + font-size: 10px; + color: var(--text-muted); + transition: transform 0.2s var(--ease-out); +} +.reasoning-card.open .rc-chev { + transform: rotate(180deg); +} +.reasoning-card .reasoning-body { + padding: 4px 10px 8px; +} +.reasoning-card .reasoning-content { + line-height: 1.5; + color: var(--text-tertiary); + font-size: 11.5px; +} +.reasoning-card .reasoning-content p { + margin: 4px 0; +} +.reasoning-card .reasoning-sweep { + height: 2px; + border-radius: 2px; + margin-top: 6px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + background-size: 200% 100%; + animation: reasoningSweep 1.2s var(--ease-out) infinite; opacity: 0.6; } -.msg-bubble .reasoning-body { - line-height: 1.4; +@keyframes reasoningSweep { + from { background-position: 200% 0; } + to { background-position: -200% 0; } } -.msg-bubble .tool-call { - background: var(--bg-hover); + +/* ===== 工具卡片(PiDeck 风格,扁平不套气泡)===== */ +.tool-card { + background: var(--bg-panel, var(--bg-hover)); color: var(--text-secondary); border-radius: var(--radius-sm); padding: 7px 10px; @@ -1207,55 +1264,101 @@ code { border: 1px solid var(--kv-border); border-left: 3px solid var(--accent); cursor: pointer; + transition: border-color 0.2s var(--ease-out), background 0.2s var(--ease-out); } -.msg-bubble .tool-call .tc-line { +.tool-card.tc-running { + border-left-color: var(--accent); +} +.tool-card.tc-done { + border-left-color: rgba(23, 169, 100, 0.8); +} +.tool-card.tc-error { + border-left-color: var(--error, #db3694); +} +.tool-card .tc-line { display: flex; align-items: center; gap: 6px; font-size: 11px; user-select: none; } -.msg-bubble .tool-call .tc-ico { +.tool-card .tc-ico { display: inline-flex; flex-shrink: 0; + color: var(--accent); } -.msg-bubble .tool-call .tc-name { +.tool-card.tc-error .tc-ico { + color: var(--error, #db3694); +} +.tool-card.tc-done .tc-ico { + color: rgba(23, 169, 100, 0.9); +} +.tool-card .tc-name { font-weight: 600; color: var(--text-primary); } -.msg-bubble .tool-call .tc-state { - margin-left: 6px; +.tool-card .tc-plugin { + font-size: 9.5px; + color: var(--text-muted); + background: var(--pre-bg); + border-radius: 4px; + padding: 0 4px; + line-height: 1.6; + flex-shrink: 0; +} +.tool-card .tc-state { + margin-left: auto; font-size: 9.5px; font-weight: 500; padding: 1px 6px; border-radius: 999px; flex-shrink: 0; - margin-left: auto; + display: inline-flex; + align-items: center; + gap: 4px; } -.msg-bubble .tool-call .tc-run { +.tool-card .tc-run { background: rgba(63, 110, 245, 0.18); color: #a3b8ff; } -.msg-bubble .tool-call .tc-done { +.tool-card .tc-done { background: rgba(23, 169, 100, 0.18); color: #6ee7a8; } -.msg-bubble .tool-call .tc-deny { +.tool-card .tc-deny { background: rgba(219, 54, 148, 0.18); color: #ff9ec6; } -.msg-bubble .tool-call .tc-caret { +.tool-card .tc-spinner { + width: 11px; + height: 11px; + border-radius: 50%; + border: 2px solid rgba(63, 110, 245, 0.25); + border-top-color: #3f6ef5; + animation: spin 0.7s linear infinite; + display: inline-block; +} +.tool-card .tc-caret { font-size: 10px; color: var(--text-muted); transition: transform 0.15s var(--ease-out); } -.msg-bubble .tool-call.open .tc-caret { +.tool-card.open .tc-caret { transform: rotate(180deg); } -.msg-bubble .tool-call .tc-detail { +.tool-card .tc-detail { margin-top: 6px; } -.msg-bubble .tool-call .tc-args { +.tool-card .tc-detail-label { + font-size: 9.5px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.03em; + margin-bottom: 2px; +} +.tool-card .tc-args, +.tool-card .tc-result { font-family: var(--font-mono); font-size: 11px; opacity: 0.85; @@ -1267,29 +1370,37 @@ code { border-radius: 4px; padding: 5px 7px; } -.msg-bubble .tool-call .tc-result { - font-family: var(--font-mono); - font-size: 11px; +.tool-card .tc-result { opacity: 0.72; - white-space: pre-wrap; - word-break: break-all; - margin-top: 5px; - background: var(--pre-bg); - color: var(--pre-color); - border-radius: 4px; - padding: 5px 7px; max-height: 80px; overflow-y: auto; } -[data-theme="light"] .msg-bubble .tool-call .tc-run { +[data-theme="light"] .tool-card .tc-run { color: #3f6ef5; } -[data-theme="light"] .msg-bubble .tool-call .tc-done { +[data-theme="light"] .tool-card .tc-done { color: #128a52; } -[data-theme="light"] .msg-bubble .tool-call .tc-deny { +[data-theme="light"] .tool-card .tc-deny { color: #c2185b; } +.tool-card.tool-drip-in { + animation: toolDripIn 0.32s var(--ease-out) both; +} +@keyframes toolDripIn { + 0% { + opacity: 0; + transform: translateY(-18px) scale(0.96); + } + 70% { + opacity: 1; + transform: translateY(2px) scale(1.005); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } +} .msg-bubble .thinking-tools { display: flex; gap: 6px; diff --git a/internal/plugins/webui/dashboard.html b/internal/plugins/webui/dashboard.html index 0d2dc74..97acbcb 100644 --- a/internal/plugins/webui/dashboard.html +++ b/internal/plugins/webui/dashboard.html @@ -518,7 +518,10 @@ background: position: fixed; bottom: 66px; left: 16px; - width: 200px; + width: 216px; + max-width: calc(100vw - 24px); + max-height: calc(100vh - 130px); + overflow-y: auto; padding: 12px; background: var(--glass-bg-strong); backdrop-filter: blur(12px); @@ -526,7 +529,7 @@ background: border-radius: var(--radius-md); box-shadow: var(--shadow-lg); display: none; - z-index: 90; + z-index: 300; } .palette-pop.on { display: block; @@ -547,7 +550,7 @@ background: margin: 3px; padding: 0; vertical-align: middle; - transition: transform var(--dur-micro) var(--ease-out); + transition: transform 0.15s, box-shadow 0.15s; } .palette-pop button.cdot:hover { transform: scale(1.25); @@ -568,26 +571,33 @@ background: margin-bottom: 6px; outline: none; } - .palette-pop input[type="range"] { - width: 100%; - margin: 2px 0 4px; - accent-color: var(--accent); - } .palette-pop .pp-row { display: flex; + flex-wrap: wrap; align-items: center; - gap: 6px; + gap: 5px; margin-top: 4px; } .palette-pop .pp-row .btn { - padding: 3px 10px; + padding: 3px 9px; font-size: 11px; + white-space: nowrap; } .palette-pop .pp-val { - font-size: 11px; + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; color: var(--text-muted); - min-width: 30px; + min-width: 0; text-align: right; + margin-left: auto; + } + .palette-pop input[type="range"] { + width: 72px; + margin: 0; + accent-color: var(--accent); + vertical-align: middle; } .container { padding: 24px; @@ -1373,26 +1383,73 @@ background: max-width: 100%; border-radius: 6px; } - .msg-bubble .reasoning { - border-left: 2px solid #dddddd; - padding-left: 8px; + + /* ===== 思考卡片(PiDeck 风格)===== */ + .reasoning-card { margin: 6px 0; - font-size: 11px; - opacity: 0.8; + border: 1px dashed var(--kv-border); + border-radius: var(--radius-sm); + background: var(--bg-panel, var(--bg-hover)); + overflow: hidden; } - .msg-bubble .reasoning-title { + .reasoning-card .reasoning-head { + display: flex; + align-items: center; + gap: 7px; + padding: 6px 10px; + font-size: 11px; cursor: pointer; - font-size: 10px; - font-weight: 600; user-select: none; - margin-bottom: 2px; + color: var(--text-secondary); + } + .reasoning-card .reasoning-head:hover { + background: var(--bg-hover); + } + .reasoning-card .rc-ico { + color: var(--accent); + flex-shrink: 0; + } + .reasoning-card .rc-title { + font-weight: 600; + color: var(--text-primary); + } + .reasoning-card .rc-chev { + margin-left: auto; + font-size: 10px; + color: var(--text-muted); + transition: transform 0.2s var(--ease-out); + } + .reasoning-card.open .rc-chev { + transform: rotate(180deg); + } + .reasoning-card .reasoning-body { + padding: 4px 10px 8px; + } + .reasoning-card .reasoning-content { + line-height: 1.5; + color: var(--text-tertiary); + font-size: 11.5px; + } + .reasoning-card .reasoning-content p { + margin: 4px 0; + } + .reasoning-card .reasoning-sweep { + height: 2px; + border-radius: 2px; + margin-top: 6px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + background-size: 200% 100%; + animation: reasoningSweep 1.2s var(--ease-out) infinite; opacity: 0.6; } - .msg-bubble .reasoning-body { - line-height: 1.4; + @keyframes reasoningSweep { + from { background-position: 200% 0; } + to { background-position: -200% 0; } } - .msg-bubble .tool-call { - background: var(--bg-hover); + + /* ===== 工具卡片(PiDeck 风格,扁平不套气泡)===== */ + .tool-card { + background: var(--bg-panel, var(--bg-hover)); color: var(--text-secondary); border-radius: var(--radius-sm); padding: 7px 10px; @@ -1401,54 +1458,101 @@ background: border: 1px solid var(--kv-border); border-left: 3px solid var(--accent); cursor: pointer; + transition: border-color 0.2s var(--ease-out), background 0.2s var(--ease-out); } - .msg-bubble .tool-call .tc-line { + .tool-card.tc-running { + border-left-color: var(--accent); + } + .tool-card.tc-done { + border-left-color: rgba(23, 169, 100, 0.8); + } + .tool-card.tc-error { + border-left-color: var(--error, #db3694); + } + .tool-card .tc-line { display: flex; align-items: center; gap: 6px; font-size: 11px; user-select: none; } - .msg-bubble .tool-call .tc-ico { + .tool-card .tc-ico { display: inline-flex; flex-shrink: 0; + color: var(--accent); } - .msg-bubble .tool-call .tc-name { + .tool-card.tc-error .tc-ico { + color: var(--error, #db3694); + } + .tool-card.tc-done .tc-ico { + color: rgba(23, 169, 100, 0.9); + } + .tool-card .tc-name { font-weight: 600; color: var(--text-primary); } - .msg-bubble .tool-call .tc-state { + .tool-card .tc-plugin { + font-size: 9.5px; + color: var(--text-muted); + background: var(--pre-bg); + border-radius: 4px; + padding: 0 4px; + line-height: 1.6; + flex-shrink: 0; + } + .tool-card .tc-state { margin-left: auto; font-size: 9.5px; font-weight: 500; padding: 1px 6px; border-radius: 999px; flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 4px; } - .msg-bubble .tool-call .tc-run { + .tool-card .tc-run { background: rgba(63, 110, 245, 0.18); color: #a3b8ff; } - .msg-bubble .tool-call .tc-done { + .tool-card .tc-done { background: rgba(23, 169, 100, 0.18); color: #6ee7a8; } - .msg-bubble .tool-call .tc-deny { + .tool-card .tc-deny { background: rgba(219, 54, 148, 0.18); color: #ff9ec6; } - .msg-bubble .tool-call .tc-caret { + .tool-card .tc-spinner { + width: 11px; + height: 11px; + border-radius: 50%; + border: 2px solid rgba(63, 110, 245, 0.25); + border-top-color: #3f6ef5; + animation: spin 0.7s linear infinite; + display: inline-block; + } + .tool-card .tc-caret { font-size: 10px; color: var(--text-muted); transition: transform 0.15s var(--ease-out); } - .msg-bubble .tool-call.open .tc-caret { + .tool-card.open .tc-caret { transform: rotate(180deg); } - .msg-bubble .tool-call .tc-detail { + .tool-card .tc-detail { margin-top: 6px; } - .msg-bubble .tool-call .tc-args { + .tool-card .tc-detail-label { + font-size: 9.5px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.03em; + margin-bottom: 2px; + } + .tool-card .tc-args, + .tool-card .tc-result { font-family: var(--font-mono); font-size: 11px; opacity: 0.85; @@ -1460,29 +1564,37 @@ background: border-radius: 4px; padding: 5px 7px; } - .msg-bubble .tool-call .tc-result { - font-family: var(--font-mono); - font-size: 11px; + .tool-card .tc-result { opacity: 0.72; - white-space: pre-wrap; - word-break: break-all; - margin-top: 5px; - background: var(--pre-bg); - color: var(--pre-color); - border-radius: 4px; - padding: 5px 7px; max-height: 80px; overflow-y: auto; } - [data-theme="light"] .msg-bubble .tool-call .tc-run { + [data-theme="light"] .tool-card .tc-run { color: #3f6ef5; } - [data-theme="light"] .msg-bubble .tool-call .tc-done { + [data-theme="light"] .tool-card .tc-done { color: #128a52; } - [data-theme="light"] .msg-bubble .tool-call .tc-deny { + [data-theme="light"] .tool-card .tc-deny { color: #c2185b; } + .tool-card.tool-drip-in { + animation: toolDripIn 0.32s var(--ease-out) both; + } + @keyframes toolDripIn { + 0% { + opacity: 0; + transform: translateY(-18px) scale(0.96); + } + 70% { + opacity: 1; + transform: translateY(2px) scale(1.005); + } + 100% { + opacity: 1; + transform: translateY(0) scale(1); + } + } .msg-bubble .thinking-tools { display: flex; gap: 6px; @@ -2387,6 +2499,8 @@ background: (function () { var saved = localStorage.getItem("ha-theme"); setTheme(saved || "light"); + var savedColor = localStorage.getItem("ha-color"); + if (savedColor) setColor(savedColor); var sb = localStorage.getItem("ha-sidebar"); if (sb === "1" || (!sb && window.innerWidth <= 768)) { document.body.classList.add("sidebar-collapsed"); @@ -3097,70 +3211,65 @@ background: c = escHtml(c); } var isChan = !!(m.source && m.source !== "webui"); - var rc = ""; - if (m.reasoning_content) { - var rcBody = - typeof marked !== "undefined" - ? marked.parse(m.reasoning_content) - : escHtml(m.reasoning_content); - rc = - '
' + - "
" + - __("展开思考", "Expand") + - "
" + - '
"; - } - var tcs = ""; - if (m.tool_calls && m.tool_calls.length > 0) { - m.tool_calls.forEach(function (tc) { - var argsStr = - typeof tc.args === "object" - ? JSON.stringify(tc.args, null, 1) - : tc.args || ""; - var resultStr = tc.result - ? typeof tc.result === "object" - ? JSON.stringify(tc.result, null, 1) - : String(tc.result) - : ""; - var statusIcon = - tc.status === "denied" - ? '' - : ''; - tcs += - '
' + - '
' + - statusIcon + - '' + - escHtml(tc.tool || tc.name || "") + - "" + - (tc.status === "denied" - ? '' + __("已拒绝", "Denied") + "" - : resultStr - ? '' + __("完成", "Done") + "" - : '' + __("调用中", "Running") + "") + - '
' + - '
"; - }); - } + var rc = ""; + if (m.reasoning_content) { + rc = renderReasoningCard(m.reasoning_content, isStreamingLast); + } + var tcs = ""; + if (m.tool_calls && m.tool_calls.length > 0) { + m.tool_calls.forEach(function (tc) { + var argsStr = + typeof tc.args === "object" + ? JSON.stringify(tc.args, null, 1) + : tc.args || ""; + var resultStr = tc.result + ? typeof tc.result === "object" + ? JSON.stringify(tc.result, null, 1) + : String(tc.result) + : ""; + var running = !resultStr && tc.status !== "denied"; + var error = tc.status === "error" || tc.status === "denied" || !!tc.error; + var drip = newlyDone.indexOf(tc.tool || tc.name || "") !== -1 ? " tool-drip-in" : ""; + var iconSvg = + error + ? '' + : running + ? '' + : ''; + var statusHtml = + tc.status === "denied" + ? '' + __("已拒绝", "Denied") + "" + : running + ? '' + __("调用中", "Running") + "" + : '' + __("完成", "Done") + ""; + var pluginHtml = tc.plugin + ? '' + escHtml(tc.plugin) + "" + : ""; + tcs += + '
' + + '
' + + iconSvg + + '' + + escHtml(tc.tool || tc.name || "") + + "" + + pluginHtml + + statusHtml + + '
' + + '
"; + }); + } var body = rc + tcs; var growCls = m._grow ? " grow-in" : ""; if (m._grow) m._grow = false; @@ -3274,6 +3383,41 @@ background: } } + function renderReasoningCard(text, isStreaming) { + var body = + typeof marked !== "undefined" + ? marked.parse(text) + : escHtml(text); + var preview = + typeof marked !== "undefined" + ? text.replace(/[\s\n]+/g, " ").slice(0, 60) + : escHtml(text).replace(/<[^>]+>/g, " ").slice(0, 60); + return ( + '
' + + '
' + + '' + + '' + (isStreaming ? __("思考中...", "Thinking...") : __("思考", "Thinking")) + '' + + '
' + + '
' + + '
' + body + '
' + + (isStreaming ? '
' : "") + + '
' + ); + } + function toggleReasoning(el) { + var card = el.closest(".reasoning-card"); + if (!card) return; + var body = card.querySelector(".reasoning-body"); + if (!body) return; + var open = body.style.display !== "none"; + body.style.display = open ? "none" : "block"; + if (open) { + card.classList.remove("open"); + } else { + card.classList.add("open"); + } + } + function renderChatStarmap() { var cont = document.getElementById("sm-container-chat"); if (!cont) return;