From 86ff42e8ca9482fec28c67d70a70018fcf854973 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Thu, 6 Aug 2026 07:08:24 +0800 Subject: [PATCH] =?UTF-8?q?emrg:=20GUI=20=E9=87=8D=E8=AE=BE=E8=AE=A1=20P2?= =?UTF-8?q?=20=E2=80=94=20=E5=B7=A5=E5=85=B7=E8=BF=87=E7=A8=8B=E5=8F=8B?= =?UTF-8?q?=E5=A5=BD=E5=8C=96=20+=20=E2=9C=A6=20=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=A0=87=E8=AF=86=20(#416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 docs/design/gui-redesign.md §3.3/§4.2/§5.1 实施 P2(对话流畅漂亮): - js/copywriting.js(新):tool_name → 友好动词短语映射(doing/done/fail 三态, 零黑话:'正在读取文件…'/'已读取文件'/'这一步没成功,我换个方法试试') - app.js handleToolStart:改为友好状态行(琥珀色转动圆点 + 动词短语) - app.js handleToolEnd:✓/⚠ 图标 + 友好文案 + 耗时弱化;原始输出默认折叠, 点「展开 ⌄」渐进披露(保留 2000 字符截断 + 展开全文);失败也鼓励性措辞 - app.js disconnected:工具中断行改友好文案(连接中断,结果未知) - css/components.css:.tool-spin 转动动画、工具行 flex 布局、✦ 前缀 (.msg.assistant::before,remote 除外) - index.html:加载 copywriting.js 边界:preload/daemon_client 零改动,CSP 不变;npm test 29 绿 + 472 Python tests 通过 --- emrg/gui/renderer/app.js | 26 +++++++++++++---- emrg/gui/renderer/css/components.css | 43 ++++++++++++++++++++++++---- emrg/gui/renderer/index.html | 1 + emrg/gui/renderer/js/copywriting.js | 22 ++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 emrg/gui/renderer/js/copywriting.js diff --git a/emrg/gui/renderer/app.js b/emrg/gui/renderer/app.js index 6343b313..32e6fc24 100644 --- a/emrg/gui/renderer/app.js +++ b/emrg/gui/renderer/app.js @@ -471,8 +471,7 @@ async function handleEvent(evt) { if (card.classList.contains("running")) { card.classList.remove("running"); card.classList.add("failed"); - const st = card.querySelector(".tool-status"); - if (st) st.textContent = "结果未知——连接中断"; + card.innerHTML = '连接中断,结果未知'; } } break; @@ -570,9 +569,11 @@ function handleToolStart(data) { appendMsg(node); state.groupNodes.set(rid, node); } + // P2:友好状态行(设计 §4.2)——琥珀色转动圆点 + 动词短语,无技术黑话 const card = document.createElement("div"); card.className = "tool-card running"; - card.innerHTML = `🔧 ${escapeHtml(data.tool_name)} 运行中…`; + const phrase = toolPhrase(data.tool_name, "doing"); + card.innerHTML = `${escapeHtml(phrase)}`; appendMsg(card); state.toolCards.set(data.tool_call_id, card); } @@ -582,14 +583,21 @@ function handleToolEnd(data) { if (!card) return; const ok = !data.error; card.className = "tool-card " + (ok ? "done" : "failed"); + const phrase = toolPhrase(data.tool_name, ok ? "done" : "fail"); const elapsed = data.elapsed !== undefined ? `${data.elapsed.toFixed(1)}s` : ""; - card.querySelector(".tool-status").textContent = ok ? `完成 ${elapsed}` : `失败 ${elapsed}`; - // G91/G131:content 默认截断 2000 字符 + 展开(textContent 纯文本,不做 marked) const content = data.content || ""; + const toggle = content ? '展开 ⌄' : ""; + card.innerHTML = + `${ok ? "✓" : "⚠"}` + + `${escapeHtml(phrase)}` + + (elapsed ? `${elapsed}` : "") + + toggle; + // 原始输出默认折叠(渐进披露:点「展开」才看;G91/G131 保留 2000 字符截断) if (content) { const truncated = content.length > 2000 ? content.slice(0, 2000) + "…" : content; const body = document.createElement("div"); body.className = "tool-output"; + body.style.display = "none"; body.textContent = truncated; card.appendChild(body); if (content.length > 2000) { @@ -602,6 +610,14 @@ function handleToolEnd(data) { }); card.appendChild(btn); } + const tg = card.querySelector(".tool-toggle"); + if (tg) { + tg.addEventListener("click", () => { + const hidden = body.style.display === "none"; + body.style.display = hidden ? "block" : "none"; + tg.textContent = hidden ? "收起 ⌃" : "展开 ⌄"; + }); + } } scrollToBottom(); } diff --git a/emrg/gui/renderer/css/components.css b/emrg/gui/renderer/css/components.css index fd8dd24b..5b1a89fe 100644 --- a/emrg/gui/renderer/css/components.css +++ b/emrg/gui/renderer/css/components.css @@ -48,7 +48,7 @@ color: var(--text-1); border-radius: var(--radius-bubble) var(--radius-bubble) 6px var(--radius-bubble); } -/* EMRG:全宽无气泡,行首 ✦ 标识(P2 chat.js 渲染时加 ✦,P1 保留全宽样式) */ +/* EMRG:全宽无气泡,行首 ✦ 品牌标识(设计 §3.3;remote 有标签不加) */ .msg.assistant { align-self: flex-start; max-width: 100%; @@ -59,7 +59,9 @@ border-radius: 0; white-space: normal; } +.msg.assistant::before { content: "✦ "; color: var(--accent); font-weight: 600; } .msg.assistant.remote { border-left: 3px solid var(--amber); padding-left: var(--space-3); } +.msg.assistant.remote::before { content: none; } /* 系统提示:居中弱化小字 */ .msg.system { align-self: center; @@ -73,7 +75,7 @@ .remote-label { font-size: 11px; color: var(--amber); margin-bottom: var(--space-1); } .msg-body { color: var(--text-1); } -/* ── 工具过程(P2 改折叠友好行;P1 保留卡片样式保证功能)── */ +/* ── 工具过程:友好状态行(设计 §4.2 重点——折叠式,渐进披露)── */ .tool-card { align-self: flex-start; min-width: 320px; @@ -86,14 +88,43 @@ font-size: var(--text-secondary); margin: 2px 0; box-shadow: var(--shadow-sm); + display: flex; + align-items: center; + gap: var(--space-2); + flex-wrap: wrap; } .tool-card.running { border-left-color: var(--amber); } .tool-card.done { border-left-color: var(--accent); } .tool-card.failed { border-left-color: var(--red); } -.tool-icon { } -.tool-name { font-family: var(--font-mono); font-weight: 600; } -.tool-status { color: var(--text-2); margin-left: var(--space-2); font-size: var(--text-aux); } +/* 进行中:琥珀色温和转动圆点(不刺眼) */ +.tool-spin { + width: 12px; + height: 12px; + border-radius: 50%; + border: 2px solid var(--amber); + border-top-color: transparent; + animation: tool-spin 0.9s linear infinite; + flex-shrink: 0; +} +@keyframes tool-spin { to { transform: rotate(360deg); } } +/* 完成/失败图标 */ +.tool-icon { flex-shrink: 0; } +.tool-card.done .tool-icon { color: var(--accent); font-weight: 600; } +.tool-card.failed .tool-icon { color: var(--red); font-weight: 600; } +.tool-text { flex-shrink: 0; } +.tool-detail { color: var(--text-3); font-size: var(--text-aux); } +.tool-toggle { + color: var(--blue); + cursor: pointer; + font-size: var(--text-aux); + margin-left: auto; + flex-shrink: 0; + padding: 2px 4px; + border-radius: 6px; +} +.tool-toggle:hover { background: var(--bg-soft); } .tool-output { + flex-basis: 100%; margin-top: var(--space-2); background: var(--bg-soft); border-radius: var(--radius-input); @@ -106,6 +137,7 @@ word-break: break-all; } .tool-expand { + flex-basis: 100%; margin-top: var(--space-1); background: none; border: none; @@ -113,6 +145,7 @@ cursor: pointer; font-size: var(--text-aux); padding: 0; + text-align: left; } .tool-expand:hover { text-decoration: underline; } diff --git a/emrg/gui/renderer/index.html b/emrg/gui/renderer/index.html index b7189f82..9b908f00 100644 --- a/emrg/gui/renderer/index.html +++ b/emrg/gui/renderer/index.html @@ -115,6 +115,7 @@

欢迎使用 EMRG

+ diff --git a/emrg/gui/renderer/js/copywriting.js b/emrg/gui/renderer/js/copywriting.js new file mode 100644 index 00000000..e204f61b --- /dev/null +++ b/emrg/gui/renderer/js/copywriting.js @@ -0,0 +1,22 @@ +"use strict"; +/** + * copywriting.js — 友好文案映射(docs/design/gui-redesign.md §4.2 工具过程 / §5.1 文案规范) + * 按 tool_name 映射动词短语(面向非开发者,零技术黑话)。 + * 状态三态:doing(进行中)/ done(完成)/ fail(失败——统一鼓励性措辞,不责怪用户)。 + */ +const TOOL_PHRASES = { + bash: { doing: "正在运行命令…", done: "命令已执行", fail: "这一步没成功,我换个方法试试" }, + read: { doing: "正在读取文件…", done: "已读取文件", fail: "这一步没成功,我换个方法试试" }, + write: { doing: "正在写入文件…", done: "文件已保存", fail: "这一步没成功,我换个方法试试" }, + edit: { doing: "正在修改文件…", done: "文件已修改", fail: "这一步没成功,我换个方法试试" }, + glob: { doing: "正在查找文件…", done: "文件查找完成", fail: "这一步没成功,我换个方法试试" }, + grep: { doing: "正在搜索内容…", done: "搜索完成", fail: "这一步没成功,我换个方法试试" }, + browser: { doing: "正在浏览网页…", done: "网页操作完成", fail: "这一步没成功,我换个方法试试" }, + default: { doing: "正在处理…", done: "处理完成", fail: "这一步没成功,我换个方法试试" }, +}; + +/** 取工具友好短语:toolPhrase(toolName, "doing" | "done" | "fail") */ +function toolPhrase(toolName, kind) { + const p = TOOL_PHRASES[toolName] || TOOL_PHRASES.default; + return p[kind] || TOOL_PHRASES.default[kind]; +}