dsh-conversation-navigator 0.1.16 → 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 +5 -2
- package/lib/client.js +182 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页*
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|
- **按轮折叠大纲**:默认只显示"第 N 轮 + 你的问题"列表,长对话一目了然
|
|
10
|
-
-
|
|
10
|
+
- **关键词过滤**:点击左上角搜索图标呼出输入框,只按「你的提问 + 助手实际回复文本」过滤(上下文、工具、命令、压缩、推理等不参与匹配),命中词高亮,列表文本自动定位到关键词处
|
|
11
|
+
- **展开/折叠步次**:点击轮次行右侧的箭头按钮(`▸ N`,展开时旋转为 `▾`)平滑展开/折叠该轮的步次明细(助手回复、工具调用、命令、压缩点等)
|
|
12
|
+
- **丝滑动效**:面板开合淡入淡出、步次展开/折叠高度过渡、过滤结果逐条级联淡入、折叠箭头旋转——全部纯 CSS 实现,零依赖
|
|
11
13
|
- **点击定位**:点击轮次主体或步次条目,平滑滚动跳转到对话中对应位置(不改变折叠状态)
|
|
12
14
|
- **加载更早 / 加载全部**:面板顶部两个按钮——「加载更早」向后翻一页、「加载全部」一键把所有历史轮次载入导航,之后可任意跳转(页面默认仍懒加载,只有点按钮才补载)
|
|
13
15
|
- **位置跟踪**:手动滚动对话时,面板自动高亮并跟随当前正在阅读的轮次
|
|
@@ -26,7 +28,7 @@ Codex 风格的 **DeepSeek Harness(DSH)Web 端会话导航面板**:在对话页*
|
|
|
26
28
|
dsh plugin --profile web add dsh-conversation-navigator
|
|
27
29
|
|
|
28
30
|
# 方式二:GitHub(纯 JS 包无需 prepare/allowBuilds,直接可用)
|
|
29
|
-
dsh plugin --profile web add github:gjj-star/dsh-conversation-
|
|
31
|
+
dsh plugin --profile web add github:gjj-star/dsh-conversation-navigator
|
|
30
32
|
|
|
31
33
|
# 方式三:本地 tarball
|
|
32
34
|
pnpm pack
|
|
@@ -48,6 +50,7 @@ dsh plugin --profile web add ./dsh-conversation-navigator-<version>.tgz
|
|
|
48
50
|
- 历史补载:通过 `sessions` 服务的 `binding(sessionId).session.loadOlder()` 分页向后加载,「加载全部」循环至 `hasMore=false`
|
|
49
51
|
- 跳转定位:复用 DSH 聊天视图自身的稳定 DOM 锚点 `[data-chat-anchor-key]`(与产品内部 paging/scroll 定位同源),`scrollIntoView` 平滑滚动
|
|
50
52
|
- 位置跟踪:捕获 `[data-conversation-scroll]` 滚动容器的 scroll 事件(节流 120ms),计算视口顶部首个可见节点
|
|
53
|
+
- 关键词过滤:仅为 `user` 与 `assistant-step` 节点提取检索文本(`dialogueText`),大小写不敏感匹配,命中片段以 `<mark>` 高亮并按首个命中位置截取显示窗口
|
|
51
54
|
- 样式:按钮/图标复用 `@deepseek-ai/dsh-client-ui-primitives`;面板容器自建 `<style>` 注入,颜色使用 `--dsw-*` 主题 token;插件卸载时随 fiber 清理
|
|
52
55
|
|
|
53
56
|
## 兼容性说明
|
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;
|
|
@@ -117,19 +120,29 @@ window.__ModuleLoader__.load({
|
|
|
117
120
|
}
|
|
118
121
|
.cnvnav-chevron-wrap { flex: none; display: flex; }
|
|
119
122
|
.cnvnav-chevron {
|
|
120
|
-
min-width: 30px;
|
|
121
|
-
display: flex; align-items: center; justify-content: center;
|
|
123
|
+
min-width: 30px; align-self: stretch;
|
|
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)); }
|
|
@@ -162,10 +175,13 @@ window.__ModuleLoader__.load({
|
|
|
162
175
|
.cnvnav-item-time {
|
|
163
176
|
flex: none; color: var(--dsw-alias-label-secondary, #888); font-size: 10px; opacity: .8;
|
|
164
177
|
}
|
|
178
|
+
.cnvnav-hl {
|
|
179
|
+
background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #f59e0b) 30%, transparent);
|
|
180
|
+
color: inherit; border-radius: 2px; padding: 0 1px; font-weight: 600;
|
|
181
|
+
}
|
|
165
182
|
.cnvnav-empty { padding: 18px 10px; text-align: center; color: var(--dsw-alias-label-secondary, #888); }
|
|
166
|
-
.cnvnav-filter-row { padding: 6px 6px 0; }
|
|
167
183
|
.cnvnav-filter {
|
|
168
|
-
width:
|
|
184
|
+
flex: 1; min-width: 0; box-sizing: border-box;
|
|
169
185
|
height: 28px; padding: 0 8px;
|
|
170
186
|
border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
|
|
171
187
|
border-radius: 8px;
|
|
@@ -173,9 +189,14 @@ window.__ModuleLoader__.load({
|
|
|
173
189
|
color: var(--dsw-alias-label-primary, #222);
|
|
174
190
|
font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif);
|
|
175
191
|
font-size: 12px; outline: none;
|
|
192
|
+
animation: cnvnav-search-in .16s ease-out;
|
|
176
193
|
}
|
|
177
194
|
.cnvnav-filter::placeholder { color: var(--dsw-alias-label-secondary, #888); }
|
|
178
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
|
+
}
|
|
179
200
|
.cnvnav-topbar { display: flex; gap: 6px; margin: 6px 6px 0; }
|
|
180
201
|
.cnvnav-footer { display: flex; gap: 6px; margin: 0 6px 6px; }
|
|
181
202
|
.cnvnav-bar-btn { flex: 1; display: block; }
|
|
@@ -265,6 +286,23 @@ window.__ModuleLoader__.load({
|
|
|
265
286
|
}
|
|
266
287
|
return null;
|
|
267
288
|
}
|
|
289
|
+
/* Searchable dialogue text: user questions + agent actual dialogue only
|
|
290
|
+
(excludes context, tool calls, commands, compaction, reasoning, etc.) */
|
|
291
|
+
function dialogueText(node) {
|
|
292
|
+
const d = node && node.data;
|
|
293
|
+
if (!d || typeof d !== "object") return null;
|
|
294
|
+
if (node.kind === "user") return firstText(d.content);
|
|
295
|
+
if (node.kind === "assistant-step") {
|
|
296
|
+
if (!Array.isArray(d.blocks)) return null;
|
|
297
|
+
const parts = [];
|
|
298
|
+
for (const b of d.blocks) {
|
|
299
|
+
if (!b || typeof b !== "object") continue;
|
|
300
|
+
if ((b.type === "text" || b.kind === "text") && typeof b.text === "string" && b.text.trim() !== "") parts.push(b.text.trim());
|
|
301
|
+
}
|
|
302
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
268
306
|
function previewFor(node) {
|
|
269
307
|
const d = node && node.data;
|
|
270
308
|
if (!d || typeof d !== "object") return null;
|
|
@@ -355,6 +393,7 @@ window.__ModuleLoader__.load({
|
|
|
355
393
|
label: meta.label,
|
|
356
394
|
cls: meta.cls,
|
|
357
395
|
preview: previewFor(node),
|
|
396
|
+
search: dialogueText(node),
|
|
358
397
|
time: fmtTime(timeOf(node)),
|
|
359
398
|
};
|
|
360
399
|
const turn = turnOf(node);
|
|
@@ -481,6 +520,11 @@ window.__ModuleLoader__.load({
|
|
|
481
520
|
function Panel() {
|
|
482
521
|
const snap = useStore();
|
|
483
522
|
const [filter, setFilter] = React.useState("");
|
|
523
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
524
|
+
function toggleSearch() {
|
|
525
|
+
if (searchOpen) setFilter("");
|
|
526
|
+
setSearchOpen(!searchOpen);
|
|
527
|
+
}
|
|
484
528
|
|
|
485
529
|
React.useEffect(() => {
|
|
486
530
|
if (!snap.open) return;
|
|
@@ -525,7 +569,7 @@ window.__ModuleLoader__.load({
|
|
|
525
569
|
React.useEffect(() => {
|
|
526
570
|
if (!snap.open || snap.activeKey === null) return;
|
|
527
571
|
let target = itemEls[snap.activeKey];
|
|
528
|
-
if (target === undefined || target === null) {
|
|
572
|
+
if (target === undefined || target === null || (typeof target.closest === "function" && target.closest(".cnvnav-steps-collapsed") !== null)) {
|
|
529
573
|
const gid = snap.outline !== null ? snap.outline.byKey[snap.activeKey] : undefined;
|
|
530
574
|
if (gid !== undefined) target = groupEls[gid];
|
|
531
575
|
}
|
|
@@ -535,13 +579,11 @@ window.__ModuleLoader__.load({
|
|
|
535
579
|
const er = target.getBoundingClientRect();
|
|
536
580
|
const rel = er.top - lr.top + list.scrollTop;
|
|
537
581
|
if (rel < list.scrollTop || rel + er.height > list.scrollTop + list.clientHeight) {
|
|
538
|
-
list.
|
|
582
|
+
list.scrollTo({ top: Math.max(0, rel - list.clientHeight / 2), behavior: "smooth" });
|
|
539
583
|
}
|
|
540
584
|
}
|
|
541
585
|
}, [snap.open, snap.activeKey]);
|
|
542
586
|
|
|
543
|
-
if (!snap.open) return null;
|
|
544
|
-
|
|
545
587
|
const groups = snap.outline !== null ? snap.outline.groups : [];
|
|
546
588
|
const count = snap.outline !== null ? snap.outline.count : 0;
|
|
547
589
|
|
|
@@ -550,24 +592,63 @@ window.__ModuleLoader__.load({
|
|
|
550
592
|
function hit(text) {
|
|
551
593
|
return typeof text === "string" && text !== "" && text.toLowerCase().indexOf(kw) !== -1;
|
|
552
594
|
}
|
|
553
|
-
|
|
595
|
+
/* Split text into nodes, wrapping every case-insensitive keyword match in a <mark>. */
|
|
596
|
+
function highlightedNodes(text, kw) {
|
|
597
|
+
if (typeof text !== "string" || kw === "") return [text];
|
|
598
|
+
const lower = text.toLowerCase();
|
|
599
|
+
const out = [];
|
|
600
|
+
let i = 0;
|
|
601
|
+
let m = 0;
|
|
602
|
+
for (;;) {
|
|
603
|
+
const idx = lower.indexOf(kw, i);
|
|
604
|
+
if (idx === -1) { if (i < text.length) out.push(text.slice(i)); break; }
|
|
605
|
+
if (idx > i) out.push(text.slice(i, idx));
|
|
606
|
+
out.push(React.createElement("mark", { key: "m" + (m++), className: "cnvnav-hl" }, text.slice(idx, idx + kw.length)));
|
|
607
|
+
i = idx + kw.length;
|
|
608
|
+
}
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
/* Window the text around the first keyword match (so the keyword is visible,
|
|
612
|
+
not hidden behind head-truncation), then highlight every occurrence. */
|
|
613
|
+
function snippetNodes(text, kw, before, after) {
|
|
614
|
+
if (typeof text !== "string") return [text];
|
|
615
|
+
const idx = text.toLowerCase().indexOf(kw);
|
|
616
|
+
if (idx === -1) return highlightedNodes(text, kw);
|
|
617
|
+
const start = Math.max(0, idx - before);
|
|
618
|
+
const end = Math.min(text.length, idx + kw.length + after);
|
|
619
|
+
const s = text.slice(start, end);
|
|
620
|
+
const parts = [];
|
|
621
|
+
if (start > 0) parts.push("…");
|
|
622
|
+
parts.push(...highlightedNodes(s, kw));
|
|
623
|
+
if (end < text.length) parts.push("…");
|
|
624
|
+
return parts;
|
|
625
|
+
}
|
|
626
|
+
function renderItem(item, staggerMs) {
|
|
627
|
+
const useSnippet = filtering && hit(item.search);
|
|
628
|
+
const preview = useSnippet ? snippetNodes(item.search, kw, 20, 50) : item.preview;
|
|
629
|
+
const hasPreview = preview !== null && preview !== "" && (!Array.isArray(preview) || preview.length > 0);
|
|
554
630
|
return React.createElement("button", {
|
|
555
631
|
key: item.key,
|
|
556
632
|
type: "button",
|
|
557
633
|
ref: (el) => { itemEls[item.key] = el; },
|
|
558
|
-
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,
|
|
559
636
|
onClick: () => jumpTo(item.key),
|
|
560
637
|
},
|
|
561
638
|
React.createElement("span", { className: "cnvnav-badge cnvnav-badge-" + item.cls }, item.label),
|
|
562
|
-
|
|
563
|
-
? React.createElement("span", { className: "cnvnav-item-preview" },
|
|
639
|
+
hasPreview
|
|
640
|
+
? React.createElement("span", { className: "cnvnav-item-preview" }, preview)
|
|
564
641
|
: null,
|
|
565
642
|
item.time !== ""
|
|
566
643
|
? React.createElement("span", { className: "cnvnav-item-time" }, item.time)
|
|
567
644
|
: null,
|
|
568
645
|
);
|
|
569
646
|
}
|
|
570
|
-
function renderGroupHead(g, groupActive) {
|
|
647
|
+
function renderGroupHead(g, groupActive, chevron) {
|
|
648
|
+
const headSearch = g.items.length > 0 ? g.items[0].search : null;
|
|
649
|
+
const useSnippet = filtering && hit(headSearch);
|
|
650
|
+
const sub = useSnippet ? snippetNodes(headSearch, kw, 15, 40) : g.subtitle;
|
|
651
|
+
const hasSub = sub !== "" && (!Array.isArray(sub) || sub.length > 0);
|
|
571
652
|
return React.createElement("div", {
|
|
572
653
|
ref: (el) => { groupEls[g.gid] = el; },
|
|
573
654
|
className: "cnvnav-group-row" + (groupActive ? " cnvnav-group-row-active" : ""),
|
|
@@ -584,12 +665,13 @@ window.__ModuleLoader__.load({
|
|
|
584
665
|
onClick: () => jumpTo(g.headKey),
|
|
585
666
|
},
|
|
586
667
|
React.createElement("span", { className: "cnvnav-group-title" }, g.title),
|
|
587
|
-
|
|
588
|
-
? React.createElement("span", { className: "cnvnav-group-sub" },
|
|
668
|
+
hasSub
|
|
669
|
+
? React.createElement("span", { className: "cnvnav-group-sub" }, sub)
|
|
589
670
|
: null,
|
|
590
671
|
),
|
|
591
672
|
),
|
|
592
673
|
),
|
|
674
|
+
chevron !== undefined ? chevron : null,
|
|
593
675
|
);
|
|
594
676
|
}
|
|
595
677
|
|
|
@@ -597,14 +679,27 @@ window.__ModuleLoader__.load({
|
|
|
597
679
|
if (filtering) {
|
|
598
680
|
groupNodes = [];
|
|
599
681
|
for (const g of groups) {
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
682
|
+
const headHit = g.headIsUser && g.items.length > 0 && hit(g.items[0].search);
|
|
683
|
+
const hits = g.items.filter((it, i) =>
|
|
684
|
+
(it.kind === "user" || it.kind === "assistant-step") &&
|
|
685
|
+
hit(it.search) &&
|
|
686
|
+
!(i === 0 && g.headIsUser)
|
|
687
|
+
);
|
|
688
|
+
if (!headHit && hits.length === 0) continue;
|
|
604
689
|
const groupActive = g.items.some((it) => it.key === snap.activeKey);
|
|
605
|
-
groupNodes.push(React.createElement("div", {
|
|
606
|
-
|
|
607
|
-
|
|
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
|
+
},
|
|
695
|
+
renderGroupHead(g, groupActive, null),
|
|
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,
|
|
608
703
|
));
|
|
609
704
|
}
|
|
610
705
|
} else {
|
|
@@ -614,44 +709,83 @@ window.__ModuleLoader__.load({
|
|
|
614
709
|
const stepItems = isTurnGroup ? (g.headIsUser ? g.items.slice(1) : g.items) : [];
|
|
615
710
|
const hasSteps = stepItems.length > 0;
|
|
616
711
|
const groupActive = g.items.some((it) => it.key === snap.activeKey);
|
|
712
|
+
const chevron = hasSteps
|
|
713
|
+
? React.createElement(Tooltip, {
|
|
714
|
+
label: expanded ? "折叠步次" : "展开步次",
|
|
715
|
+
side: "top",
|
|
716
|
+
delayMs: 500,
|
|
717
|
+
},
|
|
718
|
+
React.createElement("span", { className: "cnvnav-chevron-wrap" },
|
|
719
|
+
React.createElement("button", {
|
|
720
|
+
type: "button",
|
|
721
|
+
className: "cnvnav-chevron",
|
|
722
|
+
"aria-expanded": expanded,
|
|
723
|
+
onClick: (e) => {
|
|
724
|
+
e.stopPropagation();
|
|
725
|
+
setState({ expanded: Object.assign({}, state.expanded, { [g.gid]: !expanded }) });
|
|
726
|
+
},
|
|
727
|
+
},
|
|
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)),
|
|
737
|
+
),
|
|
738
|
+
),
|
|
739
|
+
)
|
|
740
|
+
: null;
|
|
617
741
|
return React.createElement("div", { key: g.gid, className: "cnvnav-group" },
|
|
618
|
-
renderGroupHead(g, groupActive),
|
|
742
|
+
renderGroupHead(g, groupActive, chevron),
|
|
619
743
|
hasSteps
|
|
620
|
-
? React.createElement(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
delayMs: 500,
|
|
744
|
+
? React.createElement("div", {
|
|
745
|
+
className: "cnvnav-steps" + (expanded ? "" : " cnvnav-steps-collapsed"),
|
|
746
|
+
"aria-hidden": !expanded,
|
|
624
747
|
},
|
|
625
|
-
React.createElement("
|
|
626
|
-
|
|
627
|
-
type: "button",
|
|
628
|
-
className: "cnvnav-chevron",
|
|
629
|
-
"aria-expanded": expanded,
|
|
630
|
-
onClick: (e) => {
|
|
631
|
-
e.stopPropagation();
|
|
632
|
-
setState({ expanded: Object.assign({}, state.expanded, { [g.gid]: !expanded }) });
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
expanded ? "▾" : "▸ " + stepItems.length,
|
|
636
|
-
),
|
|
748
|
+
React.createElement("div", { className: "cnvnav-steps-inner" },
|
|
749
|
+
stepItems.map(renderItem),
|
|
637
750
|
),
|
|
638
751
|
)
|
|
639
752
|
: null,
|
|
640
|
-
expanded && hasSteps
|
|
641
|
-
? stepItems.map(renderItem)
|
|
642
|
-
: null,
|
|
643
753
|
);
|
|
644
754
|
});
|
|
645
755
|
}
|
|
646
756
|
|
|
647
757
|
return React.createElement("div", {
|
|
648
758
|
ref: (el) => { panelEl = el; },
|
|
649
|
-
className: "cnvnav-panel",
|
|
759
|
+
className: "cnvnav-panel" + (snap.open ? "" : " cnvnav-panel-hidden"),
|
|
650
760
|
role: "complementary",
|
|
651
761
|
"aria-label": "会话导航",
|
|
762
|
+
"aria-hidden": !snap.open,
|
|
652
763
|
},
|
|
653
764
|
React.createElement("div", { className: "cnvnav-head" },
|
|
654
|
-
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,
|
|
655
789
|
React.createElement("span", { className: "cnvnav-count" }, (filtering ? groupNodes.length + " / " : "") + String(count) + " 条"),
|
|
656
790
|
React.createElement(Tooltip, {
|
|
657
791
|
label: "收起面板",
|
|
@@ -668,15 +802,6 @@ window.__ModuleLoader__.load({
|
|
|
668
802
|
),
|
|
669
803
|
),
|
|
670
804
|
),
|
|
671
|
-
React.createElement("div", { className: "cnvnav-filter-row" },
|
|
672
|
-
React.createElement("input", {
|
|
673
|
-
type: "text",
|
|
674
|
-
className: "cnvnav-filter",
|
|
675
|
-
placeholder: "过滤提问 / 回复…",
|
|
676
|
-
value: filter,
|
|
677
|
-
onChange: (e) => setFilter(e.target.value),
|
|
678
|
-
}),
|
|
679
|
-
),
|
|
680
805
|
React.createElement("div", { className: "cnvnav-topbar" },
|
|
681
806
|
React.createElement(Tooltip, {
|
|
682
807
|
label: "加载更早的一批轮次",
|