Streamline conversation activity UI
This commit is contained in:
parent
02b278b175
commit
bcdbdd4738
|
|
@ -5,6 +5,10 @@
|
|||
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
|
||||
> 工程口径的完整记录见 `PROGRESS.md` / git log。
|
||||
|
||||
## 0.60.11 — 2026-07-29
|
||||
|
||||
- 网页工作台进一步回归清爽白色界面:助手回复改为开放式文档流,工具调用与结果合并成可展开的紧凑活动项;同时修复语音输入停止后按钮仍显示“停止”的问题。
|
||||
|
||||
## 0.60.10 — 2026-07-29
|
||||
|
||||
- 网页工作台恢复清爽的中性白色配色,助手回复回到左侧阅读轴;手机端进一步缩小顶部页签、操作按钮和输入区,并移除默认的文件保存位置提示,为对话正文留出更多空间。
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-07-29(Web 工作台移动端空间优化,bump 0.60.10)
|
||||
最后更新:2026-07-29(Web 对话流去卡片化,bump 0.60.11)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
### 2026-07-29
|
||||
|
||||
- **07-29 / 0.60.11 / Web 对话流去卡片化 + 工具活动合并 + 语音状态修复**:工作台背景统一以纯白为主,浅灰仅保留在 hover、代码块等局部状态;助手历史/实时回复移除重复角色标签、大卡片边框、左色条与运行态红框,改为左锚开放文档流。历史态按 `tool_call_id` 将 assistant 调用与 tool result 合并,实时态复用同一 details 原位更新结果,统一为默认折叠单行活动项(执行中/成功/失败状态明确),产物 chip、媒体 banner 与后台进程活化继续保留;顶部 task progress dock 的位置和逻辑未改。另修短按语音转连续录音时直接覆写按钮 `textContent`、销毁 SVG/label 导致停止后无法恢复“语音”的问题,收口为 class/label/ARIA 状态函数。相关 Python 24 项 + Node 5 项及 JS 语法、diff 格式检查通过;无 API、schema、migration 或依赖变化。
|
||||
- **07-29 / 0.60.10 / Web 工作台移动端空间优化**:根据第二轮手机实图反馈把暖白/浅米灰改回中性白色,桌面助手回答、工具卡与任务进度恢复左侧阅读轴;手机端压缩顶栏、Tab、任务操作区、模型区、消息间距和 composer,高频操作键由 44px 收至 40px、输入框最低高度由 64px 收至 52px,并隐藏空闲态“请选择文件保存位置/就绪”占位行,运行、上传与错误等有效状态提示继续按需显示。相关 Python 21 项 + Node 5 项及 JS 语法、diff 格式检查通过;无 API、schema、migration 或依赖变化。
|
||||
- **07-29 / 0.60.9 / Web 工作台视觉复核修正(桌面 + 手机实图)**:据上线截图把冷蓝灰改为暖白/浅米灰;任务与文件行恢复明确分隔,助手消息收进居中 52rem 文档型回答块,工具与进度对齐同一阅读轨道;同时修掉 `task_progress`-only assistant 被过滤工具后仍留下空“助手”壳。手机端 composer 改为独立状态行 + 3 个 44px SVG 图标键 + 发送键,缩短 placeholder、加入安全区并压缩操作/模型区;录音与润色状态改只更新 label/ARIA,不再覆盖 SVG DOM。相关 Python 21 项 + Node 5 项及 JS 语法、HTML/SVG 结构、CSS 括号检查通过;无 API、schema、migration 或依赖变化。
|
||||
- **07-29 / 0.60.8 / Web 工作台 Visual Polish v1**:新增零构建 `web/static/ui.css` 作为内部轻量视觉基础层,集中颜色、间距、圆角、阴影与 focus/disabled 等控件状态;收敛三栏层级、任务/资源导航、对话正文与工具过程块、输入区、文件栏、modal/toast 及 ≤640px 触控尺寸,不改 DOM 契约、API 或业务交互。新增 CSS 文件接入、MIME 与 `no-cache` 回归测试;相关 Python 20 项 + Node 5 项通过,浏览器连接不可用故截图级视觉验收留真实页面复核。
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.60.10"
|
||||
__version__ = "0.60.11"
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ class StaticVendorTests(unittest.TestCase):
|
|||
self.assertIn('input.placeholder = mqPhone.matches', frontend)
|
||||
self.assertIn("#chat-hint:empty", css)
|
||||
self.assertNotIn("请选择文件保存位置", frontend)
|
||||
self.assertIn("function setMicButtonState(", frontend)
|
||||
self.assertNotIn('_micBtn.textContent = "■ 停止"', frontend)
|
||||
self.assertIn("const toolCallsById = new Map()", frontend)
|
||||
self.assertIn("const runningDet = finishRunningTool(ctx)", frontend)
|
||||
self.assertNotIn('<div class="role">科研助手</div>', frontend)
|
||||
self.assertIn(".tool-call.activity > summary", css)
|
||||
self.assertIn("#task-progress-dock .task-progress", css)
|
||||
|
||||
def test_frontend_surfaces_context_and_cache_stats(self) -> None:
|
||||
src = _frontend_source()
|
||||
|
|
|
|||
|
|
@ -1267,16 +1267,24 @@ function tickRunStatus(run) {
|
|||
if (run.toolElapsedEl && suffix) run.toolElapsedEl.textContent = suffix;
|
||||
}
|
||||
|
||||
function isToolResultFailure(text) {
|
||||
const value = String(text || "").trim();
|
||||
return /^(?:\[ERR(?:OR)?\]|\[Error|Error executing|Traceback \(|Command failed)/i.test(value)
|
||||
|| /\bexit code\s+[1-9]\d*\b/i.test(value);
|
||||
}
|
||||
|
||||
// 工具执行收尾:去 spinner,elapsed 定格(留着当执行耗时看)
|
||||
function finishRunningTool(run) {
|
||||
if (run.runningToolEl) { run.runningToolEl.classList.remove("running"); run.runningToolEl = null; }
|
||||
const el = run.runningToolEl;
|
||||
if (el) el.classList.remove("running");
|
||||
run.runningToolEl = null;
|
||||
run.toolElapsedEl = null;
|
||||
return el;
|
||||
}
|
||||
|
||||
function createLiveAssistantCard(run) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "msg assistant live-run";
|
||||
card.innerHTML = `<div class="role">科研助手</div>`;
|
||||
run.card = card;
|
||||
run.curSeg = null;
|
||||
if (run.acc) {
|
||||
|
|
@ -1458,6 +1466,33 @@ function renderMessages(msgs, { stickBottom = true } = {}) {
|
|||
// chip 去重:同一路径在 tool 结果里挂过 inline 图后,assistant 正文 echo 同路径不再重挂。
|
||||
// chronological 遍历,首次出现保留(tool 结果常在前),后续重复过滤掉。
|
||||
const seenRels = new Set();
|
||||
// 历史态把 assistant tool_call 与紧随其后的 tool result 合成一条活动项。
|
||||
// tool result 才是最终可追溯记录;已得到结果的 call 不再额外渲一条“准备调用”,避免双行噪声。
|
||||
const toolCallsById = new Map();
|
||||
const resolvedToolCallIds = new Set();
|
||||
for (const item of msgs) {
|
||||
const payload = item.payload || {};
|
||||
if (payload.role === "assistant" && Array.isArray(payload.tool_calls)) {
|
||||
for (const tc of payload.tool_calls) {
|
||||
const fn = (tc.function && tc.function.name) || "?";
|
||||
let argsObj = {};
|
||||
let args = "";
|
||||
try {
|
||||
argsObj = JSON.parse((tc.function && tc.function.arguments) || "{}");
|
||||
args = JSON.stringify(argsObj, null, 2);
|
||||
} catch (e) {
|
||||
args = (tc.function && tc.function.arguments) || "";
|
||||
}
|
||||
if (tc.id) toolCallsById.set(tc.id, {
|
||||
fn,
|
||||
args,
|
||||
label: toolActivityLabel(fn, argsObj),
|
||||
});
|
||||
}
|
||||
} else if (payload.role === "tool" && payload.tool_call_id) {
|
||||
resolvedToolCallIds.add(payload.tool_call_id);
|
||||
}
|
||||
}
|
||||
let currentProgressSteps = [];
|
||||
const pickFresh = (rels) => {
|
||||
const fresh = [];
|
||||
|
|
@ -1492,13 +1527,18 @@ function renderMessages(msgs, { stickBottom = true } = {}) {
|
|||
const txt = typeof p.content === "string" ? p.content : JSON.stringify(p.content);
|
||||
const wd = _workingDirName(state.taskMeta && state.taskMeta.working_dir);
|
||||
const banner = extractMediaBanner(p.name || "", txt || "");
|
||||
const callMeta = toolCallsById.get(p.tool_call_id);
|
||||
const activityLabel = callMeta ? callMeta.label : `已执行 ${p.name || "工具"}`;
|
||||
const failed = isToolResultFailure(txt);
|
||||
// 工具结果只有产物工具(seedream/seedance)挂 chip + inline 大图;通用工具
|
||||
// (grep/read/glob/shell)echo 的路径是"引用"不是"产物",不挂以免噪声。
|
||||
const isProducer = ARTIFACT_PRODUCING_TOOLS.has(p.name || "");
|
||||
const rels = isProducer ? pickFresh(extractArtifactRels(txt || "", wd)) : [];
|
||||
card.innerHTML = `
|
||||
<div class="role">工具调用 · ${escapeHtml(p.name || "")}</div>
|
||||
<details class="tool-call"><summary>结果(${(txt || "").length} 字符)${banner}</summary><pre>${escapeHtml(txt || "")}</pre></details>
|
||||
<details class="tool-call activity${failed ? " failed" : ""}">
|
||||
<summary><span class="tool-state" aria-hidden="true">${failed ? "!" : "✓"}</span><span class="tool-label">${escapeHtml(activityLabel)}</span><span class="tool-result-meta">${(txt || "").length} 字符</span>${banner}</summary>
|
||||
<pre>${escapeHtml(txt || "")}</pre>
|
||||
</details>
|
||||
${renderArtifactBarHtml(rels, isProducer)}
|
||||
`;
|
||||
// bg proc 启动结果 → 卡片活化(spinner/跳秒/停止,与直播态同构;真实状态
|
||||
|
|
@ -1510,8 +1550,8 @@ function renderMessages(msgs, { stickBottom = true } = {}) {
|
|||
const card = document.createElement("div");
|
||||
card.className = "msg " + role;
|
||||
card.dataset.idx = m.idx;
|
||||
const roleLabel = { user: "我", assistant: "科研助手", error: "错误" }[role] || role;
|
||||
let html = `<div class="role">${roleLabel}</div>`;
|
||||
const roleLabel = { user: "我", error: "错误" }[role] || role;
|
||||
let html = role === "assistant" ? "" : `<div class="role">${roleLabel}</div>`;
|
||||
let hasVisibleAssistantContent = false;
|
||||
// 思考过程(thinking 模型持久化的 reasoning_content):默认折叠,与直播态同构
|
||||
if (role === "assistant") {
|
||||
|
|
@ -1562,11 +1602,12 @@ function renderMessages(msgs, { stickBottom = true } = {}) {
|
|||
hasVisibleAssistantContent = true;
|
||||
continue;
|
||||
}
|
||||
if (tc.id && resolvedToolCallIds.has(tc.id)) continue;
|
||||
const label = toolActivityLabel(fn, argsObj);
|
||||
const isProducer = ARTIFACT_PRODUCING_TOOLS.has(fn);
|
||||
const rels = isProducer ? pickFresh(extractArtifactRels(args, wd)) : [];
|
||||
html += `
|
||||
<details class="tool-call"><summary>${escapeHtml(label)}</summary><pre>${escapeHtml(args)}</pre></details>
|
||||
<details class="tool-call activity"><summary><span class="tool-state" aria-hidden="true">…</span><span class="tool-label">${escapeHtml(label)}</span></summary><pre>${escapeHtml(args)}</pre></details>
|
||||
${renderArtifactBarHtml(rels, isProducer)}
|
||||
`;
|
||||
hasVisibleAssistantContent = true;
|
||||
|
|
@ -1951,6 +1992,17 @@ let _voice = null; // 录音态:{stream,ctx,source,node,chunks,levels,rate,t0,t
|
|||
let _holdReleased = false; // 手指在 getUserMedia 授权完成前已松开 → 启动完成后立即收尾
|
||||
let _suppressClickUntil = 0; // touch 序列结束后浏览器补发的合成 click,不当 toggle 用
|
||||
|
||||
// 只更新状态 class / label / ARIA,绝不改 button.textContent:
|
||||
// 按钮内含 SVG,直接写 textContent 会销毁图标和 .composer-label,随后停止录音也无法恢复文案。
|
||||
function setMicButtonState(recording, stoppable = false) {
|
||||
const btn = $("chat-mic");
|
||||
if (!btn) return;
|
||||
btn.classList.toggle("recording", recording);
|
||||
const label = btn.querySelector(".composer-label");
|
||||
if (label) label.textContent = stoppable ? "停止" : "语音";
|
||||
btn.setAttribute("aria-label", stoppable ? "停止录音" : "语音输入");
|
||||
}
|
||||
|
||||
// 线性插值降采样到 16k 并量化 Int16 小端(讯飞只收 16k/16bit/mono raw)
|
||||
function _pcm16kFromChunks(chunks, srcRate) {
|
||||
let total = 0;
|
||||
|
|
@ -2021,7 +2073,6 @@ function _drawWave() {
|
|||
|
||||
async function startVoiceInput(mode, startY) {
|
||||
if (_voice) return;
|
||||
const btn = $("chat-mic");
|
||||
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
|
||||
$("chat-hint").textContent = "此环境不支持录音(需 HTTPS 或 localhost 访问)";
|
||||
return;
|
||||
|
|
@ -2092,12 +2143,7 @@ async function startVoiceInput(mode, startY) {
|
|||
cv.width = Math.max(1, cv.clientWidth * dpr); // 面板刚显示,读 clientWidth 强制同步布局
|
||||
cv.height = Math.max(1, cv.clientHeight * dpr);
|
||||
_vrHint(mode === "hold" ? "松开转写 · 上滑取消" : "说完按 Enter 结束 · Esc 取消");
|
||||
btn.classList.add("recording");
|
||||
if (mode === "click") {
|
||||
const label = btn.querySelector(".composer-label");
|
||||
if (label) label.textContent = "停止";
|
||||
btn.setAttribute("aria-label", "停止录音");
|
||||
}
|
||||
setMicButtonState(true, mode === "click");
|
||||
const tick = () => {
|
||||
if (!_voice) return;
|
||||
const sec = Math.floor((Date.now() - _voice.t0) / 1000);
|
||||
|
|
@ -2113,9 +2159,7 @@ async function startVoiceInput(mode, startY) {
|
|||
if (mode === "hold" && _holdReleased) {
|
||||
if (Date.now() - _pressT0 < HOLD_TOGGLE_MS && !_voice.cancelArmed) {
|
||||
_voice.mode = "click";
|
||||
const label = btn.querySelector(".composer-label");
|
||||
if (label) label.textContent = "停止";
|
||||
btn.setAttribute("aria-label", "停止录音");
|
||||
setMicButtonState(true, true);
|
||||
_vrHint("说完按 Enter 结束 · Esc 取消");
|
||||
} else {
|
||||
stopVoiceInput(_voice.cancelArmed);
|
||||
|
|
@ -2129,11 +2173,7 @@ async function stopVoiceInput(discard) {
|
|||
_voice = null;
|
||||
clearTimeout(v.timer);
|
||||
cancelAnimationFrame(v.raf);
|
||||
const btn = $("chat-mic");
|
||||
btn.classList.remove("recording");
|
||||
const label = btn.querySelector(".composer-label");
|
||||
if (label) label.textContent = "语音";
|
||||
btn.setAttribute("aria-label", "语音输入");
|
||||
setMicButtonState(false);
|
||||
$("chat-form").classList.remove("recording");
|
||||
$("voice-rec-panel").classList.remove("cancel-armed");
|
||||
$("vr-time").textContent = "0:00";
|
||||
|
|
@ -2226,7 +2266,7 @@ const _holdEnd = (e) => {
|
|||
if (e.type !== "pointercancel" && !_voice.cancelArmed && Date.now() - _pressT0 < HOLD_TOGGLE_MS) {
|
||||
// 短按 → 连续录音(免持):说完 Enter / 再点一下结束
|
||||
_voice.mode = "click";
|
||||
_micBtn.textContent = "■ 停止";
|
||||
setMicButtonState(true, true);
|
||||
_vrHint("说完按 Enter 结束 · Esc 取消");
|
||||
return;
|
||||
}
|
||||
|
|
@ -2419,7 +2459,7 @@ async function sendMessage(overrideText) {
|
|||
// assistant 流式占位卡
|
||||
const asstCard = document.createElement("div");
|
||||
asstCard.className = "msg assistant live-run";
|
||||
asstCard.innerHTML = `<div class="role">科研助手</div><div class="body streaming"></div>`;
|
||||
asstCard.innerHTML = `<div class="body streaming"></div>`;
|
||||
wrap.appendChild(asstCard);
|
||||
wrap.scrollTop = wrap.scrollHeight;
|
||||
|
||||
|
|
@ -2745,8 +2785,8 @@ function handleSseEvent(ev, asstCard, ctx) {
|
|||
}
|
||||
const label = toolActivityLabel(fn, args);
|
||||
const det = document.createElement("details");
|
||||
det.className = "tool-call running";
|
||||
det.innerHTML = `<summary>${escapeHtml(label)}<span class="tool-elapsed"></span></summary><pre>${escapeHtml(argsStr)}</pre>`;
|
||||
det.className = "tool-call activity running";
|
||||
det.innerHTML = `<summary><span class="tool-state" aria-hidden="true">…</span><span class="tool-label">${escapeHtml(label)}</span><span class="tool-elapsed"></span></summary><pre>${escapeHtml(argsStr)}</pre>`;
|
||||
asstCard.appendChild(det);
|
||||
// 运行态:spinner + 跳秒,tool_result 到达时定格(区分"在执行"和"卡死")
|
||||
ctx.runningToolEl = det;
|
||||
|
|
@ -2769,13 +2809,24 @@ function handleSseEvent(ev, asstCard, ctx) {
|
|||
const toolName = (ev.data && ev.data.name) || "";
|
||||
if (toolName === "task_progress") return;
|
||||
if (toolName === "ask_user") return; // 选项卡已在 tool_call 阶段渲染,结果是占位不展示
|
||||
finishRunningTool(ctx); // 去 spinner,elapsed 定格
|
||||
const runningDet = finishRunningTool(ctx); // 去 spinner,elapsed 定格
|
||||
closeTextSeg(ctx); // 结果卡追加到当前文字段之下,之后新文字另起底部段
|
||||
const banner = extractMediaBanner(toolName, txtStr);
|
||||
const det = document.createElement("details");
|
||||
det.className = "tool-call";
|
||||
det.innerHTML = `<summary>工具结果${banner}</summary><pre>${escapeHtml(txtStr)}</pre>`;
|
||||
asstCard.appendChild(det);
|
||||
const failed = isToolResultFailure(txtStr);
|
||||
const det = runningDet || document.createElement("details");
|
||||
det.classList.add("tool-call", "activity");
|
||||
det.classList.toggle("failed", failed);
|
||||
if (!runningDet) {
|
||||
det.innerHTML = `<summary><span class="tool-state" aria-hidden="true">${failed ? "!" : "✓"}</span><span class="tool-label">${escapeHtml(toolName ? `已执行 ${toolName}` : "工具执行完成")}</span></summary><pre></pre>`;
|
||||
asstCard.appendChild(det);
|
||||
} else {
|
||||
const stateEl = det.querySelector(".tool-state");
|
||||
if (stateEl) stateEl.textContent = failed ? "!" : "✓";
|
||||
}
|
||||
const pre = det.querySelector("pre");
|
||||
if (pre) pre.textContent = txtStr;
|
||||
const summary = det.querySelector("summary");
|
||||
if (summary && banner) summary.insertAdjacentHTML("beforeend", banner);
|
||||
const wd = _workingDirName(ctx.workingDir);
|
||||
const isProducer = ARTIFACT_PRODUCING_TOOLS.has(toolName);
|
||||
const fresh = isProducer
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@
|
|||
color-scheme: light;
|
||||
|
||||
/* Neutral workspace palette */
|
||||
--bg: #f7f7f7;
|
||||
--bg: #ffffff;
|
||||
--panel: #ffffff;
|
||||
--panel-subtle: #fafafa;
|
||||
--panel-muted: #f5f5f5;
|
||||
--panel-subtle: #fcfcfc;
|
||||
--panel-muted: #f8f8f8;
|
||||
--border: #e1e3e5;
|
||||
--border-soft: #eceeef;
|
||||
--list-divider: #d9dde0;
|
||||
|
|
@ -28,9 +28,9 @@
|
|||
--accent-soft: #fbecea;
|
||||
--accent-ring: rgba(184, 58, 49, 0.16);
|
||||
|
||||
--hover: #f2f3f4;
|
||||
--code-bg: #f5f5f5;
|
||||
--user-bg: #f2f6fa;
|
||||
--hover: #f7f7f7;
|
||||
--code-bg: #f7f7f7;
|
||||
--user-bg: #f6f8fa;
|
||||
--asst-bg: #ffffff;
|
||||
|
||||
--c-green: #238653;
|
||||
|
|
@ -191,7 +191,7 @@ header .who {
|
|||
|
||||
#pane-left,
|
||||
#pane-right {
|
||||
background: var(--panel-subtle);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
#pane-mid {
|
||||
|
|
@ -209,7 +209,7 @@ header .who {
|
|||
.pane-head {
|
||||
min-height: 44px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(248, 249, 251, 0.96);
|
||||
background: var(--panel);
|
||||
border-bottom-color: var(--border-soft);
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +254,7 @@ header .who {
|
|||
}
|
||||
|
||||
.task-row:hover {
|
||||
background: var(--panel);
|
||||
background: var(--hover);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
@ -370,22 +370,19 @@ header .who {
|
|||
width: min(100%, 52rem);
|
||||
max-width: min(100%, 52rem);
|
||||
align-self: flex-start;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-left: 3px solid #cbd0d4;
|
||||
border-radius: var(--r-lg);
|
||||
background: #ffffff;
|
||||
padding: 3px 0 18px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.msg.assistant.live-run {
|
||||
padding: 16px 18px;
|
||||
border-color: rgba(184, 58, 49, 0.2);
|
||||
border-left-color: var(--accent);
|
||||
border-left-width: 3px;
|
||||
border-radius: var(--r-lg);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(184, 58, 49, 0.05),
|
||||
0 8px 24px rgba(184, 58, 49, 0.06);
|
||||
padding: 3px 0 18px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.msg .role {
|
||||
|
|
@ -398,33 +395,20 @@ header .who {
|
|||
}
|
||||
|
||||
.msg.assistant > .role {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #656b70;
|
||||
}
|
||||
|
||||
.msg.assistant > .role::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
flex: none;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-soft);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.msg.tool {
|
||||
width: min(100%, 52rem);
|
||||
max-width: min(100%, 52rem);
|
||||
align-self: flex-start;
|
||||
padding: 0 18px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.msg.tool > .role {
|
||||
margin-bottom: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#task-progress-dock .task-progress {
|
||||
|
|
@ -459,7 +443,6 @@ header .who {
|
|||
color: #555b60;
|
||||
}
|
||||
|
||||
.tool-call summary,
|
||||
.reasoning summary {
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--border-soft);
|
||||
|
|
@ -467,11 +450,88 @@ header .who {
|
|||
background: var(--panel-muted);
|
||||
}
|
||||
|
||||
.tool-call summary:hover,
|
||||
.reasoning summary:hover {
|
||||
background: var(--hover);
|
||||
}
|
||||
|
||||
.tool-call.activity {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.tool-call.activity > summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
gap: 7px;
|
||||
padding: 3px 6px;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
border-radius: var(--r-md);
|
||||
background: transparent;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tool-call.activity > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-call.activity > summary::after {
|
||||
content: "›";
|
||||
flex: none;
|
||||
margin-left: 2px;
|
||||
color: var(--muted);
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tool-call.activity[open] > summary::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.tool-call.activity > summary:hover,
|
||||
.tool-call.activity[open] > summary {
|
||||
color: var(--text);
|
||||
background: var(--hover);
|
||||
}
|
||||
|
||||
.tool-call.activity .tool-state {
|
||||
width: 16px;
|
||||
flex: none;
|
||||
color: var(--c-green);
|
||||
font-family: var(--mono);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tool-call.activity.running .tool-state {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.tool-call.activity.failed > summary,
|
||||
.tool-call.activity.failed .tool-state {
|
||||
color: var(--c-red);
|
||||
}
|
||||
|
||||
.tool-call.activity .tool-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-call.activity .tool-result-meta {
|
||||
margin-left: auto;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-call.activity > pre {
|
||||
margin: 3px 0 5px 23px;
|
||||
}
|
||||
|
||||
.tool-call pre {
|
||||
border: 1px solid var(--border-soft);
|
||||
background: var(--code-bg);
|
||||
|
|
@ -538,7 +598,7 @@ header .who {
|
|||
}
|
||||
|
||||
.file-row:hover {
|
||||
background: var(--panel);
|
||||
background: var(--hover);
|
||||
}
|
||||
|
||||
.file-row .name {
|
||||
|
|
@ -697,13 +757,13 @@ header .who {
|
|||
.msg.assistant {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 10px 12px;
|
||||
padding: 2px 0 14px;
|
||||
}
|
||||
|
||||
.msg.tool {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.msg .body {
|
||||
|
|
|
|||
Loading…
Reference in New Issue