dsh-conversation-navigator 0.1.22 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -5
  2. package/lib/client.js +168 -13
  3. package/package.json +6 -2
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # DSH 会话导航插件(dsh-conversation-navigator)
2
2
 
3
- Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页**右侧**悬浮展示按轮折叠的对话大纲,点击任意节点平滑跳转,滚动对话时实时高亮当前阅读位置,步次徽标配色与内置"轨迹"视图统一。
3
+ [![npm](https://img.shields.io/npm/v/dsh-conversation-navigator?style=flat-square&color=blue)](https://www.npmjs.com/package/dsh-conversation-navigator)
4
+ [![downloads](https://img.shields.io/npm/dm/dsh-conversation-navigator?style=flat-square&color=blue)](https://www.npmjs.com/package/dsh-conversation-navigator)
5
+ [![stars](https://img.shields.io/github/stars/gjj-star/dsh-conversation-navigator?style=flat-square&color=green)](https://github.com/gjj-star/dsh-conversation-navigator)
6
+ [![license](https://img.shields.io/github/license/gjj-star/dsh-conversation-navigator?style=flat-square&color=teal)](https://github.com/gjj-star/dsh-conversation-navigator)
7
+ [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen?style=flat-square)](https://www.npmjs.com/package/dsh-conversation-navigator)
8
+ [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
9
+ [![DeepSeek Harness](https://img.shields.io/badge/DeepSeek_Harness-Plugin-blue?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
10
+
11
+ **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页**右侧**悬浮展示按轮折叠的对话大纲,点击任意节点平滑跳转,滚动对话时实时高亮当前阅读位置,步次徽标配色与内置"轨迹"视图统一。
4
12
 
5
13
  纯浏览器插件(无宿主行为)、纯 JavaScript、零构建、零 npm 依赖(按钮/Tooltip 复用 DSH 内核 seed 的官方 primitives)。
6
14
 
@@ -14,14 +22,14 @@ Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页*
14
22
  - **关键词过滤**:点击左上角搜索图标呼出输入框,只按「你的提问 + 助手实际回复文本」过滤(上下文、工具、命令、压缩、推理等不参与匹配),命中词高亮,列表文本自动定位到关键词处
15
23
  - **展开/折叠步次**:点击轮次行右侧的箭头按钮(`▸ N`,展开时旋转为 `▾`)平滑展开/折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
16
24
  - **悬停查看全文**:鼠标在轮次行停留片刻,气泡显示该轮用户提问的完整原文,不再被单行截断
17
- - **显示 / 隐藏轮次**:头部切换按钮一键切换两种风格——「显示轮次」为经典分组视图(压缩等系统事件与轮次同级加粗展示);「隐藏轮次」为极简视图,每一行统一为轨迹徽标 + 文本(用户 = 业务蓝、助手 = 紫罗兰、压缩 = 中性灰),搜索框展开时按钮自动让位
25
+ - **显示 / 隐藏 / 极简三种模式**:头部切换按钮三态循环——「显示轮次」为经典分组视图(压缩等系统事件与轮次同级加粗展示);「隐藏轮次」每行统一为轨迹徽标 + 文本(用户 = 业务蓝、助手 = 紫罗兰、压缩 = 中性灰);「极简模式」收起时只露行内指示条(当前 = 品牌色实色、非当前 = 主题前景色掺 40%),悬停从右向左展开 7 行画幅的定位面板(点击跳转、悬停全文气泡、超 7 条仅滑块滚动),外置悬浮按钮返回「显示轮次」,搜索框展开时按钮自动让位
18
26
  - **丝滑动效**:面板开合淡入淡出、步次展开/折叠高度过渡、过滤结果逐条级联淡入、折叠箭头旋转——全部纯 CSS 实现,零依赖
19
27
  - **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
20
28
  - **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
21
29
  - **位置跟踪**:手动滚动对话时,面板自动高亮并跟随当前正在阅读的轮次
22
30
  - **右侧定位**:面板锚定视口右侧,收起/展开左侧边栏时纹丝不动
23
31
  - **回到最新 / 全部折叠**:面板底部两个快捷按钮
24
- - **轨迹配色**:用户/插话 = 业务蓝、上下文 = 成功绿、助手 = 紫罗兰、工具 = 琥珀、压缩 = 中性灰(取自 `dsh-client-ui-trajectory` 的 kindTag 主题 token,自动适配明暗主题)
32
+ - **轨迹配色**:用户/插话 = 业务蓝、上下文 = 成功绿、助手 = 紫罗兰、工具 = 琥珀、压缩 = 中性灰(与内置轨迹视图一致的 `--dsw` 主题 token,自动适配明暗主题)
25
33
  - **DSH 原生风格**:操作按钮复用官方 `Button`/`Tooltip` 组件与官方图标(搜索、关闭);其余操作图标(导航、加载更早、加载全部、回到最新、全部折叠、切换轮次等)为按 DSH 描边风格手绘的内联 SVG,`currentColor` 自动适配明暗主题
26
34
  - 切换工作区/会话自动跟随并重建大纲
27
35
 
@@ -47,7 +55,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
47
55
 
48
56
  ## 更新
49
57
 
50
- 修改 `lib/client.js` 后重启 `dsh web` 即可;插件没有持久状态,展开/折叠、显示/隐藏轮次、搜索关键词等界面状态仅存于页面会话内。
58
+ 修改 `lib/client.js` 后重启 `dsh web` 即可;插件没有持久状态,展开/折叠、显示/隐藏/极简模式、搜索关键词等界面状态仅存于页面会话内。
51
59
 
52
60
  ## 工作原理
53
61
 
@@ -58,7 +66,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
58
66
  - 位置跟踪:捕获 `[data-conversation-scroll]` 滚动容器的 scroll 事件(节流 120ms),计算视口顶部首个可见节点
59
67
  - 关键词过滤:仅为 `user` 与 `assistant-step` 节点提取检索文本(`dialogueText`),大小写不敏感匹配,命中片段以 `<mark>` 高亮并按首个命中位置截取显示窗口
60
68
  - 悬停全文:轮次头气泡读取 `fullDialogueText`(用户节点全部文本块拼接),用 `Tooltip` 展示并限宽 340px
61
- - 显示/隐藏轮次:头部按钮切换 `turnsHidden`,隐藏时仅把轮次头标题替换为轨迹徽标(`titleNode` 策略),分组结构不变;搜索框展开时按钮让位
69
+ - 显示/隐藏/极简模式:头部按钮循环切换 `viewMode`(full/hidden/minimal),隐藏时仅把轮次头标题替换为轨迹徽标(`titleNode` 策略);极简时渲染独立的定位面板(收起仅露行内指示条,悬停展开),搜索框展开时按钮让位
62
70
  - 样式:`Button`/`Tooltip`/搜索与关闭图标复用 `@deepseek-ai/dsh-client-ui-primitives`,其余图标为内联 SVG;面板容器自建 `<style>` 注入,颜色使用 `--dsw-*` 主题 token;插件卸载时随 fiber 清理
63
71
 
64
72
  ## 兼容性说明
@@ -73,6 +81,8 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
73
81
  lib/
74
82
  index.js # 宿主侧空入口(纯浏览器插件)
75
83
  client.js # 浏览器端完整实现(window.__ModuleLoader__ 模块格式)
84
+ assets/
85
+ screenshots/ # 截图(README 主图 + 市场截图墙)
76
86
  cordis.patch.yml # 组合包补丁层(插入插件行)
77
87
  example.patch.yml # 手动安装时的补丁示例
78
88
  ```
package/lib/client.js CHANGED
@@ -205,6 +205,76 @@ window.__ModuleLoader__.load({
205
205
  color: inherit; border-radius: 2px; padding: 0 1px; font-weight: 600;
206
206
  }
207
207
  .cnvnav-empty { padding: 18px 10px; text-align: center; color: var(--dsw-alias-label-secondary, #888); }
208
+ /* ---------- 极简模式 ---------- */
209
+ .cnvnav-mini-wrap {
210
+ position: fixed; z-index: 9999; right: 12px;
211
+ pointer-events: auto;
212
+ transition: opacity .18s ease, transform .18s ease;
213
+ }
214
+ .cnvnav-mini-panel {
215
+ width: 18px; height: 220px; overflow: hidden;
216
+ position: relative;
217
+ display: flex; flex-direction: column;
218
+ background: transparent;
219
+ border: 1px solid transparent;
220
+ border-radius: 12px;
221
+ box-shadow: none;
222
+ transition: width .18s ease, background .16s ease, border-color .16s ease, box-shadow .16s ease;
223
+ }
224
+ .cnvnav-mini-wrap:hover .cnvnav-mini-panel {
225
+ width: 288px;
226
+ background: var(--dsw-alias-bg-overlay, #fff);
227
+ border-color: var(--dsw-alias-border-l1, rgba(128,128,128,.35));
228
+ box-shadow: 0 16px 48px rgba(0,0,0,.22);
229
+ }
230
+ .cnvnav-mini-fab {
231
+ position: absolute; bottom: 100%; right: -12.5px;
232
+ margin-bottom: 6px;
233
+ display: flex;
234
+ }
235
+ .cnvnav-mini-list {
236
+ flex: 1; min-height: 0; width: 288px; box-sizing: border-box;
237
+ overflow-y: auto; padding: 4px;
238
+ display: flex; flex-direction: column; gap: 2px;
239
+ }
240
+ /* dsweb 式滚动条: 只显示滑块, 无滑轨无上下按钮.
241
+ Chromium 用 webkit 伪元素(标准属性会令其失效), Firefox 用 @supports 标准属性 */
242
+ .cnvnav-mini-list::-webkit-scrollbar { width: 4px; }
243
+ .cnvnav-mini-list::-webkit-scrollbar-track { background: transparent; }
244
+ .cnvnav-mini-list::-webkit-scrollbar-thumb {
245
+ background: color-mix(in srgb, var(--dsw-alias-label-secondary, #888) 40%, transparent);
246
+ border-radius: 2px;
247
+ }
248
+ .cnvnav-mini-list::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
249
+ .cnvnav-mini-list::-webkit-scrollbar-corner { background: transparent; }
250
+ @supports (-moz-appearance: none) {
251
+ .cnvnav-mini-list {
252
+ scrollbar-width: thin;
253
+ scrollbar-color: color-mix(in srgb, var(--dsw-alias-label-secondary, #888) 40%, transparent) transparent;
254
+ }
255
+ }
256
+ .cnvnav-mini-item {
257
+ flex: none; height: 28px; box-sizing: border-box; width: 100%;
258
+ display: flex; align-items: center; text-align: left;
259
+ border: none; background: transparent; cursor: pointer;
260
+ padding: 5px 8px 5px 17px; border-radius: 8px; position: relative;
261
+ color: var(--dsw-alias-label-secondary, #888);
262
+ font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
263
+ transition: background .16s ease;
264
+ }
265
+ .cnvnav-mini-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
266
+ /* 每行都有指示竖条: 非当前=掺前景色(浅色主题掺黑/深色主题掺白, 40%), 当前=品牌色实色 */
267
+ .cnvnav-mini-item::before {
268
+ content: ''; position: absolute; left: 6px; top: 6px; bottom: 6px;
269
+ width: 3px; border-radius: 2px;
270
+ background: color-mix(in srgb, var(--dsw-alias-label-primary, #222) 40%, transparent);
271
+ transition: background .16s ease;
272
+ }
273
+ .cnvnav-mini-item-active {
274
+ background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 13%, transparent);
275
+ color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 60%, var(--dsw-alias-label-primary, #222));
276
+ }
277
+ .cnvnav-mini-item-active::before { background: var(--dsw-alias-brand-primary, #4f46e5); }
208
278
  .cnvnav-filter {
209
279
  flex: 1; min-width: 0; box-sizing: border-box;
210
280
  height: 28px; padding: 0 8px;
@@ -564,7 +634,14 @@ window.__ModuleLoader__.load({
564
634
  const snap = useStore();
565
635
  const [filter, setFilter] = React.useState("");
566
636
  const [searchOpen, setSearchOpen] = React.useState(false);
567
- const [turnsHidden, setTurnsHidden] = React.useState(false);
637
+ const [viewMode, setViewMode] = React.useState("full"); /* full | hidden | minimal */
638
+ const turnsHidden = viewMode !== "full";
639
+ function nextModeLabel() {
640
+ return viewMode === "full" ? "隐藏轮次" : viewMode === "hidden" ? "极简模式" : "显示轮次";
641
+ }
642
+ function cycleMode() {
643
+ setViewMode(viewMode === "full" ? "hidden" : viewMode === "hidden" ? "minimal" : "full");
644
+ }
568
645
  function toggleSearch() {
569
646
  if (searchOpen) setFilter("");
570
647
  setSearchOpen(!searchOpen);
@@ -582,27 +659,45 @@ window.__ModuleLoader__.load({
582
659
  const vh = document.documentElement.clientHeight || 800;
583
660
  const sp = document.querySelector("[data-conversation-scroll]");
584
661
  const width = 288;
662
+ const mini = viewMode === "minimal";
663
+ const MINI_H = 220;
585
664
  if (sp === null) {
586
665
  /* 会话视图可能尚未挂载(刷新/重启/切会话时面板先就位);
587
666
  非会话页(设置页等)则永远没有该容器。
588
667
  连续 15 次(约 6s)仍未出现才回退到视口定位, 否则保持隐藏等待校准。 */
589
668
  misses += 1;
590
669
  if (misses >= 15) {
591
- panel.style.left = Math.max(8, vw - width - 12) + "px";
592
- panel.style.top = Math.max(8, 60) + "px";
593
- panel.style.maxHeight = Math.round(vh - 90) + "px";
670
+ if (mini) {
671
+ /* 极简: 水平由 CSS right 锚定, 清掉普通面板残留的 left */
672
+ panel.style.left = "";
673
+ panel.style.top = Math.max(8, Math.round((vh - MINI_H) / 2)) + "px";
674
+ panel.style.maxHeight = MINI_H + "px";
675
+ } else {
676
+ panel.style.left = Math.max(8, vw - width - 12) + "px";
677
+ panel.style.top = Math.max(8, 60) + "px";
678
+ panel.style.maxHeight = Math.round(vh - 90) + "px";
679
+ }
594
680
  panel.classList.remove("cnvnav-panel-unplaced");
595
681
  return true;
596
682
  }
597
683
  return false;
598
684
  }
599
685
  const r = sp.getBoundingClientRect();
600
- const key = [Math.round(r.top), Math.round(r.height), Math.round(vw), Math.round(vh)].join("|");
686
+ const key = mini
687
+ ? [Math.round(r.top), Math.round(r.height), Math.round(vh)].join("|")
688
+ : [Math.round(r.top), Math.round(r.height), Math.round(vw), Math.round(vh)].join("|");
601
689
  if (key !== lastKey) {
602
690
  lastKey = key;
603
- panel.style.left = Math.max(8, vw - width - 12) + "px";
604
- panel.style.top = Math.max(8, Math.round(r.top + 8)) + "px";
605
- panel.style.maxHeight = Math.round(Math.min(r.height - 24, vh - 40)) + "px";
691
+ if (mini) {
692
+ /* 极简: 右侧锚定由 CSS(right)负责, 这里只定垂直中点 */
693
+ panel.style.left = "";
694
+ panel.style.top = Math.max(8, Math.round(r.top + (r.height - MINI_H) / 2)) + "px";
695
+ panel.style.maxHeight = MINI_H + "px";
696
+ } else {
697
+ panel.style.left = Math.max(8, vw - width - 12) + "px";
698
+ panel.style.top = Math.max(8, Math.round(r.top + 8)) + "px";
699
+ panel.style.maxHeight = Math.round(Math.min(r.height - 24, vh - 40)) + "px";
700
+ }
606
701
  }
607
702
  panel.classList.remove("cnvnav-panel-unplaced");
608
703
  return true;
@@ -624,7 +719,7 @@ window.__ModuleLoader__.load({
624
719
  clearInterval(id);
625
720
  window.removeEventListener("resize", place);
626
721
  };
627
- }, [snap.open, snap.sessionId]);
722
+ }, [snap.open, snap.sessionId, viewMode]);
628
723
 
629
724
  React.useEffect(() => {
630
725
  if (!snap.open) return;
@@ -667,6 +762,66 @@ window.__ModuleLoader__.load({
667
762
  const groups = snap.outline !== null ? snap.outline.groups : [];
668
763
  const count = snap.outline !== null ? snap.outline.count : 0;
669
764
 
765
+ /* ---------- 极简模式: 只保留定位 ---------- */
766
+ if (viewMode === "minimal") {
767
+ const miniRows = [];
768
+ for (const g of groups) {
769
+ if (g.turn === null) continue;
770
+ miniRows.push({
771
+ key: g.headKey,
772
+ text: g.subtitle,
773
+ full: g.headIsUser && g.items.length > 0 ? g.items[0].full : null,
774
+ active: g.items.some((it) => it.key === snap.activeKey),
775
+ });
776
+ }
777
+ return React.createElement("div", {
778
+ ref: (el) => { panelEl = el; },
779
+ className: "cnvnav-mini-wrap cnvnav-panel-unplaced" + (snap.open ? "" : " cnvnav-panel-hidden"),
780
+ role: "complementary",
781
+ "aria-label": "会话导航",
782
+ "aria-hidden": !snap.open,
783
+ },
784
+ React.createElement(Tooltip, {
785
+ label: "显示轮次",
786
+ side: "top",
787
+ delayMs: 500,
788
+ },
789
+ React.createElement("span", { className: "cnvnav-mini-fab" },
790
+ React.createElement(Button, {
791
+ variant: "ghost",
792
+ size: "sm",
793
+ icon: React.createElement(SwitchTurnsIcon),
794
+ "aria-pressed": true,
795
+ onClick: () => cycleMode(),
796
+ }),
797
+ ),
798
+ ),
799
+ React.createElement("div", { className: "cnvnav-mini-panel" },
800
+ React.createElement("div", {
801
+ ref: (el) => { listEl = el; },
802
+ className: "cnvnav-mini-list",
803
+ },
804
+ groups.length === 0
805
+ ? React.createElement("div", { className: "cnvnav-empty" }, "当前会话暂无对话节点")
806
+ : miniRows.map((r) => React.createElement(Tooltip, {
807
+ key: r.key,
808
+ label: r.full !== null ? r.full : "跳转到此位置",
809
+ side: "top",
810
+ delayMs: 500,
811
+ maxWidth: 340,
812
+ },
813
+ React.createElement("button", {
814
+ type: "button",
815
+ ref: (el) => { itemEls[r.key] = el; },
816
+ className: "cnvnav-mini-item" + (r.active ? " cnvnav-mini-item-active" : ""),
817
+ onClick: () => jumpTo(r.key),
818
+ }, r.text),
819
+ )),
820
+ ),
821
+ ),
822
+ );
823
+ }
824
+
670
825
  const kw = filter.trim().toLowerCase();
671
826
  const filtering = kw !== "";
672
827
  function hit(text) {
@@ -881,17 +1036,17 @@ window.__ModuleLoader__.load({
881
1036
  searchOpen
882
1037
  ? null
883
1038
  : React.createElement(Tooltip, {
884
- label: turnsHidden ? "显示轮次" : "隐藏轮次",
1039
+ label: nextModeLabel(),
885
1040
  side: "bottom",
886
1041
  delayMs: 500,
887
1042
  },
888
1043
  React.createElement("span", { className: "cnvnav-turns-toggle" },
889
1044
  React.createElement(Button, {
890
- variant: turnsHidden ? "primary" : "ghost",
1045
+ variant: viewMode !== "full" ? "primary" : "ghost",
891
1046
  size: "sm",
892
1047
  icon: React.createElement(SwitchTurnsIcon),
893
- "aria-pressed": turnsHidden,
894
- onClick: () => setTurnsHidden(!turnsHidden),
1048
+ "aria-pressed": viewMode !== "full",
1049
+ onClick: () => cycleMode(),
895
1050
  }),
896
1051
  ),
897
1052
  ),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-conversation-navigator",
3
- "version": "0.1.22",
4
- "description": "Codex 风格的 DSH(DeepSeek Harness)会话导航插件:按轮折叠的对话大纲面板,支持节点点击跳转、滚动位置高亮与轨迹视图同款配色。",
3
+ "version": "0.1.24",
4
+ "description": "DSH(DeepSeek Harness)会话导航插件:按轮折叠的对话大纲面板,支持节点点击跳转、滚动位置高亮与轨迹视图同款配色。",
5
5
  "keywords": [
6
6
  "dsh",
7
7
  "deepseek-harness",
@@ -14,6 +14,10 @@
14
14
  "会话导航"
15
15
  ],
16
16
  "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/gjj-star/dsh-conversation-navigator.git"
20
+ },
17
21
  "type": "module",
18
22
  "main": "lib/index.js",
19
23
  "exports": {