dsh-chat-gutter(聊天区双侧标识带 + 快速移动面板)
DSH 桌面端 / Web 聊天区两侧空白带上的标记,加上输入区一个两列跳转面板。纯客户端,只 require('react'),不依赖桌宠、图片素材或任何第三方模块。
四条标记
- 回合编号路标(右侧空白带):每个回合一枚
#1#2…(纯文字),锚定用户自己发的那条消息,细竖线串成时间线,随滚动实时跟随。 - 过程徽标(左侧空白带):按回合聚合成一枚,文案
⚙️工具数 ⌨️命令数 🧐思考数,没有的类不写;锚点优先官方回合汇总条,再退过程组头,最后退回该回合第一个工具行。 另有🤔提问(等待作答的提问卡)与📦上下文压缩;答过的提问在过程组的「向用户提出了问题」那一行留一枚🤔。 - 回答段弧线(左侧):AI 干完活后的整段正文,左侧画一条花瓣形弧线——中段最厚、上下收尖、外缘向左深鼓,颜色取
--dsw-specific-accent。弧线长度等于段落真实长度(两端各外延 4px),滚动时只跟着内容上下移动,长度与弧度不变。 ▶答徽标:钉在弧线的真中点(段落垂直中心),随内容移动;中点滚出正文可视区时贴到可见部分的边缘,整段都出窗时与弧线一起消失。
快速移动面板
输入区左侧「快速移动」按钮,按钮正上方弹出两列面板。三种打开方式:
- 鼠标移上去:停约 0.12 秒自动弹出(扫过不停留就不弹);移开按钮和面板约 0.35 秒后自动收起——这 0.35 秒正好够把鼠标从按钮移进面板。
- 左键点击:点开、再点收起。点开的这一份钉住,鼠标移开也不收。
- 右键:直接打开并钉住。
点过面板里的分类或条目之后,面板同样钉住,直到点面板外、按 Esc、或再点一次按钮才关。触屏设备没有悬停,照旧点一下打开。
- 左列:
🤔 AI 提问、⚙️ 工具、▶️ AI 回答、💬 你的消息,每类后面跟这一类的条数;空类别整行变淡,点开右列写「这一类还没有」。 - 右列:这一类里的编号
1 · 摘要,摘要取 20 个字。过程组的摘要跳过组头那句「已调用工具/已读取文件/已写入文件等」,直接取组里第一行的内容;路径类文本保留末尾,同一个目录下只差文件名的两条也能分清。点编号就跳到那一处并收起面板;点面板外、按 Esc、再点一次按钮都能关。条目多了右列自己滚动,行不会被压扁。 - 底部:
⬆️ 加载更早。官方聊天区顶部还有更早的历史时出现,点它等于点官方那个按钮,补一页后收起面板;没有更早的历史时整条不显示,官方正在加载时同步显示「加载中…」并禁用。
面板是挂在 document.body 下、position: fixed 的浮层:宽度固定 340px(左列 118px、右列自适应),最高 320px,不参与输入区排版,开关面板都不挤动输入框与会话框。按钮上方放不下时翻到按钮下面,窗口尺寸变化或页面滚动时跟着按钮重新摆位。
收起的过程组照样进面板,一个过程组算一条:组里的内容收在 hidden="until-found" 的子树里,面板按组的文案归到「AI 提问」或「工具」。点它的编号先让那一回合展开(触发官方 beforematch 揭示),等这一行长出真实位置再滚过去;展开失败时退回那一回合的汇总行。
各类的取法:
| 左列 | 取什么 |
|---|---|
| AI 提问 | 过程组组头文案是「向用户提出了问题」的组;只看组头,组内容里提到 ask_user_question 不算 |
| 工具 | 其余过程小结行(已调用工具 / 已读取文件 / 执行了命令…) |
| AI 回答 | 聊天流里的正文行,连续几段合并成一段算一条 |
| 你的消息 | 用户自己发的消息、插话、回合触发行 |
跳转直接写聊天区滚动容器的滚动位置(目标行停在正文上沿下方 96px),不经过 scrollIntoView。
「AI 提问」条目的摘要取题干:官方把那行问答折在工具行里,点开才渲染,而展开会让页面滚动,所以插件走只读路径——顺着 DOM 节点上的 React 内部数据取题干(组里那个 [data-chat-flow-kind="tool-call"] 行):调用进行中读入参原文 root.argsRaw,回答之后官方把入参挪进 root.call.argsRaw,两条都认;再退一步用流式参数视图 args.value('questions')。不展开、不点击,页面不动。悬停能看到全部题干;题干读不到时退回提问行自己的文案(如「提问1/1 已回答」),不会拿组里第一行的思考摘要充数;要看问答全文仍需点开折叠行。
配置页
侧栏「插件」→ dsh-chat-gutter 包详情 → 行 dsh-chat-gutter 的「配置」:
- 启用左侧标识带(总开关)
- 回合编号路标
- 行级徽标
- 回答起点高亮:关闭 / 干完活后 / 总是
点「保存」写入宿主配置并立即生效;关掉总开关会移除覆盖层、全部标记与正文高亮属性。
工作方式
- 在
document.body下挂左右两层position: fixed、pointer-events: none的覆盖层,宽度 92px,纵向铺满视口。所有标记画在覆盖层里,不修改官方聊天 DOM 结构。 - 裁剪窗=官方滚动容器:先认
[data-conversation-scroll],再按overflow-y往上找(最多 16 层),找不到才退回屏幕可视区。正文正是在这一层被裁掉的,比它高得多、上边跑到屏幕外的内容列不能当裁剪线。 - 覆盖层额外用
clip-path: inset(...)裁到正文可视区,最后一道闸:任何标记都画不进标题栏。写入的裁剪切串单独记着,浏览器会把inset()的第四个值归一化,拿读回值当缓存会每帧重写。 - 位置每次从聊天流行与滚动容器的矩形推算:左侧带
left = 内容列左界 - 92 - 8,右侧带left = 内容列右界 + 8;空白不足时整条带隐藏,不遮内容。 - 只处理可见行:
[hidden]祖先下的行、data-chat-motion="collapse"行跳过;展开后自动补上。 - 回答段用
data-dsh-answer-start="1"标在目标行上,用inset box-shadow画左侧琥珀条,不改背景色。 - 重扫触发:聊天流行变化、流式文本增长、折叠/展开、会话切换。结构指纹没变时只量坐标走快路;滚动与缩放只重算几何,用
requestAnimationFrame节流。 - 标记节点按 key 复用,不整层清空重建;卸载时移除覆盖层、样式、面板与所有
data-dsh-answer-start。
已知边界
- 锚点全部来自官方聊天区属性:
[data-chat-turn]、[data-chat-flow-kind]、[data-chat-flow-key]、[data-step-process]、[data-conversation-scroll]、[data-turn-process]、[data-question-key]、[data-variant="think"]。官方改版重命名后,对应标记与面板分类会失效,需要更新lib/client.js的选择器。 - 「AI 提问」在历史会话里没有独立卡片元素:待答时是
[data-question-key]的提问卡,答过之后只在过程小结行里留下一句「向用户提出了问题」。面板按组头这句文案归类(不看组内容,否则思考文本里提到 ask_user_question 的工具组会被误判),界面语言不是中文时这一类的归类会落到工具里。 - 过程组收起时,组里的工具行带
hidden祖先、不参与面板;面板按「一个过程小结一条」列,展开后也不重复列组内每一行。 - 窗口太窄、聊天列左侧没有约 104px 空白时整条带自动隐藏;面板与回答高亮不受影响。
- 同一页面存在多个聊天流容器时,只跟随第一个可见容器。
- 不读取会话数据、不发网络请求、不写
localStorage;只读 DOM 的行属性与文本。 - 宿主解析不到
@deepseek-ai/schemastery时行仍可加载,浏览器半边按内置默认值(三项全开、回答段弧线「干完活后」)运行。
环境
- DSH 桌面端 0.2.1-rc.2(
dsh.engines.dsh = ">=0.2.1-rc.2") - 浏览器半边只
require('react');dsh.client.inject声明宿主提供的两个槽位包@deepseek-ai/dsh-client-ui-conversation、@deepseek-ai/dsh-client-ui-slots
开发与测试
cd dsh-chat-gutter
node --check lib/client.js && node --check lib/index.js
pnpm test # node --test "tests/*.test.mjs"
pnpm pack # dsh-chat-gutter-<版本>.tgz
tar -tzf dsh-chat-gutter-<版本>.tgz # 核对包内容,不许有 lib/.dsh-ops-update-*.lock
tests/ 用最小 DOM 桩覆盖扫描、编号、徽标、回答段弧线、裁剪窗、覆盖层裁剪、快速移动面板与配置开关;真实页面行为按 INSTALL-PLAN.md 的验收点人工过一遍。
安装 / 回滚
见 INSTALL-PLAN.md。安装、启用、停用、重启由用户在应用界面里操作。
No comments yet. Be the first to write one.