dsh-mega-chat-nav 0.1.1
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/LICENSE +201 -0
- package/README.en.md +149 -0
- package/README.md +149 -0
- package/cordis.patch.yml +4 -0
- package/lib/client/NavOverlay.js +35 -0
- package/lib/client/NavRail.js +12 -0
- package/lib/client/QuestionNavSettingsTab.js +26 -0
- package/lib/client/QuestionNavStrip.js +555 -0
- package/lib/client/components/ConfigPanel.js +14 -0
- package/lib/client/components/DotRail.js +62 -0
- package/lib/client/components/NavOverlay.js +121 -0
- package/lib/client/components/NavRail.js +97 -0
- package/lib/client/components/RailDispatch.js +22 -0
- package/lib/client/components/RailHost.js +79 -0
- package/lib/client/components/SearchBox.js +66 -0
- package/lib/client/components/SettingsPage.js +46 -0
- package/lib/client/components/StandaloneSettings.js +16 -0
- package/lib/client/components/codex/CodexRail.js +139 -0
- package/lib/client/components/deepseek/DeepseekRail.js +264 -0
- package/lib/client/components/minimal/MinimalRail.js +135 -0
- package/lib/client/components/minimal/cards.js +32 -0
- package/lib/client/components/minimal/dot.js +11 -0
- package/lib/client/components/minimal/hint.js +30 -0
- package/lib/client/components/minimal/paging.js +5 -0
- package/lib/client/components/minimal/quick-settings.js +28 -0
- package/lib/client/components/minimal/search.js +67 -0
- package/lib/client/components/minimal/useRail.js +716 -0
- package/lib/client/components/rail/cards.js +32 -0
- package/lib/client/components/rail/hint.js +30 -0
- package/lib/client/components/rail/paging.js +5 -0
- package/lib/client/components/rail/quick-settings.js +28 -0
- package/lib/client/components/rail/search.js +76 -0
- package/lib/client/components/rail/useRail.js +845 -0
- package/lib/client/components/settings/SettingsPage.js +46 -0
- package/lib/client/components/settings/SettingsTab.js +17 -0
- package/lib/client/components/settings/StandaloneSettings.js +16 -0
- package/lib/client/components/shared/Cascade.js +26 -0
- package/lib/client/components/shared/ConfigPanel.js +27 -0
- package/lib/client/components/shared/Dot.js +8 -0
- package/lib/client/components/shared/Hint.js +5 -0
- package/lib/client/components/shared/PagingButton.js +5 -0
- package/lib/client/components/shared/RailShell.js +78 -0
- package/lib/client/components/shared/SearchBox.js +63 -0
- package/lib/client/components/shared/hooks.js +427 -0
- package/lib/client/components/shared/types.js +1 -0
- package/lib/client/config.js +1 -0
- package/lib/client/engines/jump.js +34 -0
- package/lib/client/engines/useAtBottom.js +25 -0
- package/lib/client/engines/useJump.js +40 -0
- package/lib/client/engines/useScrollSpy.js +88 -0
- package/lib/client/hooks.js +11 -0
- package/lib/client/index.js +312 -0
- package/lib/client/jump.js +255 -0
- package/lib/client/locales.js +142 -0
- package/lib/client/pure.js +61 -0
- package/lib/client/settings.js +369 -0
- package/lib/client/virtual/VirtualList.js +21 -0
- package/lib/client.js +3213 -0
- package/lib/compat.js +145 -0
- package/lib/config.js +37 -0
- package/lib/core/active-dot.js +17 -0
- package/lib/core/align.js +9 -0
- package/lib/core/config.js +37 -0
- package/lib/core/focus.js +38 -0
- package/lib/core/jump.js +98 -0
- package/lib/core/logic/active-dot.js +16 -0
- package/lib/core/logic/focus.js +34 -0
- package/lib/core/logic/jump.js +188 -0
- package/lib/core/logic/nodes.js +59 -0
- package/lib/core/model/nav-item.js +1 -0
- package/lib/core/model/turns.js +73 -0
- package/lib/core/nav-item.js +1 -0
- package/lib/core/nodes.js +57 -0
- package/lib/core/page-size.js +7 -0
- package/lib/core/question-entry.js +1 -0
- package/lib/core/time.js +23 -0
- package/lib/core/turn-dots.js +85 -0
- package/lib/core/utils/measure.js +24 -0
- package/lib/core/utils/time.js +26 -0
- package/lib/index.js +64 -0
- package/lib/nav-item.js +1 -0
- package/lib/nodes.js +59 -0
- package/lib/projection.js +126 -0
- package/lib/schema.js +26 -0
- package/lib/search.js +285 -0
- package/lib/settings.js +89 -0
- package/lib/types/client/NavOverlay.d.ts +18 -0
- package/lib/types/client/NavRail.d.ts +7 -0
- package/lib/types/client/QuestionNavSettingsTab.d.ts +16 -0
- package/lib/types/client/QuestionNavStrip.d.ts +48 -0
- package/lib/types/client/components/ConfigPanel.d.ts +14 -0
- package/lib/types/client/components/DotRail.d.ts +15 -0
- package/lib/types/client/components/NavOverlay.d.ts +30 -0
- package/lib/types/client/components/NavRail.d.ts +16 -0
- package/lib/types/client/components/RailDispatch.d.ts +8 -0
- package/lib/types/client/components/RailHost.d.ts +27 -0
- package/lib/types/client/components/SearchBox.d.ts +9 -0
- package/lib/types/client/components/SettingsPage.d.ts +9 -0
- package/lib/types/client/components/StandaloneSettings.d.ts +13 -0
- package/lib/types/client/components/codex/CodexRail.d.ts +3 -0
- package/lib/types/client/components/deepseek/DeepseekRail.d.ts +3 -0
- package/lib/types/client/components/minimal/MinimalRail.d.ts +8 -0
- package/lib/types/client/components/minimal/cards.d.ts +19 -0
- package/lib/types/client/components/minimal/dot.d.ts +14 -0
- package/lib/types/client/components/minimal/hint.d.ts +11 -0
- package/lib/types/client/components/minimal/paging.d.ts +10 -0
- package/lib/types/client/components/minimal/quick-settings.d.ts +8 -0
- package/lib/types/client/components/minimal/search.d.ts +27 -0
- package/lib/types/client/components/minimal/useRail.d.ts +90 -0
- package/lib/types/client/components/rail/cards.d.ts +19 -0
- package/lib/types/client/components/rail/hint.d.ts +11 -0
- package/lib/types/client/components/rail/paging.d.ts +10 -0
- package/lib/types/client/components/rail/quick-settings.d.ts +8 -0
- package/lib/types/client/components/rail/search.d.ts +34 -0
- package/lib/types/client/components/rail/useRail.d.ts +132 -0
- package/lib/types/client/components/settings/SettingsPage.d.ts +9 -0
- package/lib/types/client/components/settings/SettingsTab.d.ts +6 -0
- package/lib/types/client/components/settings/StandaloneSettings.d.ts +13 -0
- package/lib/types/client/components/shared/Cascade.d.ts +14 -0
- package/lib/types/client/components/shared/ConfigPanel.d.ts +8 -0
- package/lib/types/client/components/shared/Dot.d.ts +17 -0
- package/lib/types/client/components/shared/Hint.d.ts +6 -0
- package/lib/types/client/components/shared/PagingButton.d.ts +10 -0
- package/lib/types/client/components/shared/RailShell.d.ts +43 -0
- package/lib/types/client/components/shared/SearchBox.d.ts +18 -0
- package/lib/types/client/components/shared/hooks.d.ts +64 -0
- package/lib/types/client/components/shared/types.d.ts +101 -0
- package/lib/types/client/config.d.ts +9 -0
- package/lib/types/client/engines/jump.d.ts +17 -0
- package/lib/types/client/engines/useAtBottom.d.ts +1 -0
- package/lib/types/client/engines/useJump.d.ts +16 -0
- package/lib/types/client/engines/useScrollSpy.d.ts +12 -0
- package/lib/types/client/hooks.d.ts +7 -0
- package/lib/types/client/index.d.ts +31 -0
- package/lib/types/client/jump.d.ts +47 -0
- package/lib/types/client/locales.d.ts +142 -0
- package/lib/types/client/pure.d.ts +21 -0
- package/lib/types/client/settings.d.ts +173 -0
- package/lib/types/client/virtual/VirtualList.d.ts +15 -0
- package/lib/types/compat.d.ts +44 -0
- package/lib/types/config.d.ts +44 -0
- package/lib/types/core/active-dot.d.ts +15 -0
- package/lib/types/core/align.d.ts +10 -0
- package/lib/types/core/config.d.ts +44 -0
- package/lib/types/core/focus.d.ts +24 -0
- package/lib/types/core/jump.d.ts +39 -0
- package/lib/types/core/logic/active-dot.d.ts +15 -0
- package/lib/types/core/logic/focus.d.ts +22 -0
- package/lib/types/core/logic/jump.d.ts +39 -0
- package/lib/types/core/logic/nodes.d.ts +48 -0
- package/lib/types/core/model/nav-item.d.ts +12 -0
- package/lib/types/core/model/turns.d.ts +30 -0
- package/lib/types/core/nav-item.d.ts +12 -0
- package/lib/types/core/nodes.d.ts +43 -0
- package/lib/types/core/page-size.d.ts +8 -0
- package/lib/types/core/question-entry.d.ts +12 -0
- package/lib/types/core/time.d.ts +4 -0
- package/lib/types/core/turn-dots.d.ts +29 -0
- package/lib/types/core/utils/measure.d.ts +12 -0
- package/lib/types/core/utils/time.d.ts +4 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/nav-item.d.ts +12 -0
- package/lib/types/nodes.d.ts +48 -0
- package/lib/types/projection.d.ts +71 -0
- package/lib/types/schema.d.ts +37 -0
- package/lib/types/search.d.ts +42 -0
- package/lib/types/settings.d.ts +99 -0
- package/package.json +71 -0
- package/src/client/components/RailDispatch.tsx +27 -0
- package/src/client/components/codex/CodexRail.tsx +348 -0
- package/src/client/components/deepseek/DeepseekRail.tsx +522 -0
- package/src/client/components/minimal/MinimalRail.tsx +324 -0
- package/src/client/components/minimal/dot.tsx +32 -0
- package/src/client/components/rail/cards.tsx +102 -0
- package/src/client/components/rail/hint.tsx +50 -0
- package/src/client/components/rail/paging.tsx +26 -0
- package/src/client/components/rail/quick-settings.tsx +130 -0
- package/src/client/components/rail/search.tsx +147 -0
- package/src/client/components/rail/useRail.ts +906 -0
- package/src/client/components/settings/SettingsPage.tsx +198 -0
- package/src/client/components/settings/StandaloneSettings.tsx +28 -0
- package/src/client/components/shared/types.ts +99 -0
- package/src/client/index.ts +388 -0
- package/src/client/jump.ts +293 -0
- package/src/client/locales.ts +143 -0
- package/src/client/settings.ts +482 -0
- package/src/client/styles.css +1580 -0
- package/src/compat.ts +161 -0
- package/src/index.ts +66 -0
- package/src/projection.ts +174 -0
- package/src/search.ts +291 -0
- package/src/settings.ts +169 -0
package/lib/search.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
function eventText(ev) {
|
|
2
|
+
if (ev.type === 'tool/call') {
|
|
3
|
+
// 工具调用:name + 参数 JSON 前段(可被搜索命中)
|
|
4
|
+
const d = ev.data;
|
|
5
|
+
const name = typeof d?.name === 'string' ? d.name : '';
|
|
6
|
+
const args = typeof d?.arguments === 'string' ? d.arguments : '';
|
|
7
|
+
return name.length === 0 && args.length === 0 ? '' : name + ' ' + args.slice(0, 200);
|
|
8
|
+
}
|
|
9
|
+
if (ev.type !== 'user/message' && ev.type !== 'assistant/message')
|
|
10
|
+
return '';
|
|
11
|
+
const msg = ev.type === 'user/message' ? ev.data : ev.data?.message;
|
|
12
|
+
const content = msg?.content;
|
|
13
|
+
if (typeof content === 'string')
|
|
14
|
+
return content;
|
|
15
|
+
if (Array.isArray(content)) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
for (const b of content) {
|
|
18
|
+
if (b && typeof b === 'object' && b.type === 'text') {
|
|
19
|
+
const txt = b.text;
|
|
20
|
+
if (typeof txt === 'string')
|
|
21
|
+
parts.push(txt);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return parts.join(' ');
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
function roleOf(ev) {
|
|
29
|
+
if (ev.type === 'user/message') {
|
|
30
|
+
const kind = ev.data?.source?.kind;
|
|
31
|
+
return kind === 'steering' ? 'steering' : 'user';
|
|
32
|
+
}
|
|
33
|
+
if (ev.type === 'tool/call')
|
|
34
|
+
return 'tool';
|
|
35
|
+
return 'assistant';
|
|
36
|
+
}
|
|
37
|
+
function keyOf(ev) {
|
|
38
|
+
const id = ev.type === 'user/message' ? ev.data?.id : ev.data?.message?.id;
|
|
39
|
+
return typeof id === 'string' && id.length > 0 ? id : 'seq:' + (ev.seq ?? 0);
|
|
40
|
+
}
|
|
41
|
+
function snippetAround(text, q, radius = 40) {
|
|
42
|
+
const idx = text.toLowerCase().indexOf(q.toLowerCase());
|
|
43
|
+
if (idx < 0)
|
|
44
|
+
return null;
|
|
45
|
+
const start = Math.max(0, idx - radius);
|
|
46
|
+
const end = Math.min(text.length, idx + q.length + radius);
|
|
47
|
+
const pre = start > 0 ? '…' : '';
|
|
48
|
+
const post = end < text.length ? '…' : '';
|
|
49
|
+
return { snippet: pre + text.slice(start, end) + post, hitStart: idx - start + (start > 0 ? 1 : 0), hitLen: q.length };
|
|
50
|
+
}
|
|
51
|
+
function makeLru(capacity = 32) {
|
|
52
|
+
const map = new Map();
|
|
53
|
+
return {
|
|
54
|
+
get(k) {
|
|
55
|
+
const v = map.get(k);
|
|
56
|
+
if (v !== undefined) {
|
|
57
|
+
map.delete(k);
|
|
58
|
+
map.set(k, v);
|
|
59
|
+
}
|
|
60
|
+
return v;
|
|
61
|
+
},
|
|
62
|
+
set(k, v) {
|
|
63
|
+
map.delete(k);
|
|
64
|
+
map.set(k, v);
|
|
65
|
+
if (map.size > capacity) {
|
|
66
|
+
const first = map.keys().next().value;
|
|
67
|
+
if (first !== undefined)
|
|
68
|
+
map.delete(first);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const searchCache = makeLru();
|
|
74
|
+
/** 访问可选服务:未注入的 cordis 服务在属性读取时抛错,捕获后按缺失处理 */
|
|
75
|
+
function tryGet(getter) {
|
|
76
|
+
try {
|
|
77
|
+
return getter();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function readBody(req) {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const r = req;
|
|
86
|
+
const chunks = [];
|
|
87
|
+
r.on?.('data', (chunk) => {
|
|
88
|
+
if (typeof chunk === 'string')
|
|
89
|
+
chunks.push(Buffer.from(chunk));
|
|
90
|
+
else if (chunk instanceof Uint8Array)
|
|
91
|
+
chunks.push(Buffer.from(chunk));
|
|
92
|
+
});
|
|
93
|
+
r.on?.('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
94
|
+
r.on?.('error', reject);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 读取会话事件:合并「已加载窗口」(live)与「更早历史」(持久化日志 →
|
|
99
|
+
* sessionQuery 兜底),按 seq 去重(历史中与 live 重复的丢弃),整体按 seq 排序。
|
|
100
|
+
*/
|
|
101
|
+
function readEventSources(sessionId, sessions, persistence, query) {
|
|
102
|
+
const live = (sessions?.get(sessionId)?.events ?? []);
|
|
103
|
+
const liveSeqs = new Set();
|
|
104
|
+
for (const ev of live) {
|
|
105
|
+
if (typeof ev?.seq === 'number')
|
|
106
|
+
liveSeqs.add(ev.seq);
|
|
107
|
+
}
|
|
108
|
+
let historic = [];
|
|
109
|
+
try {
|
|
110
|
+
const raw = persistence?.readRaw(sessionId);
|
|
111
|
+
if (Array.isArray(raw))
|
|
112
|
+
historic = raw;
|
|
113
|
+
}
|
|
114
|
+
catch { /* 下一级 */ }
|
|
115
|
+
if (historic.length === 0) {
|
|
116
|
+
const q = query?.readSession(sessionId);
|
|
117
|
+
if (q && Array.isArray(q.events))
|
|
118
|
+
historic = q.events;
|
|
119
|
+
}
|
|
120
|
+
const out = live.map((ev) => ({ ev, historic: false }));
|
|
121
|
+
for (const ev of historic) {
|
|
122
|
+
if (typeof ev?.seq === 'number' && liveSeqs.has(ev.seq))
|
|
123
|
+
continue;
|
|
124
|
+
out.push({ ev, historic: true });
|
|
125
|
+
}
|
|
126
|
+
return out.sort((a, b) => (a.ev.seq ?? 0) - (b.ev.seq ?? 0));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 命中锚点:直接按消息 kind 拼出完整 DOM 锚点 key(对齐 conversationContextKey 公式):
|
|
130
|
+
* - 用户/steering 消息 → '13:input-message' + id
|
|
131
|
+
* - assistant 消息 → '14:assistant-step' + turn + ':' + step(assistant-step 定义见
|
|
132
|
+
* dsh-client-ui-conversation;id 为 turn:step 组合)
|
|
133
|
+
* AI 命中的方向判定 seq 取所属轮用户消息序号(AI 行紧跟其用户提问,位置近似)。
|
|
134
|
+
*/
|
|
135
|
+
/** 事件 → 搜索内容范围 */
|
|
136
|
+
function scopeOf(ev) {
|
|
137
|
+
if (ev.type === 'tool/call')
|
|
138
|
+
return 'tool';
|
|
139
|
+
if (ev.type === 'assistant/message')
|
|
140
|
+
return 'assistant';
|
|
141
|
+
return 'user';
|
|
142
|
+
}
|
|
143
|
+
export function searchEvents(events, q, limit, scopes = ['user', 'assistant', 'tool']) {
|
|
144
|
+
const hits = [];
|
|
145
|
+
const needle = q.toLowerCase();
|
|
146
|
+
let turn = 0;
|
|
147
|
+
let lastUserSeq = 0;
|
|
148
|
+
for (const { ev, historic } of events) {
|
|
149
|
+
if (ev.type === 'turn/start') {
|
|
150
|
+
const t = ev.data?.turn;
|
|
151
|
+
if (typeof t === 'number')
|
|
152
|
+
turn = t;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (ev.type === 'user/message')
|
|
156
|
+
lastUserSeq = ev.seq ?? 0;
|
|
157
|
+
const text = eventText(ev);
|
|
158
|
+
if (!text || !text.toLowerCase().includes(needle))
|
|
159
|
+
continue;
|
|
160
|
+
if (!scopes.includes(scopeOf(ev)))
|
|
161
|
+
continue;
|
|
162
|
+
const role = roleOf(ev);
|
|
163
|
+
const isAssistant = role === 'assistant';
|
|
164
|
+
const isTool = ev.type === 'tool/call';
|
|
165
|
+
const anchorKey = isTool
|
|
166
|
+
? '9:tool-call' + String(ev.data?.callId ?? '')
|
|
167
|
+
: isAssistant
|
|
168
|
+
? '14:assistant-step' + turn + ':' + ev.data?.step
|
|
169
|
+
: '13:input-message' + keyOf(ev);
|
|
170
|
+
const anchorSeq = isAssistant || isTool ? lastUserSeq : ev.seq ?? 0;
|
|
171
|
+
const snip = snippetAround(text, q);
|
|
172
|
+
hits.push({
|
|
173
|
+
key: anchorKey,
|
|
174
|
+
seq: anchorSeq,
|
|
175
|
+
time: ev.time ?? 0,
|
|
176
|
+
role,
|
|
177
|
+
turn,
|
|
178
|
+
historic,
|
|
179
|
+
snippet: snip ? snip.snippet : text.slice(0, 60),
|
|
180
|
+
hitStart: snip ? snip.hitStart : -1,
|
|
181
|
+
hitLen: snip ? snip.hitLen : 0,
|
|
182
|
+
});
|
|
183
|
+
if (limit > 0 && hits.length >= limit)
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
return { hits, truncated: limit > 0 && hits.length >= limit };
|
|
187
|
+
}
|
|
188
|
+
export function registerSearchRoute(ctx) {
|
|
189
|
+
ctx.inject(['webServer', 'sessions'], (scoped) => {
|
|
190
|
+
const webServer = scoped.webServer;
|
|
191
|
+
const sessions = scoped.sessions;
|
|
192
|
+
if (!webServer || !sessions)
|
|
193
|
+
return;
|
|
194
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
195
|
+
const settingsService = settingsCtx.settings;
|
|
196
|
+
if (!settingsService)
|
|
197
|
+
return;
|
|
198
|
+
ctx.effect(() => {
|
|
199
|
+
const dispose = webServer.register({
|
|
200
|
+
kind: 'prefix',
|
|
201
|
+
path: '/mega-chat-nav',
|
|
202
|
+
handler: async (req, res) => {
|
|
203
|
+
const httpRes = res;
|
|
204
|
+
const httpReq = req;
|
|
205
|
+
const write = (code, body) => {
|
|
206
|
+
httpRes.statusCode = code;
|
|
207
|
+
httpRes.setHeader?.('Content-Type', 'application/json; charset=utf-8');
|
|
208
|
+
httpRes.end(JSON.stringify(body));
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
const url = new URL(httpReq.url ?? '/', 'http://localhost');
|
|
212
|
+
// 嵌套配置写回:scope.set 仅支持 section 内标量,嵌套 section 必须走 host 桥
|
|
213
|
+
if (url.pathname === '/mega-chat-nav/settings') {
|
|
214
|
+
if ((httpReq.method ?? 'GET') !== 'POST') {
|
|
215
|
+
write(405, { ok: false, error: 'POST required' });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const raw = await readBody(req);
|
|
219
|
+
let patch;
|
|
220
|
+
let revision;
|
|
221
|
+
try {
|
|
222
|
+
const body = JSON.parse(raw);
|
|
223
|
+
patch = body.patch;
|
|
224
|
+
revision = body.revision;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
write(400, { ok: false, error: 'bad json' });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (typeof patch !== 'object' || patch === null || Array.isArray(patch)) {
|
|
231
|
+
write(400, { ok: false, error: 'patch must be an object' });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await settingsService.update('mega-chat-nav', patch, typeof revision === 'number' ? revision : undefined);
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
write(409, { ok: false, error: 'settings conflict: ' + String(e) });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
write(200, { ok: true });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (url.pathname !== '/mega-chat-nav/search') {
|
|
245
|
+
write(404, { ok: false, error: 'not found' });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const sessionId = url.searchParams.get('session') ?? '';
|
|
249
|
+
const q = (url.searchParams.get('q') ?? '').trim();
|
|
250
|
+
// limit<=0(含未传)→ 不限条数,返回全部命中
|
|
251
|
+
const rawLimit = url.searchParams.get('limit');
|
|
252
|
+
const limit = rawLimit === null || rawLimit === ''
|
|
253
|
+
? 0
|
|
254
|
+
: Math.max(0, Math.min(Number(rawLimit) || 0, 500));
|
|
255
|
+
if (!sessionId || q.length < 1) {
|
|
256
|
+
write(400, { ok: false, error: 'session & q required' });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// 搜索内容范围(多选);缺省全选;user 必选强制保留
|
|
260
|
+
const rawScopes = (url.searchParams.get('scopes') ?? '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
261
|
+
const scopes = rawScopes.length > 0
|
|
262
|
+
? [...new Set(['user', ...rawScopes])].filter((s) => s === 'user' || s === 'assistant' || s === 'tool')
|
|
263
|
+
: ['user', 'assistant', 'tool'];
|
|
264
|
+
const cacheKey = sessionId + '|' + q + '|' + limit;
|
|
265
|
+
const cached = searchCache.get(cacheKey);
|
|
266
|
+
if (cached) {
|
|
267
|
+
write(200, { ok: true, ...cached });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const persistence = tryGet(() => scoped.sessionPersistence);
|
|
271
|
+
const query = tryGet(() => scoped.sessionQuery);
|
|
272
|
+
const result = searchEvents(readEventSources(sessionId, sessions, persistence, query), q, limit, scopes);
|
|
273
|
+
searchCache.set(cacheKey, result);
|
|
274
|
+
write(200, { ok: true, ...result });
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
write(500, { ok: false, error: String(e) });
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return dispose;
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
package/lib/settings.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side durable settings for the mega-chat-nav plugin.
|
|
3
|
+
* 配置选项为本插件自有定义:host 半持有此处;client 半在
|
|
4
|
+
* src/client/settings.ts 持有同值副本(client bundle 不得依赖宿主包)。
|
|
5
|
+
*
|
|
6
|
+
* @module dsh-mega-chat-nav/settings
|
|
7
|
+
*/
|
|
8
|
+
import z from '@deepseek-ai/schemastery';
|
|
9
|
+
/** 风格选项:简约 / codex(时间线刻度)/ deepseek(用户消息面板,常态短横折叠) */
|
|
10
|
+
export const STYLE_OPTIONS = ['minimal', 'codex', 'deepseek'];
|
|
11
|
+
/** 停靠侧选项 */
|
|
12
|
+
export const ALIGN_OPTIONS = ['left', 'right'];
|
|
13
|
+
/** 节点栏条带高度档位 */
|
|
14
|
+
export const BAND_OPTIONS = ['compact', 'standard', 'tall'];
|
|
15
|
+
/** 跳转动画方式 */
|
|
16
|
+
export const SCROLL_OPTIONS = ['smooth', 'instant'];
|
|
17
|
+
/** 显示策略:常显 / 悬停浮现 / 隐藏 */
|
|
18
|
+
export const SHOW_OPTIONS = ['show', 'peek', 'hide'];
|
|
19
|
+
/** 翻页标记默认显示模式(按风格独立):minimal 常显(历史行为)/ codex 隐藏 */
|
|
20
|
+
export const DEFAULT_PAGING = {
|
|
21
|
+
minimal: 'show',
|
|
22
|
+
codex: 'hide',
|
|
23
|
+
deepseek: 'peek',
|
|
24
|
+
};
|
|
25
|
+
/** 搜索内容范围(多选;用户必选) */
|
|
26
|
+
export const SEARCH_SCOPE_OPTIONS = ['user', 'assistant', 'tool'];
|
|
27
|
+
export const DEFAULT_SEARCH_SCOPES = ['user', 'assistant', 'tool'];
|
|
28
|
+
/** 悬停卡片数量 */
|
|
29
|
+
export const CARD_OPTIONS = [1, 3, 5];
|
|
30
|
+
/** 悬浮卡默认数量(按风格独立):minimal 3 张 / codex 1 张 / deepseek 1 张(无悬浮卡能力,占位) */
|
|
31
|
+
export const DEFAULT_CARD_COUNT = {
|
|
32
|
+
minimal: 3,
|
|
33
|
+
codex: 1,
|
|
34
|
+
deepseek: 1,
|
|
35
|
+
};
|
|
36
|
+
/** 悬浮卡内容块(多选) */
|
|
37
|
+
export const CARD_ITEM_OPTIONS = ['turn', 'duration', 'favorite'];
|
|
38
|
+
export const DEFAULT_CARD_ITEMS = ['turn', 'duration', 'favorite'];
|
|
39
|
+
/** 刻度色调:柔和(低对比)/ 深邃(高对比、清晰) */
|
|
40
|
+
export const MARK_TONE_OPTIONS = ['soft', 'deep'];
|
|
41
|
+
export const DEFAULT_MARK_TONE = 'deep';
|
|
42
|
+
export const DEFAULT_STYLE = 'minimal';
|
|
43
|
+
export const DEFAULT_ALIGN = 'left';
|
|
44
|
+
export const DEFAULT_BAND = 'standard';
|
|
45
|
+
export const DEFAULT_SCROLL = 'smooth';
|
|
46
|
+
export const DEFAULT_SHOW = 'show';
|
|
47
|
+
// 显示模式兼容旧布尔(true → show / false → hide):旧 user 段里的布尔值
|
|
48
|
+
// 不能导致 register 的 schema 校验失败,否则 namespace 注册失败、设置无法持久化
|
|
49
|
+
const ShowModeSchema = z.union([z.boolean(), ...SHOW_OPTIONS]).default(DEFAULT_SHOW);
|
|
50
|
+
const GeneralSchema = z.object({
|
|
51
|
+
style: z.union([...STYLE_OPTIONS]).default(DEFAULT_STYLE),
|
|
52
|
+
align: z.union([...ALIGN_OPTIONS]).default(DEFAULT_ALIGN),
|
|
53
|
+
scrollBehavior: z.union([...SCROLL_OPTIONS]).default(DEFAULT_SCROLL),
|
|
54
|
+
showCount: ShowModeSchema,
|
|
55
|
+
showSearch: ShowModeSchema,
|
|
56
|
+
showQuickSettings: ShowModeSchema,
|
|
57
|
+
showFavorites: ShowModeSchema,
|
|
58
|
+
/* 旧版 general.showPaging 残留兼容(0.9.x 曾置于此处);新主源已迁移 styles.<style> */
|
|
59
|
+
showPaging: ShowModeSchema,
|
|
60
|
+
searchScopes: z.array(z.union([...SEARCH_SCOPE_OPTIONS])).default([...DEFAULT_SEARCH_SCOPES]),
|
|
61
|
+
railOffset: z.natural().max(32).default(8),
|
|
62
|
+
});
|
|
63
|
+
/** 风格公共字段(两风格默认一致的配置) */
|
|
64
|
+
const StyleFields = {
|
|
65
|
+
bandHeight: z.union([...BAND_OPTIONS]).default(DEFAULT_BAND),
|
|
66
|
+
cardItems: z.array(z.union([...CARD_ITEM_OPTIONS])).default([...DEFAULT_CARD_ITEMS]),
|
|
67
|
+
markTone: z.union([...MARK_TONE_OPTIONS]).default(DEFAULT_MARK_TONE),
|
|
68
|
+
};
|
|
69
|
+
/** 每风格独立默认字段:悬浮卡数与翻页标记(minimal 常显 / codex 隐藏 / deepseek 浮现) */
|
|
70
|
+
const StyleDefaults = {
|
|
71
|
+
minimal: { cardCount: DEFAULT_CARD_COUNT.minimal, showPaging: DEFAULT_PAGING.minimal },
|
|
72
|
+
codex: { cardCount: DEFAULT_CARD_COUNT.codex, showPaging: DEFAULT_PAGING.codex },
|
|
73
|
+
deepseek: { cardCount: DEFAULT_CARD_COUNT.deepseek, showPaging: DEFAULT_PAGING.deepseek },
|
|
74
|
+
};
|
|
75
|
+
const styleSchemaFields = (style) => ({
|
|
76
|
+
cardCount: z.union([...CARD_OPTIONS]).default(StyleDefaults[style].cardCount),
|
|
77
|
+
showPaging: z.union([...SHOW_OPTIONS]).default(StyleDefaults[style].showPaging),
|
|
78
|
+
});
|
|
79
|
+
/** 持久化设置 schema;也是浏览器 scope 校验的 wire 信封 */
|
|
80
|
+
export const NavSettingsSchema = z.object({
|
|
81
|
+
general: GeneralSchema,
|
|
82
|
+
styles: z.object({
|
|
83
|
+
minimal: z.object({ ...StyleFields, ...styleSchemaFields('minimal') }),
|
|
84
|
+
codex: z.object({ ...StyleFields, ...styleSchemaFields('codex') }),
|
|
85
|
+
deepseek: z.object({ ...StyleFields, ...styleSchemaFields('deepseek') }),
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
/** 本插件设置命名空间 */
|
|
89
|
+
export const megaChatNavSettingsNamespace = 'mega-chat-nav';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { NavConfig } from '../schema.ts';
|
|
3
|
+
interface ScopeLike {
|
|
4
|
+
getSnapshot(): {
|
|
5
|
+
value?: NavConfig;
|
|
6
|
+
};
|
|
7
|
+
subscribe(fn: () => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
interface OverlayProps {
|
|
10
|
+
scope?: ScopeLike;
|
|
11
|
+
renderSlot?: (name: string) => ReactNode;
|
|
12
|
+
SessionProvider?: (props: {
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}) => ReactNode;
|
|
15
|
+
}
|
|
16
|
+
export declare function useScope(scope?: ScopeLike): NavConfig | undefined;
|
|
17
|
+
export declare function NavOverlay(props: OverlayProps): ReactNode;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plugin's settings page inside the shell's Plugins section
|
|
3
|
+
* (`settings.plugins.tab`): two segmented controls — which edge of the
|
|
4
|
+
* conversation column the dot rail anchors to, and how many hidden dots one
|
|
5
|
+
* ▲/▼ paging-button click reveals. The choices are written to the
|
|
6
|
+
* `mega-chat-nav` settings namespace (registered by the host half); the strip
|
|
7
|
+
* re-anchors and re-steps live when the snapshot changes.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-mega-chat-nav/client/settings-tab
|
|
10
|
+
*/
|
|
11
|
+
import type { QuestionNavInjected } from './QuestionNavStrip.tsx';
|
|
12
|
+
interface ComponentProps extends QuestionNavInjected {
|
|
13
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
14
|
+
}
|
|
15
|
+
export declare function QuestionNavSettingsTab(props: ComponentProps): React.JSX.Element | null;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
2
|
+
import type { QuestionNode } from '../core/nodes.ts';
|
|
3
|
+
import type { AlignPreference } from '../core/align.ts';
|
|
4
|
+
import type { PageSize } from '../core/page-size.ts';
|
|
5
|
+
/** Minimal observable shape of a session projection face. */
|
|
6
|
+
export interface ObservableFace {
|
|
7
|
+
/** Current projection value (unknown — validated structurally at read). */
|
|
8
|
+
getSnapshot: () => unknown;
|
|
9
|
+
/** Subscribe to value changes; returns an unsubscribe. */
|
|
10
|
+
subscribe: (listener: () => void) => () => void;
|
|
11
|
+
}
|
|
12
|
+
/** Values the registrant inject face supplies (wired in src/client/index.ts). */
|
|
13
|
+
export interface QuestionNavInjected {
|
|
14
|
+
/** Extract the user questions of a session's currently loaded window. */
|
|
15
|
+
readQuestions: (sessionId: SessionId) => QuestionNode[];
|
|
16
|
+
/** Subscribe to the session list; returns an unsubscribe. */
|
|
17
|
+
subscribeList: (cb: () => void) => () => void;
|
|
18
|
+
/** Subscribe to a session's content; returns an unsubscribe. */
|
|
19
|
+
subscribeContent: (sessionId: SessionId, cb: () => void) => () => void;
|
|
20
|
+
/** The session's `msgNavMessages` projection face, when the host unit is registered. */
|
|
21
|
+
questionProjection: (sessionId: SessionId) => ObservableFace | undefined;
|
|
22
|
+
/** Jump the chat to a question row (pages the window on demand). */
|
|
23
|
+
jump: (sessionId: SessionId, key: string) => void;
|
|
24
|
+
/** Current rail anchor edge (defaults to 'left' before the settings section is ready). */
|
|
25
|
+
align: () => AlignPreference;
|
|
26
|
+
/** Observe plugin-settings changes (align, page size); returns an unsubscribe. */
|
|
27
|
+
subscribeSettings: (cb: () => void) => () => void;
|
|
28
|
+
/** Persist a new anchor edge. */
|
|
29
|
+
setAlign: (align: AlignPreference) => void;
|
|
30
|
+
/** Current ▲/▼ page size (defaults to DEFAULT_PAGE_SIZE before the settings section is ready). */
|
|
31
|
+
pageSize: () => PageSize;
|
|
32
|
+
/** Persist a new page size. */
|
|
33
|
+
setPageSize: (pageSize: PageSize) => void;
|
|
34
|
+
}
|
|
35
|
+
/** 宽松 props 面(原 PropsRuntime/PropsLocale 来自虚拟包,此处本地化) */
|
|
36
|
+
interface ComponentProps extends QuestionNavInjected {
|
|
37
|
+
useSessions?: <S>(selector: (s: SessionListLike) => S) => S;
|
|
38
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
39
|
+
}
|
|
40
|
+
/** 会话列表最小面(供 useSessions selector) */
|
|
41
|
+
interface SessionListLike {
|
|
42
|
+
current?: unknown;
|
|
43
|
+
byId?: Record<string, {
|
|
44
|
+
blank?: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare function QuestionNavStrip(props: ComponentProps): React.JSX.Element | null;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { NavConfig } from '../../schema.ts';
|
|
3
|
+
import type { ScopeLike } from '../config.ts';
|
|
4
|
+
export interface ConfigPanelProps {
|
|
5
|
+
scope: ScopeLike | undefined;
|
|
6
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
7
|
+
onClose(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ConfigPanel(props: ConfigPanelProps): ReactNode;
|
|
10
|
+
export declare function Switch({ checked, onChange }: {
|
|
11
|
+
checked: boolean;
|
|
12
|
+
onChange(v: boolean): void;
|
|
13
|
+
}): ReactNode;
|
|
14
|
+
export type { NavConfig };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { MsgNavItem } from '../../projection.ts';
|
|
3
|
+
import type { NavConfig } from '../../schema.ts';
|
|
4
|
+
import type { RailPos } from './NavOverlay.tsx';
|
|
5
|
+
export interface DotRailProps {
|
|
6
|
+
items: readonly MsgNavItem[];
|
|
7
|
+
align: 'left' | 'right';
|
|
8
|
+
cfg: NavConfig;
|
|
9
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
10
|
+
activeKey: string | null;
|
|
11
|
+
failed: boolean;
|
|
12
|
+
onJump(key: string): void;
|
|
13
|
+
pos: RailPos | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function DotRail(props: DotRailProps): ReactNode;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ScopeLike } from '../config.ts';
|
|
3
|
+
import { type SessionLike } from '../engines/useJump.ts';
|
|
4
|
+
export interface SessionsLike {
|
|
5
|
+
binding?(sessionId: string): {
|
|
6
|
+
session?: SessionLike & {
|
|
7
|
+
projections?: ProjectionsLike;
|
|
8
|
+
};
|
|
9
|
+
} | undefined;
|
|
10
|
+
}
|
|
11
|
+
interface ProjectionsLike {
|
|
12
|
+
faceOf?(key: string): {
|
|
13
|
+
getSnapshot(): unknown;
|
|
14
|
+
subscribe(fn: () => void): () => void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface NavOverlayProps {
|
|
18
|
+
scope?: ScopeLike;
|
|
19
|
+
sessions?: SessionsLike;
|
|
20
|
+
useSessions?: <S>(selector: (s: unknown) => S) => S;
|
|
21
|
+
t?: (key: string, vars?: Record<string, string | number>) => string;
|
|
22
|
+
}
|
|
23
|
+
export interface RailPos {
|
|
24
|
+
top: number;
|
|
25
|
+
height: number;
|
|
26
|
+
left?: number;
|
|
27
|
+
right?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function NavOverlay(props: NavOverlayProps): ReactNode;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ScopeLike } from '../config.ts';
|
|
3
|
+
import { type SessionLike } from '../engines/useJump.ts';
|
|
4
|
+
export interface SessionsLike {
|
|
5
|
+
binding?(sessionId: string): {
|
|
6
|
+
session?: SessionLike;
|
|
7
|
+
} | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface NavRailProps {
|
|
10
|
+
scope?: ScopeLike;
|
|
11
|
+
useProjection?: (key: string) => unknown;
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
sessions?: SessionsLike;
|
|
14
|
+
t?: (key: string, vars?: Record<string, string | number>) => string;
|
|
15
|
+
}
|
|
16
|
+
export declare function NavRail(props: NavRailProps): ReactNode;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { NavInjected, SessionSelector, Translate } from './shared/types.ts';
|
|
3
|
+
export interface RailDispatchProps {
|
|
4
|
+
injected: NavInjected | undefined;
|
|
5
|
+
useSessions?: SessionSelector;
|
|
6
|
+
t: Translate;
|
|
7
|
+
}
|
|
8
|
+
export declare function RailDispatch(props: RailDispatchProps): ReactNode;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { MsgNavItem } from '../../projection.ts';
|
|
3
|
+
import type { NavConfig } from '../../schema.ts';
|
|
4
|
+
import type { ScopeLike } from '../config.ts';
|
|
5
|
+
import { type SessionLike } from '../engines/useJump.ts';
|
|
6
|
+
export interface SessionsLike {
|
|
7
|
+
binding?(sessionId: string): {
|
|
8
|
+
session?: SessionLike;
|
|
9
|
+
} | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface RailHostProps {
|
|
12
|
+
scope?: ScopeLike;
|
|
13
|
+
useProjection?: (key: string) => unknown;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
sessions?: SessionsLike;
|
|
16
|
+
t?: (key: string, vars?: Record<string, string | number>) => string;
|
|
17
|
+
}
|
|
18
|
+
export declare function RailHost(props: RailHostProps): ReactNode;
|
|
19
|
+
export interface StyleCommon {
|
|
20
|
+
items: readonly MsgNavItem[];
|
|
21
|
+
align: 'left' | 'right';
|
|
22
|
+
collapsed: boolean;
|
|
23
|
+
setCollapsed(v: boolean): void;
|
|
24
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
25
|
+
general: NavConfig['general'];
|
|
26
|
+
jump(key: string): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface SearchBoxProps {
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
debounceMs: number;
|
|
5
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
6
|
+
onJump(key: string): void;
|
|
7
|
+
onClose(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function SearchBox(props: SearchBoxProps): ReactNode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ScopeLike } from '../config.ts';
|
|
3
|
+
export interface SettingsPageProps {
|
|
4
|
+
scope?: ScopeLike;
|
|
5
|
+
t(key: string, vars?: Record<string, string | number>): string;
|
|
6
|
+
/** mega-settings 已收纳时由宿主传入提示(自足页内检测) */
|
|
7
|
+
hosted?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function SettingsPage(props: SettingsPageProps): ReactNode;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ScopeLike } from '../config.ts';
|
|
3
|
+
interface SlotsLike {
|
|
4
|
+
entries?(key: string): readonly unknown[];
|
|
5
|
+
subscribe?(key: string, fn: () => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
export interface StandaloneSettingsProps {
|
|
8
|
+
scope?: ScopeLike;
|
|
9
|
+
slots?: SlotsLike;
|
|
10
|
+
t?(key: string, vars?: Record<string, string | number>): string;
|
|
11
|
+
}
|
|
12
|
+
export declare function StandaloneSettings(props: StandaloneSettingsProps): ReactNode;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { NavInjected, SessionListLike, Translate } from '../shared/types.ts';
|
|
3
|
+
export interface MinimalRailProps {
|
|
4
|
+
injected: NavInjected | undefined;
|
|
5
|
+
useSessions?: <S>(selector: (s: SessionListLike) => S) => S;
|
|
6
|
+
t: Translate;
|
|
7
|
+
}
|
|
8
|
+
export declare function MinimalRail(props: MinimalRailProps): ReactNode;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { RailAlign, CardItem } from '../../settings.ts';
|
|
3
|
+
import type { FocusState, RailMarker } from '../shared/types.ts';
|
|
4
|
+
export interface CardsProps {
|
|
5
|
+
focus: FocusState;
|
|
6
|
+
align: RailAlign;
|
|
7
|
+
/** 显示哪些内容块(多选配置) */
|
|
8
|
+
cardItems: readonly CardItem[];
|
|
9
|
+
/** 收藏集合 */
|
|
10
|
+
favorites: ReadonlySet<string>;
|
|
11
|
+
/** 切换收藏(按该轮首问锚点 key) */
|
|
12
|
+
onToggleFavorite: (key: string) => void;
|
|
13
|
+
onJump: (dot: RailMarker) => void;
|
|
14
|
+
/** 卡片 hover 保活(取消 rail 的清焦定时) */
|
|
15
|
+
onEnter: () => void;
|
|
16
|
+
/** 离开卡片后重新排程清焦 */
|
|
17
|
+
onLeave: () => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function Cards(props: CardsProps): ReactNode;
|