dsh-conversation-navigator 0.1.18 → 0.1.20
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.
- package/README.md +8 -4
- package/lib/client.js +91 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页**右侧**悬浮展示按轮折叠的对话大纲,点击任意节点平滑跳转,滚动对话时实时高亮当前阅读位置,步次徽标配色与内置"轨迹"视图统一。
|
|
4
4
|
|
|
5
|
-
纯浏览器插件(无宿主行为)、纯 JavaScript、零构建、零 npm 依赖(
|
|
5
|
+
纯浏览器插件(无宿主行为)、纯 JavaScript、零构建、零 npm 依赖(按钮/Tooltip 复用 DSH 内核 seed 的官方 primitives)。
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|
- **按轮折叠大纲**:默认只显示"第 N 轮 + 你的问题"列表,长对话一目了然
|
|
10
10
|
- **关键词过滤**:点击左上角搜索图标呼出输入框,只按「你的提问 + 助手实际回复文本」过滤(上下文、工具、命令、压缩、推理等不参与匹配),命中词高亮,列表文本自动定位到关键词处
|
|
11
11
|
- **展开/折叠步次**:点击轮次行右侧的箭头按钮(`▸ N`,展开时旋转为 `▾`)平滑展开/折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
|
|
12
|
+
- **悬停查看全文**:鼠标在轮次行停留片刻,气泡显示该轮用户提问的完整原文,不再被单行截断
|
|
13
|
+
- **显示 / 隐藏轮次**:头部切换按钮一键切换两种风格——「显示轮次」为经典分组视图(压缩等系统事件与轮次同级加粗展示);「隐藏轮次」为极简视图,每一行统一为轨迹徽标 + 文本(用户 = 业务蓝、助手 = 紫罗兰、压缩 = 中性灰),搜索框展开时按钮自动让位
|
|
12
14
|
- **丝滑动效**:面板开合淡入淡出、步次展开/折叠高度过渡、过滤结果逐条级联淡入、折叠箭头旋转——全部纯 CSS 实现,零依赖
|
|
13
15
|
- **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
|
|
14
16
|
- **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
|
|
@@ -16,7 +18,7 @@ Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页*
|
|
|
16
18
|
- **右侧定位**:面板锚定视口右侧,收起/展开左侧边栏时纹丝不动
|
|
17
19
|
- **回到最新 / 全部折叠**:面板底部两个快捷按钮
|
|
18
20
|
- **轨迹配色**:用户/插话 = 业务蓝、上下文 = 成功绿、助手 = 紫罗兰、工具 = 琥珀、压缩 = 中性灰(取自 `dsh-client-ui-trajectory` 的 kindTag 主题 token,自动适配明暗主题)
|
|
19
|
-
- **DSH
|
|
21
|
+
- **DSH 原生风格**:操作按钮复用官方 `Button`/`Tooltip` 组件与官方图标(搜索、关闭);其余操作图标(导航、加载更早、加载全部、回到最新、全部折叠、切换轮次等)为按 DSH 描边风格手绘的内联 SVG,`currentColor` 自动适配明暗主题
|
|
20
22
|
- 切换工作区/会话自动跟随并重建大纲
|
|
21
23
|
|
|
22
24
|
## 安装
|
|
@@ -41,7 +43,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
41
43
|
|
|
42
44
|
## 更新
|
|
43
45
|
|
|
44
|
-
修改 `lib/client.js` 后重启 `dsh web`
|
|
46
|
+
修改 `lib/client.js` 后重启 `dsh web` 即可;插件没有持久状态,展开/折叠、显示/隐藏轮次、搜索关键词等界面状态仅存于页面会话内。
|
|
45
47
|
|
|
46
48
|
## 工作原理
|
|
47
49
|
|
|
@@ -51,7 +53,9 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
51
53
|
- 跳转定位:复用 DSH 聊天视图自身的稳定 DOM 锚点 `[data-chat-anchor-key]`(与产品内部 paging/scroll 定位同源),`scrollIntoView` 平滑滚动
|
|
52
54
|
- 位置跟踪:捕获 `[data-conversation-scroll]` 滚动容器的 scroll 事件(节流 120ms),计算视口顶部首个可见节点
|
|
53
55
|
- 关键词过滤:仅为 `user` 与 `assistant-step` 节点提取检索文本(`dialogueText`),大小写不敏感匹配,命中片段以 `<mark>` 高亮并按首个命中位置截取显示窗口
|
|
54
|
-
-
|
|
56
|
+
- 悬停全文:轮次头气泡读取 `fullDialogueText`(用户节点全部文本块拼接),用 `Tooltip` 展示并限宽 340px
|
|
57
|
+
- 显示/隐藏轮次:头部按钮切换 `turnsHidden`,隐藏时仅把轮次头标题替换为轨迹徽标(`titleNode` 策略),分组结构不变;搜索框展开时按钮让位
|
|
58
|
+
- 样式:`Button`/`Tooltip`/搜索与关闭图标复用 `@deepseek-ai/dsh-client-ui-primitives`,其余图标为内联 SVG;面板容器自建 `<style>` 注入,颜色使用 `--dsw-*` 主题 token;插件卸载时随 fiber 清理
|
|
55
59
|
|
|
56
60
|
## 兼容性说明
|
|
57
61
|
|
package/lib/client.js
CHANGED
|
@@ -65,6 +65,16 @@ window.__ModuleLoader__.load({
|
|
|
65
65
|
);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
function SwitchTurnsIcon() {
|
|
69
|
+
return React.createElement("svg", {
|
|
70
|
+
viewBox: "0 0 1024 1024", width: 14, height: 14, fill: "currentColor",
|
|
71
|
+
"aria-hidden": true,
|
|
72
|
+
},
|
|
73
|
+
React.createElement("path", { d: "M168 454c-8.837 0-16-7.163-16-16v-48c0-8.837 7.163-16 16-16h531.572L519.63 191.225c-9.858-10.014-2.934-26.883 10.978-27.22l0.424-0.005h70a16 16 0 0 1 11.402 4.775l254 258C876.392 436.89 869.226 454 855.032 454H168z m642-61.741v0.032L855 438h0.032L810 392.259zM601.032 180H601l190.992 194h0.032L601.032 180z" }),
|
|
74
|
+
React.createElement("path", { d: "M855.064 570c8.837 0 16 7.163 16 16l0 48c0 8.837-7.163 16-16 16l-531.572 0L503.434 832.775c9.858 10.014 2.934 26.884-10.978 27.22l-0.424 0.005-70 0a16 16 0 0 1-11.402-4.775l-254-258C146.672 587.11 153.838 570 168.032 570L855.064 570z m-642 61.741l0-0.032L168.064 586l-0.032 0L213.064 631.741zM422.032 844H422.064l-190.992-194-0.032 0L422.032 844z" }),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
/* ---------- own stylesheet (static packages have no styles builtin) ---------- */
|
|
69
79
|
const CSS = `
|
|
70
80
|
.cnvnav-panel {
|
|
@@ -89,6 +99,7 @@ window.__ModuleLoader__.load({
|
|
|
89
99
|
}
|
|
90
100
|
.cnvnav-count { color: var(--dsw-alias-label-secondary, #888); font-size: 11px; }
|
|
91
101
|
.cnvnav-search { flex: none; display: flex; }
|
|
102
|
+
.cnvnav-turns-toggle { flex: none; display: flex; }
|
|
92
103
|
.cnvnav-close { margin-left: auto; }
|
|
93
104
|
.cnvnav-list {
|
|
94
105
|
overflow-y: auto; padding: 6px; min-height: 0;
|
|
@@ -99,20 +110,29 @@ window.__ModuleLoader__.load({
|
|
|
99
110
|
.cnvnav-group-row {
|
|
100
111
|
display: flex; align-items: stretch;
|
|
101
112
|
border-radius: 8px; overflow: hidden;
|
|
113
|
+
position: relative;
|
|
102
114
|
transition: background .16s ease;
|
|
103
115
|
}
|
|
104
116
|
.cnvnav-group-row:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
105
|
-
.cnvnav-group-row-active { background: var(--dsw-alias-
|
|
117
|
+
.cnvnav-group-row-active { background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 13%, transparent); }
|
|
118
|
+
.cnvnav-group-row-active::before {
|
|
119
|
+
content: ''; position: absolute; left: 6px; top: 6px; bottom: 6px;
|
|
120
|
+
width: 3px; border-radius: 2px;
|
|
121
|
+
background: var(--dsw-alias-brand-primary, #4f46e5);
|
|
122
|
+
}
|
|
123
|
+
.cnvnav-group-row-active:hover { background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 18%, transparent); }
|
|
106
124
|
.cnvnav-group-row-active .cnvnav-group-title { color: var(--dsw-alias-brand-primary, #4f46e5); }
|
|
125
|
+
.cnvnav-group-row-active .cnvnav-group-sub { color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 45%, var(--dsw-alias-label-secondary, #888)); }
|
|
107
126
|
.cnvnav-group-head-wrap { flex: 1; min-width: 0; display: flex; }
|
|
108
127
|
.cnvnav-group-head {
|
|
109
128
|
width: 100%;
|
|
110
129
|
display: flex; align-items: baseline; gap: 6px; text-align: left;
|
|
111
130
|
border: none; background: transparent; cursor: pointer;
|
|
112
|
-
padding: 5px 0 5px
|
|
131
|
+
padding: 5px 0 5px 17px;
|
|
113
132
|
color: var(--dsw-alias-label-primary, #222);
|
|
114
133
|
}
|
|
115
134
|
.cnvnav-group-title { font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif); font-size: 14px; font-weight: 700; line-height: 20px; white-space: nowrap; transition: color .16s ease; }
|
|
135
|
+
.cnvnav-group-head .cnvnav-badge { align-self: center; }
|
|
116
136
|
.cnvnav-group-sub {
|
|
117
137
|
color: var(--dsw-alias-label-secondary, #888);
|
|
118
138
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
@@ -145,12 +165,16 @@ window.__ModuleLoader__.load({
|
|
|
145
165
|
transition: background .16s ease;
|
|
146
166
|
}
|
|
147
167
|
.cnvnav-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
148
|
-
.cnvnav-item-active { background: var(--dsw-alias-
|
|
149
|
-
.cnvnav-item-active
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
.cnvnav-item-active { background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 13%, transparent); }
|
|
169
|
+
.cnvnav-item-active:hover { background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 18%, transparent); }
|
|
170
|
+
.cnvnav-item-active .cnvnav-item-preview { color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f46e5) 60%, var(--dsw-alias-label-primary, #222)); }
|
|
171
|
+
.cnvnav-item-active::before,
|
|
172
|
+
.cnvnav-item-active::after {
|
|
173
|
+
content: ''; position: absolute; left: 6px; width: 3px; border-radius: 2px;
|
|
152
174
|
background: var(--dsw-alias-brand-primary, #4f46e5);
|
|
153
175
|
}
|
|
176
|
+
.cnvnav-item-active::before { top: 6px; height: calc(50% - 7.5px); }
|
|
177
|
+
.cnvnav-item-active::after { bottom: 6px; height: calc(50% - 7.5px); }
|
|
154
178
|
.cnvnav-badge {
|
|
155
179
|
flex: none; min-width: 36px; box-sizing: border-box;
|
|
156
180
|
display: inline-flex; align-items: center; justify-content: center;
|
|
@@ -303,7 +327,24 @@ window.__ModuleLoader__.load({
|
|
|
303
327
|
}
|
|
304
328
|
return null;
|
|
305
329
|
}
|
|
306
|
-
|
|
330
|
+
/* Full user question text (all text blocks) for the hover bubble. */
|
|
331
|
+
function fullDialogueText(node) {
|
|
332
|
+
const d = node && node.data;
|
|
333
|
+
if (!d || typeof d !== "object") return null;
|
|
334
|
+
if (node.kind !== "user") return null;
|
|
335
|
+
if (typeof d.content === "string") {
|
|
336
|
+
const t = d.content.trim();
|
|
337
|
+
return t === "" ? null : t;
|
|
338
|
+
}
|
|
339
|
+
if (!Array.isArray(d.content)) return null;
|
|
340
|
+
const parts = [];
|
|
341
|
+
for (const b of d.content) {
|
|
342
|
+
if (!b || typeof b !== "object") continue;
|
|
343
|
+
if ((b.type === "text" || b.kind === "text") && typeof b.text === "string" && b.text.trim() !== "") parts.push(b.text.trim());
|
|
344
|
+
}
|
|
345
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
346
|
+
}
|
|
347
|
+
function previewFor(node) {
|
|
307
348
|
const d = node && node.data;
|
|
308
349
|
if (!d || typeof d !== "object") return null;
|
|
309
350
|
switch (node.kind) {
|
|
@@ -394,6 +435,7 @@ window.__ModuleLoader__.load({
|
|
|
394
435
|
cls: meta.cls,
|
|
395
436
|
preview: previewFor(node),
|
|
396
437
|
search: dialogueText(node),
|
|
438
|
+
full: fullDialogueText(node),
|
|
397
439
|
time: fmtTime(timeOf(node)),
|
|
398
440
|
};
|
|
399
441
|
const turn = turnOf(node);
|
|
@@ -521,6 +563,7 @@ window.__ModuleLoader__.load({
|
|
|
521
563
|
const snap = useStore();
|
|
522
564
|
const [filter, setFilter] = React.useState("");
|
|
523
565
|
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
566
|
+
const [turnsHidden, setTurnsHidden] = React.useState(false);
|
|
524
567
|
function toggleSearch() {
|
|
525
568
|
if (searchOpen) setFilter("");
|
|
526
569
|
setSearchOpen(!searchOpen);
|
|
@@ -646,15 +689,37 @@ window.__ModuleLoader__.load({
|
|
|
646
689
|
}
|
|
647
690
|
function renderGroupHead(g, groupActive, chevron) {
|
|
648
691
|
const headSearch = g.items.length > 0 ? g.items[0].search : null;
|
|
692
|
+
const headFull = g.headIsUser && g.items.length > 0 ? g.items[0].full : null;
|
|
649
693
|
const useSnippet = filtering && hit(headSearch);
|
|
650
694
|
const sub = useSnippet ? snippetNodes(headSearch, kw, 15, 40) : g.subtitle;
|
|
651
695
|
const hasSub = sub !== "" && (!Array.isArray(sub) || sub.length > 0);
|
|
696
|
+
/* 标题策略:
|
|
697
|
+
显示轮次: 轮次行 = 加粗「第 N 轮」; 压缩等非轮次行 = 加粗类型标签, 视作轮次同级
|
|
698
|
+
隐藏轮次: 每行统一轨迹徽标 — 轮次头 = 「用户」业务蓝(助手开头则助手紫), 非轮次行 = 各自徽标(压缩=中性灰) */
|
|
699
|
+
let titleNode = null;
|
|
700
|
+
if (g.turn === null) {
|
|
701
|
+
if (turnsHidden && g.items.length > 0) {
|
|
702
|
+
const headItem = g.items[0];
|
|
703
|
+
titleNode = React.createElement("span", {
|
|
704
|
+
className: "cnvnav-badge cnvnav-badge-" + headItem.cls,
|
|
705
|
+
}, headItem.label);
|
|
706
|
+
} else {
|
|
707
|
+
titleNode = React.createElement("span", { className: "cnvnav-group-title" }, g.title);
|
|
708
|
+
}
|
|
709
|
+
} else if (!turnsHidden) {
|
|
710
|
+
titleNode = React.createElement("span", { className: "cnvnav-group-title" }, g.title);
|
|
711
|
+
} else if (g.items.length > 0) {
|
|
712
|
+
const headItem = g.items[0];
|
|
713
|
+
titleNode = React.createElement("span", {
|
|
714
|
+
className: "cnvnav-badge cnvnav-badge-" + headItem.cls,
|
|
715
|
+
}, headItem.kind === "user" ? "用户" : headItem.label);
|
|
716
|
+
}
|
|
652
717
|
return React.createElement("div", {
|
|
653
718
|
ref: (el) => { groupEls[g.gid] = el; },
|
|
654
719
|
className: "cnvnav-group-row" + (groupActive ? " cnvnav-group-row-active" : ""),
|
|
655
720
|
},
|
|
656
721
|
React.createElement(Tooltip, {
|
|
657
|
-
label: "跳转到此位置",
|
|
722
|
+
label: headFull !== null ? headFull : "跳转到此位置", maxWidth: 340,
|
|
658
723
|
side: "top",
|
|
659
724
|
delayMs: 500,
|
|
660
725
|
},
|
|
@@ -664,7 +729,7 @@ window.__ModuleLoader__.load({
|
|
|
664
729
|
className: "cnvnav-group-head",
|
|
665
730
|
onClick: () => jumpTo(g.headKey),
|
|
666
731
|
},
|
|
667
|
-
|
|
732
|
+
titleNode,
|
|
668
733
|
hasSub
|
|
669
734
|
? React.createElement("span", { className: "cnvnav-group-sub" }, sub)
|
|
670
735
|
: null,
|
|
@@ -776,6 +841,23 @@ window.__ModuleLoader__.load({
|
|
|
776
841
|
}),
|
|
777
842
|
),
|
|
778
843
|
),
|
|
844
|
+
searchOpen
|
|
845
|
+
? null
|
|
846
|
+
: React.createElement(Tooltip, {
|
|
847
|
+
label: turnsHidden ? "显示轮次" : "隐藏轮次",
|
|
848
|
+
side: "bottom",
|
|
849
|
+
delayMs: 500,
|
|
850
|
+
},
|
|
851
|
+
React.createElement("span", { className: "cnvnav-turns-toggle" },
|
|
852
|
+
React.createElement(Button, {
|
|
853
|
+
variant: turnsHidden ? "primary" : "ghost",
|
|
854
|
+
size: "sm",
|
|
855
|
+
icon: React.createElement(SwitchTurnsIcon),
|
|
856
|
+
"aria-pressed": turnsHidden,
|
|
857
|
+
onClick: () => setTurnsHidden(!turnsHidden),
|
|
858
|
+
}),
|
|
859
|
+
),
|
|
860
|
+
),
|
|
779
861
|
searchOpen
|
|
780
862
|
? React.createElement("input", {
|
|
781
863
|
type: "text",
|