dsh-conversation-navigator 0.1.17 → 0.1.18
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 +3 -2
- package/lib/client.js +90 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页*
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|
- **按轮折叠大纲**:默认只显示"第 N 轮 + 你的问题"列表,长对话一目了然
|
|
10
|
-
-
|
|
11
|
-
-
|
|
10
|
+
- **关键词过滤**:点击左上角搜索图标呼出输入框,只按「你的提问 + 助手实际回复文本」过滤(上下文、工具、命令、压缩、推理等不参与匹配),命中词高亮,列表文本自动定位到关键词处
|
|
11
|
+
- **展开/折叠步次**:点击轮次行右侧的箭头按钮(`▸ N`,展开时旋转为 `▾`)平滑展开/折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
|
|
12
|
+
- **丝滑动效**:面板开合淡入淡出、步次展开/折叠高度过渡、过滤结果逐条级联淡入、折叠箭头旋转——全部纯 CSS 实现,零依赖
|
|
12
13
|
- **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
|
|
13
14
|
- **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
|
|
14
15
|
- **位置跟踪**:手动滚动对话时,面板自动高亮并跟随当前正在阅读的轮次
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,7 @@ 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, Tooltip, IconCloseOutline16 } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
8
|
+
const { Button, Tooltip, IconCloseOutline16, IconSearchOutline16 } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
|
|
10
10
|
/* ---------- custom icons (DSH outline style, unique to this panel) ---------- */
|
|
11
11
|
function ToggleIcon() {
|
|
@@ -79,24 +79,27 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
font-size: 12px;
|
|
80
80
|
color: var(--dsw-alias-label-primary, #222);
|
|
81
81
|
overflow: hidden;
|
|
82
|
+
transition: opacity .18s ease, transform .18s ease;
|
|
82
83
|
}
|
|
84
|
+
.cnvnav-panel-hidden { opacity: 0; transform: translateY(-6px) scale(.985); pointer-events: none; }
|
|
83
85
|
.cnvnav-head {
|
|
84
86
|
display: flex; align-items: center; gap: 8px;
|
|
85
87
|
padding: 10px 12px;
|
|
86
88
|
border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
|
|
87
89
|
}
|
|
88
|
-
.cnvnav-title { font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif); font-size: 16px; font-weight: 700; line-height: 22px; }
|
|
89
90
|
.cnvnav-count { color: var(--dsw-alias-label-secondary, #888); font-size: 11px; }
|
|
91
|
+
.cnvnav-search { flex: none; display: flex; }
|
|
90
92
|
.cnvnav-close { margin-left: auto; }
|
|
91
93
|
.cnvnav-list {
|
|
92
94
|
overflow-y: auto; padding: 6px; min-height: 0;
|
|
93
95
|
display: flex; flex-direction: column; gap: 2px;
|
|
94
96
|
scrollbar-width: thin;
|
|
95
97
|
}
|
|
96
|
-
.cnvnav-group { display: flex; flex-direction: column;
|
|
98
|
+
.cnvnav-group { display: flex; flex-direction: column; margin-bottom: 2px; }
|
|
97
99
|
.cnvnav-group-row {
|
|
98
100
|
display: flex; align-items: stretch;
|
|
99
101
|
border-radius: 8px; overflow: hidden;
|
|
102
|
+
transition: background .16s ease;
|
|
100
103
|
}
|
|
101
104
|
.cnvnav-group-row:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
102
105
|
.cnvnav-group-row-active { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
@@ -109,7 +112,7 @@ window.__ModuleLoader__.load({
|
|
|
109
112
|
padding: 5px 0 5px 8px;
|
|
110
113
|
color: var(--dsw-alias-label-primary, #222);
|
|
111
114
|
}
|
|
112
|
-
.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; }
|
|
115
|
+
.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; }
|
|
113
116
|
.cnvnav-group-sub {
|
|
114
117
|
color: var(--dsw-alias-label-secondary, #888);
|
|
115
118
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
@@ -118,18 +121,28 @@ window.__ModuleLoader__.load({
|
|
|
118
121
|
.cnvnav-chevron-wrap { flex: none; display: flex; }
|
|
119
122
|
.cnvnav-chevron {
|
|
120
123
|
min-width: 30px; align-self: stretch;
|
|
121
|
-
display: flex; align-items: center; justify-content: center;
|
|
124
|
+
display: flex; align-items: center; justify-content: center; gap: 3px;
|
|
122
125
|
border: none; background: transparent; cursor: pointer;
|
|
123
126
|
color: var(--dsw-alias-label-secondary, #888);
|
|
124
127
|
font-size: 11px; line-height: 1; padding: 0 8px;
|
|
125
128
|
white-space: nowrap;
|
|
129
|
+
transition: color .16s ease, background .16s ease;
|
|
126
130
|
}
|
|
127
131
|
.cnvnav-chevron:hover { color: var(--dsw-alias-brand-primary, #4f46e5); background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
132
|
+
.cnvnav-chevron-icon { flex: none; display: block; transition: transform .2s cubic-bezier(.4, 0, .2, 1); }
|
|
133
|
+
.cnvnav-chevron[aria-expanded="true"] .cnvnav-chevron-icon { transform: rotate(90deg); }
|
|
134
|
+
.cnvnav-chevron-count { font-size: 11px; line-height: 1; }
|
|
135
|
+
.cnvnav-steps { display: grid; grid-template-rows: 1fr; margin-top: 2px; transition: grid-template-rows .24s cubic-bezier(.4, 0, .2, 1), margin-top .24s cubic-bezier(.4, 0, .2, 1); }
|
|
136
|
+
.cnvnav-steps-collapsed { grid-template-rows: 0fr; margin-top: 0; }
|
|
137
|
+
.cnvnav-steps-inner { overflow: hidden; min-height: 0; display: flex; flex-direction: column; gap: 2px; }
|
|
138
|
+
.cnvnav-fade-item { animation: cnvnav-fade-up .2s ease-out backwards; }
|
|
139
|
+
@keyframes cnvnav-fade-up { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
128
140
|
.cnvnav-item {
|
|
129
141
|
display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
|
|
130
142
|
border: none; background: transparent; cursor: pointer;
|
|
131
143
|
padding: 5px 8px 5px 22px; border-radius: 8px; position: relative;
|
|
132
144
|
color: var(--dsw-alias-label-primary, #222);
|
|
145
|
+
transition: background .16s ease;
|
|
133
146
|
}
|
|
134
147
|
.cnvnav-item:hover { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
135
148
|
.cnvnav-item-active { background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12)); }
|
|
@@ -167,9 +180,8 @@ window.__ModuleLoader__.load({
|
|
|
167
180
|
color: inherit; border-radius: 2px; padding: 0 1px; font-weight: 600;
|
|
168
181
|
}
|
|
169
182
|
.cnvnav-empty { padding: 18px 10px; text-align: center; color: var(--dsw-alias-label-secondary, #888); }
|
|
170
|
-
.cnvnav-filter-row { padding: 6px 6px 0; }
|
|
171
183
|
.cnvnav-filter {
|
|
172
|
-
width:
|
|
184
|
+
flex: 1; min-width: 0; box-sizing: border-box;
|
|
173
185
|
height: 28px; padding: 0 8px;
|
|
174
186
|
border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
|
|
175
187
|
border-radius: 8px;
|
|
@@ -177,9 +189,14 @@ window.__ModuleLoader__.load({
|
|
|
177
189
|
color: var(--dsw-alias-label-primary, #222);
|
|
178
190
|
font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif);
|
|
179
191
|
font-size: 12px; outline: none;
|
|
192
|
+
animation: cnvnav-search-in .16s ease-out;
|
|
180
193
|
}
|
|
181
194
|
.cnvnav-filter::placeholder { color: var(--dsw-alias-label-secondary, #888); }
|
|
182
195
|
.cnvnav-filter:focus { border-color: var(--dsw-alias-brand-primary, #4f46e5); }
|
|
196
|
+
@keyframes cnvnav-search-in {
|
|
197
|
+
from { opacity: 0; transform: translateX(4px); }
|
|
198
|
+
to { opacity: 1; transform: translateX(0); }
|
|
199
|
+
}
|
|
183
200
|
.cnvnav-topbar { display: flex; gap: 6px; margin: 6px 6px 0; }
|
|
184
201
|
.cnvnav-footer { display: flex; gap: 6px; margin: 0 6px 6px; }
|
|
185
202
|
.cnvnav-bar-btn { flex: 1; display: block; }
|
|
@@ -503,6 +520,11 @@ window.__ModuleLoader__.load({
|
|
|
503
520
|
function Panel() {
|
|
504
521
|
const snap = useStore();
|
|
505
522
|
const [filter, setFilter] = React.useState("");
|
|
523
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
524
|
+
function toggleSearch() {
|
|
525
|
+
if (searchOpen) setFilter("");
|
|
526
|
+
setSearchOpen(!searchOpen);
|
|
527
|
+
}
|
|
506
528
|
|
|
507
529
|
React.useEffect(() => {
|
|
508
530
|
if (!snap.open) return;
|
|
@@ -547,7 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
547
569
|
React.useEffect(() => {
|
|
548
570
|
if (!snap.open || snap.activeKey === null) return;
|
|
549
571
|
let target = itemEls[snap.activeKey];
|
|
550
|
-
if (target === undefined || target === null) {
|
|
572
|
+
if (target === undefined || target === null || (typeof target.closest === "function" && target.closest(".cnvnav-steps-collapsed") !== null)) {
|
|
551
573
|
const gid = snap.outline !== null ? snap.outline.byKey[snap.activeKey] : undefined;
|
|
552
574
|
if (gid !== undefined) target = groupEls[gid];
|
|
553
575
|
}
|
|
@@ -557,13 +579,11 @@ window.__ModuleLoader__.load({
|
|
|
557
579
|
const er = target.getBoundingClientRect();
|
|
558
580
|
const rel = er.top - lr.top + list.scrollTop;
|
|
559
581
|
if (rel < list.scrollTop || rel + er.height > list.scrollTop + list.clientHeight) {
|
|
560
|
-
list.
|
|
582
|
+
list.scrollTo({ top: Math.max(0, rel - list.clientHeight / 2), behavior: "smooth" });
|
|
561
583
|
}
|
|
562
584
|
}
|
|
563
585
|
}, [snap.open, snap.activeKey]);
|
|
564
586
|
|
|
565
|
-
if (!snap.open) return null;
|
|
566
|
-
|
|
567
587
|
const groups = snap.outline !== null ? snap.outline.groups : [];
|
|
568
588
|
const count = snap.outline !== null ? snap.outline.count : 0;
|
|
569
589
|
|
|
@@ -603,7 +623,7 @@ window.__ModuleLoader__.load({
|
|
|
603
623
|
if (end < text.length) parts.push("…");
|
|
604
624
|
return parts;
|
|
605
625
|
}
|
|
606
|
-
function renderItem(item) {
|
|
626
|
+
function renderItem(item, staggerMs) {
|
|
607
627
|
const useSnippet = filtering && hit(item.search);
|
|
608
628
|
const preview = useSnippet ? snippetNodes(item.search, kw, 20, 50) : item.preview;
|
|
609
629
|
const hasPreview = preview !== null && preview !== "" && (!Array.isArray(preview) || preview.length > 0);
|
|
@@ -611,7 +631,8 @@ window.__ModuleLoader__.load({
|
|
|
611
631
|
key: item.key,
|
|
612
632
|
type: "button",
|
|
613
633
|
ref: (el) => { itemEls[item.key] = el; },
|
|
614
|
-
className: "cnvnav-item" + (item.key === snap.activeKey ? " cnvnav-item-active" : ""),
|
|
634
|
+
className: "cnvnav-item" + (item.key === snap.activeKey ? " cnvnav-item-active" : "") + (staggerMs !== undefined ? " cnvnav-fade-item" : ""),
|
|
635
|
+
style: staggerMs !== undefined ? { animationDelay: staggerMs + "ms" } : undefined,
|
|
615
636
|
onClick: () => jumpTo(item.key),
|
|
616
637
|
},
|
|
617
638
|
React.createElement("span", { className: "cnvnav-badge cnvnav-badge-" + item.cls }, item.label),
|
|
@@ -666,9 +687,19 @@ window.__ModuleLoader__.load({
|
|
|
666
687
|
);
|
|
667
688
|
if (!headHit && hits.length === 0) continue;
|
|
668
689
|
const groupActive = g.items.some((it) => it.key === snap.activeKey);
|
|
669
|
-
groupNodes.push(React.createElement("div", {
|
|
690
|
+
groupNodes.push(React.createElement("div", {
|
|
691
|
+
key: g.gid,
|
|
692
|
+
className: "cnvnav-group cnvnav-fade-item",
|
|
693
|
+
style: { animationDelay: (groupNodes.length * 18) + "ms" },
|
|
694
|
+
},
|
|
670
695
|
renderGroupHead(g, groupActive, null),
|
|
671
|
-
hits.
|
|
696
|
+
hits.length > 0
|
|
697
|
+
? React.createElement("div", { className: "cnvnav-steps" },
|
|
698
|
+
React.createElement("div", { className: "cnvnav-steps-inner" },
|
|
699
|
+
hits.map((it, i) => renderItem(it, i * 20)),
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
: null,
|
|
672
703
|
));
|
|
673
704
|
}
|
|
674
705
|
} else {
|
|
@@ -694,15 +725,30 @@ window.__ModuleLoader__.load({
|
|
|
694
725
|
setState({ expanded: Object.assign({}, state.expanded, { [g.gid]: !expanded }) });
|
|
695
726
|
},
|
|
696
727
|
},
|
|
697
|
-
|
|
728
|
+
React.createElement("svg", {
|
|
729
|
+
className: "cnvnav-chevron-icon",
|
|
730
|
+
viewBox: "0 0 12 12", width: 12, height: 12, fill: "none",
|
|
731
|
+
stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round",
|
|
732
|
+
"aria-hidden": true,
|
|
733
|
+
},
|
|
734
|
+
React.createElement("path", { d: "M4.5 2.5 8 6l-3.5 3.5" }),
|
|
735
|
+
),
|
|
736
|
+
expanded ? null : React.createElement("span", { className: "cnvnav-chevron-count" }, String(stepItems.length)),
|
|
698
737
|
),
|
|
699
738
|
),
|
|
700
739
|
)
|
|
701
740
|
: null;
|
|
702
741
|
return React.createElement("div", { key: g.gid, className: "cnvnav-group" },
|
|
703
742
|
renderGroupHead(g, groupActive, chevron),
|
|
704
|
-
|
|
705
|
-
?
|
|
743
|
+
hasSteps
|
|
744
|
+
? React.createElement("div", {
|
|
745
|
+
className: "cnvnav-steps" + (expanded ? "" : " cnvnav-steps-collapsed"),
|
|
746
|
+
"aria-hidden": !expanded,
|
|
747
|
+
},
|
|
748
|
+
React.createElement("div", { className: "cnvnav-steps-inner" },
|
|
749
|
+
stepItems.map(renderItem),
|
|
750
|
+
),
|
|
751
|
+
)
|
|
706
752
|
: null,
|
|
707
753
|
);
|
|
708
754
|
});
|
|
@@ -710,12 +756,36 @@ window.__ModuleLoader__.load({
|
|
|
710
756
|
|
|
711
757
|
return React.createElement("div", {
|
|
712
758
|
ref: (el) => { panelEl = el; },
|
|
713
|
-
className: "cnvnav-panel",
|
|
759
|
+
className: "cnvnav-panel" + (snap.open ? "" : " cnvnav-panel-hidden"),
|
|
714
760
|
role: "complementary",
|
|
715
761
|
"aria-label": "会话导航",
|
|
762
|
+
"aria-hidden": !snap.open,
|
|
716
763
|
},
|
|
717
764
|
React.createElement("div", { className: "cnvnav-head" },
|
|
718
|
-
React.createElement(
|
|
765
|
+
React.createElement(Tooltip, {
|
|
766
|
+
label: searchOpen ? "收起搜索" : "搜索关键词",
|
|
767
|
+
side: "bottom",
|
|
768
|
+
delayMs: 500,
|
|
769
|
+
},
|
|
770
|
+
React.createElement("span", { className: "cnvnav-search" },
|
|
771
|
+
React.createElement(Button, {
|
|
772
|
+
variant: "ghost",
|
|
773
|
+
size: "sm",
|
|
774
|
+
icon: React.createElement(IconSearchOutline16, { size: 14 }),
|
|
775
|
+
onClick: () => toggleSearch(),
|
|
776
|
+
}),
|
|
777
|
+
),
|
|
778
|
+
),
|
|
779
|
+
searchOpen
|
|
780
|
+
? React.createElement("input", {
|
|
781
|
+
type: "text",
|
|
782
|
+
className: "cnvnav-filter",
|
|
783
|
+
placeholder: "请输入关键词",
|
|
784
|
+
value: filter,
|
|
785
|
+
autoFocus: true,
|
|
786
|
+
onChange: (e) => setFilter(e.target.value),
|
|
787
|
+
})
|
|
788
|
+
: null,
|
|
719
789
|
React.createElement("span", { className: "cnvnav-count" }, (filtering ? groupNodes.length + " / " : "") + String(count) + " 条"),
|
|
720
790
|
React.createElement(Tooltip, {
|
|
721
791
|
label: "收起面板",
|
|
@@ -732,15 +802,6 @@ window.__ModuleLoader__.load({
|
|
|
732
802
|
),
|
|
733
803
|
),
|
|
734
804
|
),
|
|
735
|
-
React.createElement("div", { className: "cnvnav-filter-row" },
|
|
736
|
-
React.createElement("input", {
|
|
737
|
-
type: "text",
|
|
738
|
-
className: "cnvnav-filter",
|
|
739
|
-
placeholder: "过滤提问 / 回复…",
|
|
740
|
-
value: filter,
|
|
741
|
-
onChange: (e) => setFilter(e.target.value),
|
|
742
|
-
}),
|
|
743
|
-
),
|
|
744
805
|
React.createElement("div", { className: "cnvnav-topbar" },
|
|
745
806
|
React.createElement(Tooltip, {
|
|
746
807
|
label: "加载更早的一批轮次",
|