dsh-question-index
Verifieddsh-question-index · v0.1.0 · MIT · Web UI
DeepSeek Harness 会话内提问索引:右侧圆角横线时间线,悬停查看提问、点击跳转。In-session question index rail for the DeepSeek Harness Web UI.
Install
dsh plugin add dsh-question-index Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-question-index
DeepSeek Harness 会话内提问索引:右侧一条圆角横线时间线,每个用户提问一条短横线,悬停展开全部提问面板,点击跳转到对应消息。
In-session question index for the DeepSeek Harness Web UI: a floating right-edge timeline of short rounded lines — one per user question — with a hover-expanded all-questions panel and click-to-jump.
功能
- 横线时间线:右侧悬浮一条圆角横线时间线,与右边缘保持距离、上下居中;默认白色短横线,当前阅读位置的提问为蓝色。横线等距分布:提问多就密集、少就稀疏,与回复长度无关。
- 悬停展开全部提问:鼠标悬停到时间线上,弹出白底圆角面板,列出会话全部提问(内容预览 + 提问时间,无轮次),点击任意一条跳转。
- 点击跳转:点击横线或面板条目,平滑滚动到会话中对应的提问并短暂高亮;跳转后面板保持打开(鼠标未离开时间线)。目标提问尚未加载到会话窗口时,自动按需加载更早历史再跳转。
- 当前位置高亮:滚动会话时,当前阅读位置附近的提问横线高亮为蓝色。
- 长会话不卡:索引由会话事件增量构建(追加一条提问 O(1),流式输出期间零重扫),"全部提问"来自宿主侧轻量路由(不加载整个会话内容),面板列表窗口化渲染,千轮对话也流畅。
安装
要求 Node.js >= 24、DeepSeek Harness 0.1.1-rc.2 及以上。
# 从 npm 安装(推荐)
dsh plugin --profile web add dsh-question-index
# 或从 GitHub 源码安装
dsh plugin --profile web add "github:deadz000/dsh-question-index#main"
# 启动 Web UI
dsh web # → http://127.0.0.1:3080
从 GitHub 安装时,pnpm >= 10 默认禁止运行 git 依赖的 prepare 构建脚本,首次 add 会失败并打印一个 allowBuilds 键;把它复制到 profile 的 pnpm-workspace.yaml(~/.dsh/profiles/web/pnpm-workspace.yaml)后重新 add 即可。
安装后重启 dsh web 服务(或重启 dsh --profile web),刷新页面。本地 file: 安装是拷贝不是链接:改代码后需要 pnpm run build + dsh plugin --profile web remove dsh-question-index && dsh plugin --profile web add file:... 刷新拷贝,再重启服务。
使用方法
打开任意一个多轮对话,右侧出现悬浮的横线时间线:
- 把鼠标移上去 → 弹出「全部提问」面板,看到这个会话里所有提问(含尚未加载进会话窗口的更早提问)。
- 点击任意横线或面板条目 → 跳到那条提问,消息短暂高亮;面板保持打开。
- 上下滚动会话 → 当前提问的横线高亮为蓝色。
工作原理
dsh-question-index 完全跑在 DeepSeek Harness 官方的扩展点上,不修改任何 harness 源码:
| 组件 | 扩展点 | 职责 |
|---|---|---|
| node half 路由 | webServer.register(自定义 HTTP 路由 /dsh-question-index/<sessionId>) |
从宿主内存中的完整会话日志提取全部提问元数据(collectQuestions),浏览器一次 fetch 拿到,不加载整个会话内容 |
questionIndexDefinition |
ctx.conversationEvents(ConversationNodeDefinition) |
每个用户提问一个 Context:只匹配 user/message(人类来源)事件,状态即条目 |
QuestionIndexBuilder |
ctx.conversationViews(自定义 view target question-index) |
增量累积实时尾部的有序提问快照,只接收变化的提问节点 |
QuestionIndexOverlay |
shell.overlay(root 作用域) |
全框架浮动层挂载点,通过 SessionProvider 桥接会话作用域 |
QuestionIndexRail |
question-index.rail(会话作用域,自声明子槽) |
横线时间线 + 悬停面板 + 跳转(未加载目标时按需 loadOlder)+ 当前位置高亮 |
浏览器侧将「宿主全量列表」与「实时尾部索引」按消息 id 合并(mergeQuestionEntries):宿主的完整列表一次到位,新提问继续增量追加。
为什么长会话不卡
与「每次渲染都从完整会话快照重新推导提问列表」的做法不同,本项目遵循引擎的增量契约:
- 追加路径 O(1):每条
user/message事件只做一次类型检查匹配;assistant/chunk(流式输出)根本不匹配,模型打字期间我们零工作。 - "全部提问"不加载会话:宿主侧只扫描内存日志提取提问元数据(每条约几十字节),浏览器渲染的是紧凑列表,不会把整个会话历史拉进页面 —— 第一次进入长会话也不会卡。
- 禁止全量扫描:Definition 和渲染路径从不遍历事件窗口、Context 集合或聊天节点列表。
- 快照引用稳定:没有新提问时快照对象不变,组件零重渲染成本。
- 位置是纯数学:横线等距分布(
position.ts),不做 DOM 测量;当前位置高亮是 O(1) 序号映射 + rAF 节流。 - 面板窗口化:全部提问面板只渲染可视区间的条目(
QuestionIndexPanel),千条提问也只渲染几十行 DOM。
开发
与 DeepSeek Harness checkout 同级放置(仅开发期 typecheck 需要;运行时从 npm 安装依赖):
~/git/deepseek-harness # harness 源码(可选,仅类型对照)
~/git/dsh-question-index # 本仓库
pnpm install
pnpm run typecheck # 类型检查
pnpm test # vitest 单测(逻辑、构建器、宿主提取、合并、组件)
pnpm run build # tsdown:lib/index.js + lib/client.js
pnpm run watch # 开发期监听重建
本地安装测试:
pnpm run build
dsh plugin --profile web add file:D:/path/to/dsh-question-index
# 改代码后刷新拷贝:remove 再 add(见上),然后重启 dsh web
已知限制
- 跳转未加载的提问需按需加载:点击一个还没加载进会话窗口的更早提问时,会逐页加载更早历史直到目标出现(有页数上限),这是 DSH 的分页机制决定的;首次进入会话本身不受影响。
- 横线等距分布:位置与提问总数等距,不反映消息的实际像素高度,长消息段落会有少量视觉偏差;当前位置高亮同样按等距近似。
- 时间线覆盖原生滚动条区域:横线悬浮在滚动条上方,横线处无法直接拖动原生滚动条(滚轮滚动不受影响)。
- 单会话列假设:时间线定位到页面上唯一的会话滚动容器(
[data-conversation-scroll]);多列布局暂不支持。 - 宿主路由无鉴权:
/dsh-question-index/<sessionId>与/api同信任域(默认仅回环地址),不额外做鉴权。
License
MIT