dsh-conversation-navigator 0.1.2 → 0.1.4

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 (2) hide show
  1. package/lib/client.js +77 -49
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -5,26 +5,10 @@ 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 } = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
 
9
10
  /* ---------- own stylesheet (static packages have no styles builtin) ---------- */
10
11
  const CSS = `
11
- .cnvnav-toggle {
12
- display: inline-flex; align-items: center; gap: 6px;
13
- height: 28px; padding: 0 10px; border-radius: 8px;
14
- border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
15
- background: transparent;
16
- color: var(--dsw-alias-label-secondary, #666);
17
- font-size: 12px; line-height: 1; cursor: pointer;
18
- }
19
- .cnvnav-toggle:hover {
20
- background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12));
21
- color: var(--dsw-alias-label-primary, #222);
22
- }
23
- .cnvnav-toggle-on {
24
- color: var(--dsw-alias-brand-primary, #4f46e5);
25
- border-color: var(--dsw-alias-brand-primary, #4f46e5);
26
- }
27
- .cnvnav-toggle svg { display: block; }
28
12
  .cnvnav-panel {
29
13
  position: fixed; z-index: 9999; width: 288px;
30
14
  display: flex; flex-direction: column;
@@ -45,15 +29,7 @@ window.__ModuleLoader__.load({
45
29
  }
46
30
  .cnvnav-title { font-weight: 600; font-size: 13px; }
47
31
  .cnvnav-count { color: var(--dsw-alias-label-secondary, #888); font-size: 11px; }
48
- .cnvnav-close {
49
- margin-left: auto; border: none; background: transparent;
50
- color: var(--dsw-alias-label-secondary, #888); cursor: pointer;
51
- font-size: 16px; line-height: 1; padding: 2px 7px; border-radius: 6px;
52
- }
53
- .cnvnav-close:hover {
54
- background: var(--dsw-alias-bg-layer-2, rgba(128,128,128,.12));
55
- color: var(--dsw-alias-label-primary, #222);
56
- }
32
+ .cnvnav-close { margin-left: auto; }
57
33
  .cnvnav-list {
58
34
  overflow-y: auto; padding: 6px; min-height: 0;
59
35
  display: flex; flex-direction: column; gap: 2px;
@@ -127,20 +103,16 @@ window.__ModuleLoader__.load({
127
103
  flex: none; color: var(--dsw-alias-label-secondary, #888); font-size: 10px; opacity: .8;
128
104
  }
129
105
  .cnvnav-empty { padding: 18px 10px; text-align: center; color: var(--dsw-alias-label-secondary, #888); }
106
+ .cnvnav-topbar { display: flex; gap: 6px; margin: 6px 6px 0; }
130
107
  .cnvnav-footer { display: flex; gap: 6px; margin: 0 6px 6px; }
131
- .cnvnav-bottom {
132
- flex: 1; padding: 7px; border-radius: 8px;
133
- border: 1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.35));
134
- background: var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06));
135
- color: var(--dsw-alias-label-primary, #222); cursor: pointer; font-size: 12px;
136
- }
137
- .cnvnav-bottom:hover { border-color: var(--dsw-alias-brand-primary, #4f46e5); color: var(--dsw-alias-brand-primary, #4f46e5); }
108
+ .cnvnav-bar-btn { flex: 1; }
138
109
  `;
139
110
 
140
111
  function apply(ctx) {
141
112
  const slots = ctx.get("slots");
142
113
  if (slots === undefined) return;
143
114
  const timer = ctx.get("timer");
115
+ const sessions = ctx.get("sessions");
144
116
 
145
117
  /* own stylesheet with fiber-scoped cleanup */
146
118
  const styleEl = document.createElement("style");
@@ -150,7 +122,7 @@ window.__ModuleLoader__.load({
150
122
  ctx.effect(() => () => styleEl.remove());
151
123
 
152
124
  /* ---------- shared store (toggle button + overlay panel) ---------- */
153
- let state = { open: true, sessionId: null, outline: null, activeKey: null, expanded: {} };
125
+ let state = { open: true, sessionId: null, outline: null, activeKey: null, expanded: {}, hasMore: false, loadingOlder: false, loadingAll: false };
154
126
  const listeners = new Set();
155
127
  function setState(patch) {
156
128
  state = Object.assign({}, state, patch);
@@ -163,6 +135,38 @@ window.__ModuleLoader__.load({
163
135
  return snap;
164
136
  }
165
137
 
138
+ /* ---------- history paging ---------- */
139
+ let loadingAll = false;
140
+ function loadOlderPage() {
141
+ if (sessions === undefined) return;
142
+ const sid = state.sessionId;
143
+ if (sid === null) return;
144
+ const binding = sessions.binding(sid);
145
+ if (binding === undefined) return;
146
+ binding.session.loadOlder().catch((err) => console.error(err));
147
+ }
148
+ async function loadAllPages() {
149
+ if (sessions === undefined || loadingAll) return;
150
+ const sid = state.sessionId;
151
+ if (sid === null) return;
152
+ const binding = sessions.binding(sid);
153
+ if (binding === undefined) return;
154
+ loadingAll = true;
155
+ setState({ loadingAll: true });
156
+ try {
157
+ let guard = 0;
158
+ while (binding.session.getSnapshot().hasMore && guard < 500) {
159
+ await binding.session.loadOlder();
160
+ guard += 1;
161
+ }
162
+ } catch (err) {
163
+ console.error(err);
164
+ } finally {
165
+ loadingAll = false;
166
+ setState({ loadingAll: false });
167
+ }
168
+ }
169
+
166
170
  /* ---------- outline derivation from ConversationSnapshot ---------- */
167
171
  const KIND_META = {
168
172
  "user": { label: "你", cls: "traj-user" },
@@ -382,22 +386,25 @@ window.__ModuleLoader__.load({
382
386
  function ToggleButton(props) {
383
387
  const snap = useStore();
384
388
  const outline = props.useSession(selectOutline);
389
+ const hasMore = props.useSession((s) => s.hasMore);
390
+ const loadingOlder = props.useSession((s) => s.loadingOlder);
385
391
  React.useEffect(() => {
386
- setState({ sessionId: props.sessionId, outline: outline });
387
- }, [outline, props.sessionId]);
392
+ setState({ sessionId: props.sessionId, outline: outline, hasMore: hasMore, loadingOlder: loadingOlder });
393
+ }, [outline, props.sessionId, hasMore, loadingOlder]);
388
394
  React.useEffect(() => {
389
395
  if (state.sessionId !== null && state.sessionId !== props.sessionId) {
390
396
  lastActive = null;
391
397
  setState({ activeKey: null, expanded: {} });
392
398
  }
393
399
  }, [props.sessionId]);
394
- return React.createElement("button", {
395
- type: "button",
396
- className: "cnvnav-toggle" + (snap.open ? " cnvnav-toggle-on" : ""),
400
+ return React.createElement(Button, {
401
+ variant: snap.open ? "primary" : "ghost",
402
+ size: "sm",
403
+ icon: React.createElement(ToggleIcon),
397
404
  "aria-pressed": snap.open,
398
405
  title: snap.open ? "收起会话导航" : "打开会话导航",
399
406
  onClick: () => setState({ open: !snap.open }),
400
- }, React.createElement(ToggleIcon), "导航");
407
+ }, "导航");
401
408
  }
402
409
 
403
410
  /* ---------- floating navigator panel ---------- */
@@ -535,13 +542,32 @@ window.__ModuleLoader__.load({
535
542
  React.createElement("div", { className: "cnvnav-head" },
536
543
  React.createElement("span", { className: "cnvnav-title" }, "会话导航"),
537
544
  React.createElement("span", { className: "cnvnav-count" }, String(count) + " 条"),
538
- React.createElement("button", {
539
- type: "button",
545
+ React.createElement(Button, {
546
+ variant: "ghost",
547
+ size: "sm",
540
548
  className: "cnvnav-close",
541
549
  title: "收起",
542
550
  onClick: () => setState({ open: false }),
543
551
  }, "×"),
544
552
  ),
553
+ React.createElement("div", { className: "cnvnav-topbar" },
554
+ React.createElement(Button, {
555
+ variant: "ghost",
556
+ size: "sm",
557
+ className: "cnvnav-bar-btn",
558
+ disabled: !snap.hasMore || snap.loadingOlder,
559
+ title: "加载更早的一批轮次",
560
+ onClick: () => loadOlderPage(),
561
+ }, "△ 加载更早"),
562
+ React.createElement(Button, {
563
+ variant: "ghost",
564
+ size: "sm",
565
+ className: "cnvnav-bar-btn",
566
+ disabled: !snap.hasMore || snap.loadingOlder || snap.loadingAll,
567
+ title: "加载全部历史轮次",
568
+ onClick: () => loadAllPages(),
569
+ }, "▴ 加载全部"),
570
+ ),
545
571
  React.createElement("div", {
546
572
  ref: (el) => { listEl = el; },
547
573
  className: "cnvnav-list",
@@ -551,9 +577,10 @@ window.__ModuleLoader__.load({
551
577
  : groupNodes,
552
578
  ),
553
579
  React.createElement("div", { className: "cnvnav-footer" },
554
- React.createElement("button", {
555
- type: "button",
556
- className: "cnvnav-bottom",
580
+ React.createElement(Button, {
581
+ variant: "ghost",
582
+ size: "sm",
583
+ className: "cnvnav-bar-btn",
557
584
  onClick: () => {
558
585
  const gs = snap.outline !== null ? snap.outline.groups : [];
559
586
  if (gs.length === 0) return;
@@ -562,9 +589,10 @@ window.__ModuleLoader__.load({
562
589
  if (lastItem !== undefined) jumpTo(lastItem.key);
563
590
  },
564
591
  }, "↓ 回到最新"),
565
- React.createElement("button", {
566
- type: "button",
567
- className: "cnvnav-bottom",
592
+ React.createElement(Button, {
593
+ variant: "ghost",
594
+ size: "sm",
595
+ className: "cnvnav-bar-btn",
568
596
  title: "折叠所有已展开的轮次",
569
597
  onClick: () => setState({ expanded: {} }),
570
598
  }, "▼ 全部折叠"),
@@ -583,7 +611,7 @@ window.__ModuleLoader__.load({
583
611
  ));
584
612
  }
585
613
 
586
- const inject = ["slots"];
614
+ const inject = ["slots", "sessions"];
587
615
  exports.apply = apply;
588
616
  exports.inject = inject;
589
617
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-conversation-navigator",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Codex 风格的 DSH(DeepSeek Harness)会话导航插件:按轮折叠的对话大纲面板,支持节点点击跳转、滚动位置高亮与轨迹视图同款配色。",
5
5
  "keywords": [
6
6
  "dsh",