dsh-conversation-navigator 0.1.5 → 0.1.7
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 +11 -7
- package/lib/client.js +61 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# DSH 会话导航插件(dsh-conversation-navigator)
|
|
2
2
|
|
|
3
|
-
Codex 风格的 **DeepSeek Harness(DSH)Web
|
|
3
|
+
Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页**右侧**悬浮展示按轮折叠的对话大纲,点击任意节点平滑跳转,滚动对话时实时高亮当前阅读位置,步次徽标配色与内置"轨迹"视图统一。
|
|
4
4
|
|
|
5
|
-
纯浏览器插件(无宿主行为)、纯 JavaScript
|
|
5
|
+
纯浏览器插件(无宿主行为)、纯 JavaScript、零构建、零 npm 依赖(按钮/图标复用 DSH 内核 seed 的官方 primitives)。
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|
- **按轮折叠大纲**:默认只显示"第 N 轮 + 你的问题"列表,长对话一目了然
|
|
10
10
|
- **展开/折叠步次**:点击轮次行右侧的 `▸ N` / `▾` 展开或折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
|
|
11
11
|
- **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
|
|
12
|
+
- **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
|
|
12
13
|
- **位置跟踪**:手动滚动对话时,面板自动高亮并跟随当前正在阅读的轮次
|
|
14
|
+
- **右侧定位**:面板锚定视口右侧,收起/展开左侧边栏时纹丝不动
|
|
13
15
|
- **回到最新 / 全部折叠**:面板底部两个快捷按钮
|
|
14
|
-
- **轨迹配色**:用户/插话 = 业务蓝、上下文 = 成功绿、助手 = 紫罗兰、工具 = 琥珀、压缩 = 中性灰(
|
|
16
|
+
- **轨迹配色**:用户/插话 = 业务蓝、上下文 = 成功绿、助手 = 紫罗兰、工具 = 琥珀、压缩 = 中性灰(取自 `dsh-client-ui-trajectory` 的 kindTag 主题 token,自动适配明暗主题)
|
|
17
|
+
- **DSH 原生按钮与图标**:所有操作按钮复用官方 `Button` 组件与 `ic_ds_*` 图标集,与 DSH 界面完全同款
|
|
15
18
|
- 切换工作区/会话自动跟随并重建大纲
|
|
16
19
|
|
|
17
20
|
## 安装
|
|
@@ -27,7 +30,7 @@ dsh plugin --profile web add github:gjj-star/dsh-conversation-navigato
|
|
|
27
30
|
|
|
28
31
|
# 方式三:本地 tarball
|
|
29
32
|
pnpm pack
|
|
30
|
-
dsh plugin --profile web add ./dsh-conversation-navigator
|
|
33
|
+
dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
31
34
|
```
|
|
32
35
|
|
|
33
36
|
`dsh plugin` 在 profile 目录内转发给 pnpm,因此需要 **pnpm 在 PATH 上**;安装会自动把本包追加进 profile 的 `dsh.profile.bundles`,其自带的 `cordis.patch.yml` 层负责插入插件行。重启 `dsh web` 后生效,面板默认展开。
|
|
@@ -40,15 +43,16 @@ dsh plugin --profile web add ./dsh-conversation-navigator-0.1.0.tgz
|
|
|
40
43
|
|
|
41
44
|
## 工作原理
|
|
42
45
|
|
|
43
|
-
- 注册槽位:`conversation.session.header.utilities`(
|
|
46
|
+
- 注册槽位:`conversation.session.header.utilities`(标题栏「导航」开关)+ `shell.overlay`(浮动面板)
|
|
44
47
|
- 数据来源:会话级标准 props 的 `useSession`(选择 `ConversationSnapshot.chat` 的稳定渲染顺序 `order` + `ChatNodeStore`),按 `node.location` 的 turn 分组
|
|
48
|
+
- 历史补载:通过 `sessions` 服务的 `binding(sessionId).session.loadOlder()` 分页向后加载,「加载全部」循环至 `hasMore=false`
|
|
45
49
|
- 跳转定位:复用 DSH 聊天视图自身的稳定 DOM 锚点 `[data-chat-anchor-key]`(与产品内部 paging/scroll 定位同源),`scrollIntoView` 平滑滚动
|
|
46
50
|
- 位置跟踪:捕获 `[data-conversation-scroll]` 滚动容器的 scroll 事件(节流 120ms),计算视口顶部首个可见节点
|
|
47
|
-
-
|
|
51
|
+
- 样式:按钮/图标复用 `@deepseek-ai/dsh-client-ui-primitives`;面板容器自建 `<style>` 注入,颜色使用 `--dsw-*` 主题 token;插件卸载时随 fiber 清理
|
|
48
52
|
|
|
49
53
|
## 兼容性说明
|
|
50
54
|
|
|
51
|
-
- 目标平台:DSH Web 端(`dsh.client.platform: web`),依赖内核 seed 的 `react`
|
|
55
|
+
- 目标平台:DSH Web 端(`dsh.client.platform: web`),依赖内核 seed 的 `react`、`slots`、`sessions` 服务与 `@deepseek-ai/dsh-client-ui-primitives`,以及 `dsh-client-runtime`、`dsh-client-ui-conversation` 提供的标准能力(`dsh.client.inject` 已声明 runtime 与 conversation)
|
|
52
56
|
- **版本敏感点**:`[data-chat-anchor-key]` / `[data-conversation-scroll]` 是当前 DSH 聊天视图的 DOM 锚点约定,DSH 升级后若锚点变化,只需调整 `lib/client.js` 中 `findAnchor` / `computeActiveKey` 两个函数
|
|
53
57
|
- 未声明 `timer` 硬依赖:客户端的 timer 服务存在则用于节流,不存在时自动退化为未节流模式
|
|
54
58
|
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,62 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
const React = require("react");
|
|
8
|
-
const { Button,
|
|
8
|
+
const { Button, IconCloseOutline16 } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
|
|
10
|
+
/* ---------- custom icons (DSH outline style, unique to this panel) ---------- */
|
|
11
|
+
function ToggleIcon() {
|
|
12
|
+
return React.createElement("svg", {
|
|
13
|
+
viewBox: "0 0 14 14", width: 14, height: 14, fill: "none",
|
|
14
|
+
stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round",
|
|
15
|
+
"aria-hidden": true,
|
|
16
|
+
},
|
|
17
|
+
React.createElement("circle", { cx: 3.5, cy: 3, r: 1, fill: "currentColor", stroke: "none" }),
|
|
18
|
+
React.createElement("path", { d: "M6.5 3H12" }),
|
|
19
|
+
React.createElement("circle", { cx: 3.5, cy: 7, r: 1, fill: "currentColor", stroke: "none" }),
|
|
20
|
+
React.createElement("path", { d: "M6.5 7H12" }),
|
|
21
|
+
React.createElement("circle", { cx: 3.5, cy: 11, r: 1, fill: "currentColor", stroke: "none" }),
|
|
22
|
+
React.createElement("path", { d: "M6.5 11H12" }),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function LoadEarlierIcon() {
|
|
26
|
+
return React.createElement("svg", {
|
|
27
|
+
viewBox: "0 0 14 14", width: 14, height: 14, fill: "none",
|
|
28
|
+
stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round",
|
|
29
|
+
"aria-hidden": true,
|
|
30
|
+
},
|
|
31
|
+
React.createElement("path", { d: "M7 11V5M4 7.5l3-3 3 3" }),
|
|
32
|
+
React.createElement("line", { x1: 2, y1: 3, x2: 12, y2: 3 }),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function LoadAllIcon() {
|
|
36
|
+
return React.createElement("svg", {
|
|
37
|
+
viewBox: "0 0 14 14", width: 14, height: 14, fill: "none",
|
|
38
|
+
stroke: "currentColor", strokeWidth: 1.3, strokeLinecap: "round", strokeLinejoin: "round",
|
|
39
|
+
"aria-hidden": true,
|
|
40
|
+
},
|
|
41
|
+
React.createElement("path", { d: "M7 10V6M4.5 8l2.5-2.5 2.5 2.5" }),
|
|
42
|
+
React.createElement("path", { d: "M7 5.5V1.5M4.5 3.5l2.5-2.5 2.5 2.5" }),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function JumpLatestIcon() {
|
|
46
|
+
return React.createElement("svg", {
|
|
47
|
+
viewBox: "0 0 14 14", width: 14, height: 14, fill: "none",
|
|
48
|
+
stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round",
|
|
49
|
+
"aria-hidden": true,
|
|
50
|
+
},
|
|
51
|
+
React.createElement("path", { d: "M7 3V9M4 7l3 3 3-3" }),
|
|
52
|
+
React.createElement("line", { x1: 2, y1: 11, x2: 12, y2: 11 }),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function CollapseAllIcon() {
|
|
56
|
+
return React.createElement("svg", {
|
|
57
|
+
viewBox: "0 0 14 14", width: 14, height: 14, fill: "none",
|
|
58
|
+
stroke: "currentColor", strokeWidth: 1.3, strokeLinecap: "round", strokeLinejoin: "round",
|
|
59
|
+
"aria-hidden": true,
|
|
60
|
+
},
|
|
61
|
+
React.createElement("path", { d: "M5 6l2 2.5 2-2.5M5 8l2-2.5 2 2.5" }),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
9
64
|
|
|
10
65
|
/* ---------- own stylesheet (static packages have no styles builtin) ---------- */
|
|
11
66
|
const CSS = `
|
|
@@ -389,7 +444,7 @@ window.__ModuleLoader__.load({
|
|
|
389
444
|
return React.createElement(Button, {
|
|
390
445
|
variant: snap.open ? "primary" : "ghost",
|
|
391
446
|
size: "sm",
|
|
392
|
-
icon: React.createElement(
|
|
447
|
+
icon: React.createElement(ToggleIcon),
|
|
393
448
|
"aria-pressed": snap.open,
|
|
394
449
|
title: snap.open ? "收起会话导航" : "打开会话导航",
|
|
395
450
|
onClick: () => setState({ open: !snap.open }),
|
|
@@ -545,7 +600,7 @@ window.__ModuleLoader__.load({
|
|
|
545
600
|
variant: "ghost",
|
|
546
601
|
size: "sm",
|
|
547
602
|
className: "cnvnav-bar-btn",
|
|
548
|
-
icon: React.createElement(
|
|
603
|
+
icon: React.createElement(LoadEarlierIcon),
|
|
549
604
|
disabled: !snap.hasMore || snap.loadingOlder,
|
|
550
605
|
title: "加载更早的一批轮次",
|
|
551
606
|
onClick: () => loadOlderPage(),
|
|
@@ -554,7 +609,7 @@ window.__ModuleLoader__.load({
|
|
|
554
609
|
variant: "ghost",
|
|
555
610
|
size: "sm",
|
|
556
611
|
className: "cnvnav-bar-btn",
|
|
557
|
-
icon: React.createElement(
|
|
612
|
+
icon: React.createElement(LoadAllIcon),
|
|
558
613
|
disabled: !snap.hasMore || snap.loadingOlder || snap.loadingAll,
|
|
559
614
|
title: "加载全部历史轮次",
|
|
560
615
|
onClick: () => loadAllPages(),
|
|
@@ -573,7 +628,7 @@ window.__ModuleLoader__.load({
|
|
|
573
628
|
variant: "ghost",
|
|
574
629
|
size: "sm",
|
|
575
630
|
className: "cnvnav-bar-btn",
|
|
576
|
-
icon: React.createElement(
|
|
631
|
+
icon: React.createElement(JumpLatestIcon),
|
|
577
632
|
onClick: () => {
|
|
578
633
|
const gs = snap.outline !== null ? snap.outline.groups : [];
|
|
579
634
|
if (gs.length === 0) return;
|
|
@@ -586,7 +641,7 @@ window.__ModuleLoader__.load({
|
|
|
586
641
|
variant: "ghost",
|
|
587
642
|
size: "sm",
|
|
588
643
|
className: "cnvnav-bar-btn",
|
|
589
|
-
icon: React.createElement(
|
|
644
|
+
icon: React.createElement(CollapseAllIcon),
|
|
590
645
|
title: "折叠所有已展开的轮次",
|
|
591
646
|
onClick: () => setState({ expanded: {} }),
|
|
592
647
|
}, "全部折叠"),
|