diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 99181b7..3ef787a 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -353,6 +353,18 @@ function listWorkspaces(): { id: string; title: string; path: string }[] { } catch { return [] } } +/** 预览文本清洗:去掉围栏代码块(```…```,含 dsh-ui 等)与行内代码,压缩空白; + * 代码为主的片段会被滤空 → 调用方回退到更早的消息。只清洗显示用副本,不改原文。 */ +function cleanPreviewText(raw: string): string { + let s = String(raw ?? '') + s = s.replace(/```[a-zA-Z0-9_+-]*[\s\S]*?```/g, ' ') + s = s.replace(/```[a-zA-Z0-9_+-]*[\s\S]*$/g, ' ') + s = s.replace(/`[^`\n]{1,200}`/g, ' ') + s = s.replace(/```/g, ' ') + s = s.replace(/\s+/g, ' ').trim() + return s +} + /** 冷会话最近消息缓存与预热:宿主 history 只读通道(face.history 为运行期内建方法,非公开接口, * 只读侦察确认可用;拉取是带宽成本不是 Token 成本,模型不参与)。 */ const previewCache = new Map() @@ -360,20 +372,20 @@ const previewFetching = new Set() let previewSweepBusy = false let previewTimer: number | null = null -/** 从 history 事件流尾部提取最近一条成品消息文本(优先 text 块,回退任意文本) */ +/** 从 history 事件流尾部提取最近一条成品消息文本(优先 text 块;清洗代码后仍太短则回退更早消息) */ async function coldPreviewOf(face: any): Promise { if (!face || typeof face.history !== 'function') return '' - const r1 = await face.history({ maxMessages: 2 }) + const r1 = await face.history({ maxMessages: 6 }) const evs = r1?.result?.value?.events if (!Array.isArray(evs)) return '' const textOf = (blocks: any): string => { let fallback = '' if (!Array.isArray(blocks)) return '' for (const b of blocks) { - const s = typeof b?.text === 'string' ? b.text.trim().replace(/\s+/g, ' ') : '' + const s = typeof b?.text === 'string' ? b.text.trim() : '' if (!s) continue - if (b?.type === 'text') return s.slice(0, 220) - if (!fallback) fallback = s.slice(0, 220) + if (b?.type === 'text') return s + if (!fallback) fallback = s } return fallback } @@ -381,14 +393,12 @@ async function coldPreviewOf(face: any): Promise { const ev = evs[i]?.event if (!ev) continue const d = ev.data ?? {} - if (ev.type === 'user/message') { - const s = textOf(d.content ?? d.blocks) - if (s) return s - } else if (ev.type === 'assistant/message') { - const m = d.message ?? d - const s = textOf(m.content ?? m.blocks) - if (s) return s - } + let raw = '' + if (ev.type === 'user/message') raw = textOf(d.content ?? d.blocks) + else if (ev.type === 'assistant/message') { const m = d.message ?? d; raw = textOf(m.content ?? m.blocks) } + if (!raw) continue + const clean = cleanPreviewText(raw) + if (clean.length >= 8) return clean.slice(0, 220) } return '' } @@ -417,7 +427,7 @@ function schedulePreviewSweep() { previewTimer = window.setTimeout(() => { previewTimer = null; sweepPreviews() }, 6000) } -/** 从会话快照节点里提取最近一条文本(纯读内存镜像,零 Token;无则 '') */ +/** 从会话快照节点里提取最近一条文本(纯读内存镜像,零 Token;代码清洗后无则 '') */ function lastTextOf(sid: string): string { try { const face = sessionBridge?.sessions?.binding?.(sid)?.session?.getSnapshot?.() @@ -425,10 +435,16 @@ function lastTextOf(sid: string): string { for (let i = nodes.length - 1; i >= 0; i--) { const n = nodes[i] const blocks = Array.isArray(n?.blocks) ? n.blocks : (Array.isArray(n?.content) ? n.content : []) + let raw = '' for (const b of blocks) { - const s = typeof b?.text === 'string' ? b.text.trim().replace(/\s+/g, ' ') : '' - if (s) return s + const s = typeof b?.text === 'string' ? b.text.trim() : '' + if (!s) continue + if (b?.type === 'text') { raw = s; break } + if (!raw) raw = s } + if (!raw) continue + const clean = cleanPreviewText(raw) + if (clean.length >= 8) return clean.slice(0, 220) } } catch {} return '' diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index b5aa1ea..2df56e8 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -1054,7 +1054,6 @@ function ConsolePane() {
{c.icon} {c.name} -
diff --git a/01_content/src/client/styles.ts b/01_content/src/client/styles.ts index 7585681..3591895 100644 --- a/01_content/src/client/styles.ts +++ b/01_content/src/client/styles.ts @@ -391,10 +391,10 @@ export const css = xtermCss + '\n' + [ '.dsh-wt_consoleThemeBtn:hover{opacity:1;background:var(--wt-chip)}', '.dsh-wt_consoleThemeBtnOn{background:var(--wt-chip);color:var(--wt-text);opacity:1}', '.dsh-wt_consoleGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:64px;align-content:start;margin-top:clamp(32px,9vh,110px);max-width:856px;margin-left:auto;margin-right:auto}', - '.dsh-wt_consoleCard{position:relative;display:flex;flex-direction:column;gap:14px;aspect-ratio:1/1;min-height:192px;padding:21px;background:linear-gradient(135deg,rgba(255,255,255,.06) 0%,rgba(255,255,255,.015) 34%,rgba(0,0,0,.10) 100%),rgba(18,23,32,.32);backdrop-filter:blur(8px) saturate(1.25);-webkit-backdrop-filter:blur(8px) saturate(1.25);border:1px solid transparent;border-radius:16px;box-shadow:0 10px 28px rgba(0,0,0,.28),inset 0 1px 0 rgba(255,255,255,.07);cursor:pointer;transition:transform .14s ease,box-shadow .14s ease;overflow:hidden}', + '.dsh-wt_consoleCard{position:relative;display:flex;flex-direction:column;gap:14px;aspect-ratio:1/1;min-height:192px;padding:21px;background:linear-gradient(135deg,rgba(255,255,255,.05) 0%,rgba(255,255,255,.01) 34%,rgba(0,0,0,.04) 100%),rgba(18,23,32,.18);backdrop-filter:blur(4px) saturate(1.25);-webkit-backdrop-filter:blur(4px) saturate(1.25);border:1px solid transparent;border-radius:16px;box-shadow:0 10px 28px rgba(0,0,0,.28),inset 0 1px 0 rgba(255,255,255,.07);cursor:pointer;transition:transform .14s ease,box-shadow .14s ease;overflow:hidden}', // 玻璃边缘:四角黑白渐变描边(左上/右下=白,右上/左下=黑),1px 环挖空实现 '.dsh-wt_consoleCard::after{content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;background:conic-gradient(from 45deg,rgba(0,0,0,.4) 0deg,rgba(255,255,255,.55) 90deg,rgba(0,0,0,.4) 180deg,rgba(255,255,255,.55) 270deg,rgba(0,0,0,.4) 360deg);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;pointer-events:none;opacity:.5}', - '.dsh-wt_console[data-wt-theme=light] .dsh-wt_consoleCard{background:linear-gradient(135deg,rgba(255,255,255,.6) 0%,rgba(255,255,255,.22) 40%,rgba(31,41,55,.06) 100%),rgba(255,255,255,.42);box-shadow:0 10px 28px rgba(31,41,55,.18),inset 0 1px 0 rgba(255,255,255,1)}', + '.dsh-wt_console[data-wt-theme=light] .dsh-wt_consoleCard{background:linear-gradient(135deg,rgba(255,255,255,.45) 0%,rgba(255,255,255,.15) 40%,rgba(31,41,55,.04) 100%),rgba(255,255,255,.28);box-shadow:0 10px 28px rgba(31,41,55,.18),inset 0 1px 0 rgba(255,255,255,1)}', '.dsh-wt_console[data-wt-theme=light] .dsh-wt_consoleCard::after{background:conic-gradient(from 45deg,rgba(27,31,36,.4) 0deg,rgba(255,255,255,1) 90deg,rgba(27,31,36,.4) 180deg,rgba(255,255,255,1) 270deg,rgba(27,31,36,.4) 360deg);opacity:.9}', // 状态光效卡片:玻璃边缘让位给霓虹描边/旋转光环 '.dsh-wt_consoleCard-glowDone::after,.dsh-wt_consoleCard-glowNeed::after{opacity:0}', @@ -404,10 +404,6 @@ export const css = xtermCss + '\n' + [ '.dsh-wt_consoleIcon{flex:none;font-size:31px;line-height:1}', '.dsh-wt_consoleName{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:20px;font-weight:700;line-height:28px;color:var(--wt-text)}', '.dsh-wt_consoleDivider{flex:none;height:1px;background:var(--wt-border)}', - '.dsh-wt_consoleDot{flex:none;width:10px;height:10px;border-radius:50%;background:var(--wt-text3)}', - '.dsh-wt_consoleDot-busy{background:var(--dsw-alias-state-accent-primary,#4f8ef7);box-shadow:0 0 0 4px rgba(79,142,247,.16)}', - '.dsh-wt_consoleDot-need{background:var(--dsw-alias-state-warning,#d29922);box-shadow:0 0 0 4px rgba(210,153,34,.16)}', - '.dsh-wt_consoleDot-done{background:var(--dsw-alias-state-success,#3fb950);box-shadow:0 0 0 4px rgba(63,185,80,.16)}', '.dsh-wt_consoleStatusRow{display:flex;align-items:baseline;gap:9px;min-width:0}', '.dsh-wt_consoleStatus{flex:none;font-size:22px;font-weight:700;line-height:30px;color:var(--wt-text2)}', '.dsh-wt_consoleStatus-busy{color:var(--dsw-alias-state-accent-primary,#4f8ef7)}', diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md index 10a06a4..26a6e4a 100644 --- a/02_process/worklogs/2026-08-16_worktable-v2.md +++ b/02_process/worklogs/2026-08-16_worktable-v2.md @@ -1328,3 +1328,17 @@ - 验证(probe-batch16):cardBlur blur(8px) ✓、网格线 alpha .043 ✓;functional-diag 全 20 STEP ERROR_COUNT: 0。 - 备注:纯客户端改动,F5 即生效。 + +## 补记(控制室第八轮:更透玻璃 + 去圆点 + 预览过滤代码) + +- 用户反馈:① 还是不够透(怀疑内层光晕盖底)——继续加通透度;② 去掉卡片右上角小圆点 + (整卡光效已表达状态);③ 预览抓取的信息里混入隐藏代码(围栏/行内)显示很乱——抓取 + 时过滤代码,不改原文内容。 +- 实现:① 暗色底 rgba(18,23,32,.18)(.32→.18)、内层黑尾渐变 .10→.04、blur 8→4px; + 浅色底 .28、高光带 45%→15%;② ConsolePane 头删掉 .dsh-wt_consoleDot 及其全部样式; + ③ cleanPreviewText(去 ```围栏(含 dsh-ui)与行内代码、压缩空白)+ coldPreviewOf + 逐消息回退(maxMessages 2→6,清洗后不足 8 字符回退更早消息)+ lastTextOf 同样清洗。 +- 验证(probe-batch16):cardBgDark rgba(18,23,32,.18) / blur(4px) ✓、dotGone ✓、 + cleanSample「结论:完成了。 之后 结束」(围栏+行内代码全部滤除)✓、真实会话全代码消息 + 正确回退显示「暂无消息」✓;functional-diag 全 20 STEP ERROR_COUNT: 0。 +- 备注:纯客户端改动,F5 即生效。 diff --git a/04_test/probe-batch16.cjs b/04_test/probe-batch16.cjs index b7f6c66..57aee5e 100644 --- a/04_test/probe-batch16.cjs +++ b/04_test/probe-batch16.cjs @@ -145,7 +145,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); var c0 = cards[0]; out.firstCardSelf = !!(c0 && c0.classList.contains('dsh-wt_consoleCardSelf')); out.firstCardIcon = c0 ? c0.querySelector('.dsh-wt_consoleIcon').textContent : null; - out.firstCardDot = c0 && c0.querySelector('.dsh-wt_consoleDot') ? c0.querySelector('.dsh-wt_consoleDot').className.replace('dsh-wt_consoleDot','').trim() : null; + out.dotGone = !(c0 && c0.querySelector('.dsh-wt_consoleDot')); out.nameFont = c0 && c0.querySelector('.dsh-wt_consoleName') ? getComputedStyle(c0.querySelector('.dsh-wt_consoleName')).fontSize : null; out.dividerExists = !!(c0 && c0.querySelector('.dsh-wt_consoleDivider')); out.kidsGone = !(c0 && c0.querySelector('.dsh-wt_consoleBadge')); @@ -207,6 +207,15 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); await sleep(4200); var selfPrev2 = c0 && c0.querySelector('.dsh-wt_consolePreview'); out.selfPreviewAfterSweep = selfPrev2 ? selfPrev2.textContent.slice(0, 60) : null; + var BT = String.fromCharCode(96); + out.selfPreviewHasBacktick = selfPrev2 ? selfPrev2.textContent.indexOf(BT) >= 0 : null; + out.selfPreviewHasFence = selfPrev2 ? selfPrev2.textContent.indexOf(BT + BT + BT) >= 0 : null; + // 清洗正则逻辑断言(与插件 cleanPreviewText 同构) + var f1 = new RegExp(BT + '{3}[a-zA-Z0-9_+-]*[\\\\s\\\\S]*?' + BT + '{3}', 'g'); + var f2 = new RegExp(BT + '[^' + BT + '\\\\n]{1,200}' + BT, 'g'); + var sample = '结论:完成了。' + BT + BT + BT + 'dsh-ui\\n{\"json\":1}' + BT + BT + BT + ' 之后' + BT + 'inline' + BT + '结束'; + var cleaned = sample.replace(f1, ' ').replace(f2, ' ').replace(BT + '{3}', ' ').replace(/\\s+/g, ' ').trim(); + out.cleanSample = cleaned; out.selfPreviewFilled = !!(selfPrev2 && !selfPrev2.classList.contains('dsh-wt_consolePreviewNone')); var lcard = [].slice.call(cards).find(function(c){ return (c.textContent||'').indexOf('绑定测试') >= 0; }); if (lcard) { lcard.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true})); } diff --git a/AGENTS.md b/AGENTS.md index adc83b0..fbdc91e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,10 +83,10 @@ node --check lib/index.js 未绑定 → 强制绑定弹窗(左「加入现有对话」列表 / 右「新建对话」:分组 无/现有/新建, 建空会话 sessions.create 后自动绑定并打开控制室)。绑定也走 projects.v1.bindings。 - 控制室面板(split.tsx ConsolePane):卡片网格每行 3 张、超出换行;每卡 = 图标/名称/ - 三态圆点(need>done>busy>idle,不过滤 ack,永远显示事实状态)/运行时长(后台任务 - JobView.startedAt 或会话面 turnTimings 未结束轮次)/最近消息预览(会话面快照 nodes - 末条文本,纯读内存零 Token)。数据组装 = index.tsx getConsoleCards(env.console 注入), - 刷新走 consoleListeners(项目/会话快照变化推送)+ 面板每秒 tick。 + 状态大字与三色光效(need>done>busy>idle,不过滤 ack,永远显示事实状态)/运行时长 + (后台任务 JobView.startedAt 或会话面 turnTimings 未结束轮次)/最近消息预览。数据组装 + = index.tsx getConsoleCards(env.console 注入),刷新走 consoleListeners(项目/会话 + 快照变化推送)+ 面板每秒 tick。 - 卡片动作:点卡片 = 打开该项目(openSplit 或入驻项目切绑定对话);工作台自己的卡片 点击无操作。💬 跳转按钮已删除(用户定案无意义)。 - 主题:面板三选一开关(图标按钮 🌙/☀️/🖥️,title/aria 保留文字;存 view.v1 consoleTheme); @@ -105,18 +105,20 @@ node --check lib/index.js - 控制室标签不可关:PaneBody 对 content.type==='console' 的标签 locked(不渲染 ✕、 禁拖拽)——关掉会退化成窗格选择器,不可逆。 - 布局尺度:网格 gap 64px(4 倍间距)不变、max-width 856px 左右居中;卡片 1:1(面积 - 2×边长 1.4,实测 236px:名字 20/状态 22/预览 14 四行截断);标题下横向分隔线;无子代理 + 2×边长 1.4,实测 236px:名字 20/状态 22/预览 8.5 四行截断);标题下横向分隔线;无子代理 徽章、无 💬 跳转按钮;网格最后一位恒为「创建卡片」(虚线+)→ openAddPanel;入口卡无描边。 - - 背景:--wt-bg 深色 #0a0d13 + 30px 浅色井字格线(--wt-grid .028/.045,两条 - linear-gradient),工作台/蓝图质感。 + - 背景:--wt-bg 深色 #0a0d13 + 30px 浅色井字格线(--wt-grid .045/.07,两条 + linear-gradient),工作台/蓝图质感;玻璃贴片半透明底(暗 .18/浅 .28)+ backdrop blur 4px。 - 顶部标题克制:控制室页只保留侧栏入口卡一个「控制室」——分栏标题栏对 wt-console 不渲染 title(保留 ⇄/✕)、PaneBody singleConsole 不渲染标签栏、ConsolePane 头只留 主题开关(右对齐)。 - 分隔线:DIVIDER=4(分栏可拖分隔条更细)。 - - 冷会话消息预览(方案 A):binding() 对冷会话不载入文本;预热走 face.history({maxMessages:2}) + - 冷会话消息预览(方案 A):binding() 对冷会话不载入文本;预热走 face.history({maxMessages:6}) (运行期内建方法、非公开接口,只读无副作用)尾部扫 user/message 与 assistant/message 的 - text 块 → previewCache;sweepPreviews 在打开控制室时 + 控制室开着且会话快照变化防抖 6s - 触发;失败静默回退内存路径 lastTextOf。拉取是带宽成本不是 Token 成本。 + text 块 → cleanPreviewText(滤除 ```围栏与行内代码、压缩空白;不足 8 字符回退更早消息)→ + previewCache;sweepPreviews 在打开控制室时 + 控制室开着且会话快照变化防抖 6s 触发; + 失败静默回退内存路径 lastTextOf。拉取是带宽成本不是 Token 成本。 + - 状态指示:卡片右上角小圆点已删(整卡光效表达状态);状态计算不变。 ## 安装 / 重启