dsh-node-navv0.2.9

seryta/dsh-node-nav

客户端 MIT 最近更新 2026-11-11
1Stars
1Forks
0待解决 Issue
安装命令 dsh plugin --profile web add github:seryta/dsh-node-nav#f626fed263526f9976cd14a844ba5093fb72e821

介绍

对话节点导航:DSH Web GUI 右侧节点串,hover 预览、点击跳转、active 药丸跟随阅读位置

标签

conversation-navigation deepseek deepseek-harness dsh-plugin

概述

dsh-node-nav — 对话节点导航

DSH(DeepSeek Harness)Web GUI 的客户端插件:在聊天区右侧显示一列等距节点串,每条用户消息对应一个节点。hover/focus 时显示消息文本预览,点击平滑滚动到对应消息并短暂高亮,active 药丸跟随阅读位置滑动。

功能

功能说明
覆盖官方轨隐藏官方 dsh-client-ui-chat 的 TurnNavigator 轨(两条轨同处右缘、几何重叠),由本插件接管轮次导航
节点串右缘纵向节点串,每条用户消息一个圆点;最多同时显示 15 个,窗口随阅读位置滑动(无滚动条),两端渐隐提示更多
全量历史节点host 半部从会话日志提取全部用户消息(含分页未加载的历史),导航在点击前就覆盖全部历史,页面保持原生分页不展开
底部节点rail 最底端固定一个方形「跳到底部(最新消息)」节点,形状与提示区别于消息节点
hover/focus 预览显示服务端全文(300 字符截断)与时间,键盘焦点同样触发
点击跳转scrollIntoView 居中 + 品牌蓝高亮环 1.2 秒
active 跟随滚动时药丸实时标出视口内最顶部的用户消息
输入历史输入框为空时按 ↑ 回填最近一条用户输入,连续 ↑ 翻更早、↓ 翻回;编辑/发送后指针重置
旁支问答联动装了 dsh-btw 时,/btw 的旁支问答以青色菱形混进节点串(按会话日志顺序),点击把底部浮条开回那一条;未安装时行为完全不变
details 避让右侧详情面板打开时节点串自动左移
深色模式跟随 body[data-ds-dark-theme]
reduced-motion系统减少动效偏好下禁用动画
自动隐藏非对话页时不显示

截图

节点串总览(浅色模式):每个圆点对应一条用户消息,底部方形节点一键跳到底部,药丸标出当前阅读位置。

hover/focus 预览:鼠标悬停(或键盘聚焦)节点时显示该条用户消息的服务端全文与时间。

active 药丸跟随:滚动聊天区时,药丸实时移动到视口内最顶部的用户消息节点。

深色模式:跟随 body[data-ds-dark-theme] 自动切换配色。

输入历史(↑ / ↓)

输入框(draft)为空时:

  • ↑:回填最近一条用户输入;连续 ↑ 翻更早(到最早一条为止);
  • ↓:翻回更近一条;回到最新位置后 ↓ 清空 draft(与 readline/TUI 惯例一致);
  • 开始编辑回填文本、发送或清空后,指针重置(下次 ↑ 从最近一条重新开始);
  • 菜单打开时(触发词在 draft 中)天然不抢上下键;IME 组合中不拦截;
  • 数据复用 /plugins/dsh-node-nav/api/users(仅 source.kind === 'user' 的真实输入,纯图片消息 [图片] 占位不参与历史)。

旁支问答(dsh-btw)联动

装了 dsh-btw 时,它会把当前会话的旁支问答(/btw)以青色菱形混进同一条节点串——不是第二条轨,所以窗口滑动、active、hover 预览、details 避让都共用一套。

  • 顺序:按会话日志的 seq 合并。旁支记录带 anchorSeq,用户消息带 seq,seq <= anchorSeq 的消息排在前面,因此"第几次提问之后的旁支"是精确的,刷新后从日志重建、不需要额外存储;
  • 形状:用户消息是靛蓝圆点、旁支是青色菱形、底部跳转是灰方块,三种形状一眼可分;当前浮条里显示的那条菱形填色高亮;
  • 点击:把底部浮条切到那一条(浮条与节点串共用同一个选中状态,‹ › 翻页也会同步高亮),叉掉浮条不会再"丢失"——菱形是常驻入口,点同一条还能重新展开;
  • 桥接协议:两个插件是独立包,通过最小 window 契约通信——window.__dshBtw = { version, list(), selected(), open(id), subscribe(fn) }。dsh-btw 缺席/未加载时本插件行为与从前一致;dsh-btw 单独热重载会换掉该对象,本插件每秒探测一次并在身份变化时重挂订阅。

数据源:服务端会话日志 + DOM 行状态(双轨)

  • 全量列表:host 半部端点 /plugins/dsh-node-nav/api/users 从 attached 会话的 session log 提取全部真实用户消息(source.kind === 'user'),返回 [{ id, seq, time, text }]——导航覆盖全部历史,页面不展开,无历史预加载副作用;
  • DOM 行状态:锚点 key 形如 <seq>:input-message<uuid>,uuid 即消息 id,用 [data-chat-anchor-key$="<id>"] 判定该消息行是否已加载进页面;
  • 端点不可用/冷会话(未 attached)时回退到纯 DOM 扫描。

未加载的节点以虚线半透明显示;点击时翻页直到该行进入 DOM,再滚动定位。翻页走会话控制器的 loadOlder()(一页一页往前、边翻边提交,锚点一出现立刻停),运行时没有会话实例时才回退到点页面「加载更早」按钮。翻页期间节点条上显示「正在加载历史… 第 N 页」。hover 预览显示服务端全文与时间。

安装

dsh plugin --profile web add github:Seryta/dsh-node-nav

安装后重启 dsh web 并刷新页面。插件是纯手写 JS(零构建、零依赖),git 安装不会触发任何构建脚本。

也可以 clone 后从本地目录安装:

git clone https://github.com/Seryta/dsh-node-nav.git
dsh plugin --profile web add ./dsh-node-nav

与同类插件的差异

项目数据源本插件的取舍
dsh-navbarDOM 扫描(同款思路)本插件采用相同的 DOM 扫描契约,但保留 React + shell.overlay 槽位形态(可停用/启用),并补充 details 面板避让
dsh-turn-index会话快照 binding(id).session快照方案在部分 dsh 版本上只见最近窗口节点,故本插件改走 DOM

覆盖官方轨

DSH 0.1.5 起官方 dsh-client-ui-chat 自带 TurnNavigator 轨(整会话轮次导航)。它与本插件同处聊天区右缘,实测 1700px 视口下官方轨横跨 1650–1678、本插件 1656–1672 —— 横向重叠 16px、纵向 152px,两条轨会叠在一起。评估后(2026-09-10)由本插件接管轮次导航,插件启动时隐藏官方轨。

官方轨的不足(当时评估):容器宽度 ≤900px 时整轨隐藏;轨高上限 420px,长会话需在轨内滚动;预览只 1 行提问 + 3 行回答;无「跳到底部」节点;无输入历史 ↑/↓。

隐藏方式:官方类名带构建哈希(如 eGxaPq_frame),不能硬编码,故运行时探测后生成规则。三层保险:

  1. 类名规则 —— 探测按 nav 标签 + 类名含 _frame + 内部 ≥2 个 _mark 匹配,刻意不看元素可见性:display:none 的元素 rect 恒为 0,用几何判据在「官方轨已被上一份规则隐藏」时必然探空,客户端 HMR 换代空档就会漏掉(这正是首版失效的原因)。同一文档里 pI_x6G_frame(DIV,聊天滚动画布)也含 _frame,故用 nav 标签排除。类名规则对 React 重建出的新节点同样生效。
  2. 内联 display:none —— 规则之外再压一层,样式元素被摘掉也仍然隐藏;dispose 时还原。
  3. 常驻 MutationObserver —— 同时盯 body(等官方轨出现)与 head(样式元素被摘掉不会产生 body 变更);守卫是纯属性读取,不做 DOM 遍历,流式 delta 期间开销可忽略。

实测(含真实 HMR 热重载两次 + 摘除覆盖后自愈)见 tmp/dsh-node-nav-restore/verify-hmr-heal.js。要切回官方轨,删掉 client.js 里的 installOfficialRailOverride 调用即可。

已知限制

  • 输入历史与跳转依赖 dsh 客户端契约:[data-chat-anchor-key](行锚点)、[data-composer-card](composer 容器)与输入状态/写入口——0.1.2+ 走 ctx.conversation.input.for(scope) 的 state + setDraft,0.1.1 走 provide 通道 hooks.input / props.inputActions。harness 大版本改动这些契约时需同步适配。
  • setDraft 必须绑回实例调用:0.1.2+ 的 input.for() 返回 SessionInputShell 实例,其 setDraft 是原型方法(内部读 this.projection.clipboardText),摘下来单独调用会丢 this 并抛 TypeError,表现为 ↑/↓ 回填静默失效。插件统一经 inputSetDraft() 取绑定后的函数。
  • 输入历史仅覆盖当前打开的(live)会话:host 端点只查 attached 会话,未打开的历史会话无 composer 焦点,不适用输入历史。
  • 未加载节点点击的主路径依赖会话控制器契约:Session 实例从 ctx.sessions.manager.get(id)(0.2.x)或 ctx.sessions.get(id)(更早)取得,用 loadOlder() 增量翻页;两者都拿不到时回退到点页面「加载更早」按钮(该按钮分页期间会变成 disabled + 「加载中…」,故按 div[class*="_older"] > button 结构定位,不按文本)。真正翻到 hasMore=false 仍找不到才提示「目标消息未能定位」。
  • 很早的节点在有上万条事件的会话里需要翻十几页(每页由 host 端读日志准备,实测 12k 事件的会话约 30–50 秒),这是 harness 自身分页模型的成本,不是本插件额外开销。
  • 旁支菱形的顺序依赖 host 端点返回的 seq。端点不可用、退化为纯 DOM 扫描时无从比较,旁支会被追加在节点串末尾(仍可点击、仍按日志顺序)。

License

MIT

星级评价

1.5 按 GitHub Stars 换算的社区热度,非用户打分
Stars1
Forks1
待解决 Issue0
最近更新2026-11-11

基本信息

作者Seryta
版本0.2.9
开源许可MIT
默认分支master
入库时间2026-11-11

插件信息来自公开 GitHub 仓库与 SkillHub,版权归原作者所有;安装前请自行审查仓库代码。