@hpyperry/dsh-ref-lib 0.8.0 → 0.10.0
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 +4 -3
- package/lib/client/RefLibCommandCard.d.ts +29 -0
- package/lib/client/RefLibCommandCard.d.ts.map +1 -0
- package/lib/client/RefLibCommandCard.js +48 -0
- package/lib/client/RefLibCommandCard.js.map +1 -0
- package/lib/client/RefLibDock.d.ts.map +1 -1
- package/lib/client/RefLibDock.js +97 -15
- package/lib/client/RefLibDock.js.map +1 -1
- package/lib/client/RefLibPanel.d.ts.map +1 -1
- package/lib/client/RefLibPanel.js +7 -2
- package/lib/client/RefLibPanel.js.map +1 -1
- package/lib/client/index.d.ts.map +1 -1
- package/lib/client/index.js +8 -0
- package/lib/client/index.js.map +1 -1
- package/lib/client/locales.d.ts +16 -0
- package/lib/client/locales.d.ts.map +1 -1
- package/lib/client/locales.js +20 -0
- package/lib/client/locales.js.map +1 -1
- package/lib/client/refresh-guard.d.ts +31 -0
- package/lib/client/refresh-guard.d.ts.map +1 -0
- package/lib/client/refresh-guard.js +38 -0
- package/lib/client/refresh-guard.js.map +1 -0
- package/lib/client/refresh-triggers.d.ts +49 -0
- package/lib/client/refresh-triggers.d.ts.map +1 -0
- package/lib/client/refresh-triggers.js +53 -0
- package/lib/client/refresh-triggers.js.map +1 -0
- package/lib/client/styles.d.ts.map +1 -1
- package/lib/client/styles.js +111 -0
- package/lib/client/styles.js.map +1 -1
- package/lib/client.js +927 -582
- package/lib/client.js.map +1 -1
- package/lib/logic.d.ts +16 -1
- package/lib/logic.d.ts.map +1 -1
- package/lib/logic.js +19 -0
- package/lib/logic.js.map +1 -1
- package/lib/render.d.ts +6 -4
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +14 -7
- package/lib/render.js.map +1 -1
- package/lib/routes.d.ts +4 -0
- package/lib/routes.d.ts.map +1 -1
- package/lib/routes.js +9 -1
- package/lib/routes.js.map +1 -1
- package/lib/service.d.ts +61 -3
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +148 -14
- package/lib/service.js.map +1 -1
- package/lib/spec.d.ts +9 -0
- package/lib/spec.d.ts.map +1 -1
- package/lib/validate.d.ts +1 -1
- package/lib/validate.d.ts.map +1 -1
- package/lib/validate.js +11 -3
- package/lib/validate.js.map +1 -1
- package/package.json +15 -15
package/lib/client.js
CHANGED
|
@@ -8,12 +8,21 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region src/validate.ts
|
|
11
|
-
/**
|
|
11
|
+
/** 可用性字面量集合(status 字段合法值)。 */
|
|
12
|
+
const AVAILABILITY_VALUES = /* @__PURE__ */ new Set([
|
|
13
|
+
"available",
|
|
14
|
+
"missing",
|
|
15
|
+
"not-directory"
|
|
16
|
+
]);
|
|
17
|
+
/** 校验一个未知对象是否为合法条目(宽松:id/path 必填字符串,note/status/checkedAt 可选且类型正确)。 */
|
|
12
18
|
function isRefLibEntry(value) {
|
|
13
19
|
if (typeof value !== "object" || value === null) return false;
|
|
14
|
-
const { id, path, note } = value;
|
|
20
|
+
const { id, path, note, status, checkedAt } = value;
|
|
15
21
|
if (typeof id !== "string" || typeof path !== "string") return false;
|
|
16
|
-
|
|
22
|
+
if (note !== void 0 && typeof note !== "string") return false;
|
|
23
|
+
if (status !== void 0 && (typeof status !== "string" || !AVAILABILITY_VALUES.has(status))) return false;
|
|
24
|
+
if (checkedAt !== void 0 && (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))) return false;
|
|
25
|
+
return true;
|
|
17
26
|
}
|
|
18
27
|
//#endregion
|
|
19
28
|
//#region src/client/data.ts
|
|
@@ -79,571 +88,18 @@ window.__ModuleLoader__.load({
|
|
|
79
88
|
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
80
89
|
}
|
|
81
90
|
//#endregion
|
|
82
|
-
//#region src/client/
|
|
83
|
-
/**
|
|
84
|
-
* 应用内目录浏览器(browse 后端可用时,「选择目录」的落地实现)。
|
|
85
|
-
*
|
|
86
|
-
* 为什么需要它:v6 起 profile 把 directory-picker 从 -auto(本机解析为 native,
|
|
87
|
-
* OS 对话框硬编码英文且每次拉 osascript 进程)切换为 -browse 后,
|
|
88
|
-
* `host.pickDirectory`(native 专用 RPC)不再可用;本组件用 `listDirectory`
|
|
89
|
-
* (Node stdlib 列目录,zh/en 本地化、无 OS 进程)提供等价的目录选择:
|
|
90
|
-
* 面包屑 + 路径编辑 + 子目录列表,点击下行进入,「选择此目录」把当前层级添加为
|
|
91
|
-
* 参考库。简化为单列(与官方 DirectoryBrowser 的 Miller 双列相比),
|
|
92
|
-
* 但覆盖添加参考库的核心路径。
|
|
93
|
-
* @module @hpyperry/dsh-ref-lib/src/client/RefLibBrowser
|
|
94
|
-
*/
|
|
95
|
-
/** 面包屑展示:主目录子树内以本地化 Home 开头;之外显示完整祖先链。 */
|
|
96
|
-
function displayCrumbs(listing, homeLabel) {
|
|
97
|
-
const homeIndex = listing.crumbs.findIndex((crumb) => crumb.path === listing.home);
|
|
98
|
-
if (homeIndex === -1) return listing.crumbs;
|
|
99
|
-
const tail = listing.crumbs.slice(homeIndex + 1);
|
|
100
|
-
return [{
|
|
101
|
-
name: homeLabel,
|
|
102
|
-
path: listing.home,
|
|
103
|
-
hidden: false
|
|
104
|
-
}, ...tail];
|
|
105
|
-
}
|
|
106
|
-
/** 平台分隔符(由宿主主目录推断,绝不从键入文本推断)。 */
|
|
107
|
-
function separatorOf(listing) {
|
|
108
|
-
return listing.home.includes("\\") ? "\\" : "/";
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* 渲染应用内目录浏览器。
|
|
112
|
-
* @param props - 见 {@link RefLibBrowserProps}。
|
|
113
|
-
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
114
|
-
*/
|
|
115
|
-
function RefLibBrowser(props) {
|
|
116
|
-
const { open, onClose, listDirectory, t, onOpen } = props;
|
|
117
|
-
const [listing, setListing] = (0, react.useState)(null);
|
|
118
|
-
const [loading, setLoading] = (0, react.useState)(false);
|
|
119
|
-
const [error, setError] = (0, react.useState)(null);
|
|
120
|
-
const [pathDraft, setPathDraft] = (0, react.useState)(null);
|
|
121
|
-
const seq = (0, react.useRef)(0);
|
|
122
|
-
const controller = (0, react.useRef)(null);
|
|
123
|
-
const composing = (0, react.useRef)(false);
|
|
124
|
-
/** 新意图胜出:中止在途扫描并使旧结果失效。 */
|
|
125
|
-
const navigate = (target) => {
|
|
126
|
-
controller.current?.abort();
|
|
127
|
-
controller.current = null;
|
|
128
|
-
const mine = ++seq.current;
|
|
129
|
-
setLoading(true);
|
|
130
|
-
setError(null);
|
|
131
|
-
const ctrl = new AbortController();
|
|
132
|
-
controller.current = ctrl;
|
|
133
|
-
listDirectory(target, ctrl.signal).then((next) => {
|
|
134
|
-
if (mine !== seq.current) return;
|
|
135
|
-
setListing(next);
|
|
136
|
-
setLoading(false);
|
|
137
|
-
}, (cause) => {
|
|
138
|
-
if (mine !== seq.current) return;
|
|
139
|
-
setLoading(false);
|
|
140
|
-
setError(cause instanceof Error ? cause.message : String(cause));
|
|
141
|
-
});
|
|
142
|
-
};
|
|
143
|
-
(0, react.useEffect)(() => {
|
|
144
|
-
if (open) navigate();
|
|
145
|
-
else {
|
|
146
|
-
seq.current += 1;
|
|
147
|
-
controller.current?.abort();
|
|
148
|
-
controller.current = null;
|
|
149
|
-
setPathDraft(null);
|
|
150
|
-
setError(null);
|
|
151
|
-
}
|
|
152
|
-
}, [open]);
|
|
153
|
-
const openPathEditor = () => {
|
|
154
|
-
if (listing === null) {
|
|
155
|
-
setPathDraft("");
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const sep = separatorOf(listing);
|
|
159
|
-
const base = listing.path;
|
|
160
|
-
setPathDraft(base.endsWith(sep) ? base : base + sep);
|
|
161
|
-
};
|
|
162
|
-
const commitPath = () => {
|
|
163
|
-
const draft = pathDraft ?? "";
|
|
164
|
-
if (draft.trim() === "") return;
|
|
165
|
-
navigate(draft);
|
|
166
|
-
setPathDraft(null);
|
|
167
|
-
};
|
|
168
|
-
const crumbs = listing === null ? [] : displayCrumbs(listing, t("browser.home"));
|
|
169
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
170
|
-
open,
|
|
171
|
-
onClose,
|
|
172
|
-
title: t("browser.title"),
|
|
173
|
-
closeLabel: t("browser.cancel"),
|
|
174
|
-
className: "reflib-browser",
|
|
175
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
176
|
-
className: "reflib-panel",
|
|
177
|
-
children: [
|
|
178
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
179
|
-
className: "reflib-error",
|
|
180
|
-
role: "alert",
|
|
181
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
182
|
-
size: 14,
|
|
183
|
-
className: "reflib-errorIcon"
|
|
184
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
185
|
-
}),
|
|
186
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
187
|
-
className: "reflib-browserPath",
|
|
188
|
-
children: pathDraft === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
189
|
-
className: "reflib-browserCrumbs",
|
|
190
|
-
role: "navigation",
|
|
191
|
-
children: crumbs.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
192
|
-
className: "reflib-browserCrumb",
|
|
193
|
-
children: [index > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {
|
|
194
|
-
size: 12,
|
|
195
|
-
className: "reflib-browserCrumbChevron"
|
|
196
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
197
|
-
type: "button",
|
|
198
|
-
className: "reflib-browserCrumbBtn",
|
|
199
|
-
disabled: loading,
|
|
200
|
-
onClick: () => {
|
|
201
|
-
navigate(crumb.path);
|
|
202
|
-
},
|
|
203
|
-
children: crumb.name
|
|
204
|
-
})]
|
|
205
|
-
}, crumb.path))
|
|
206
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
207
|
-
type: "button",
|
|
208
|
-
className: "reflib-browserEdit",
|
|
209
|
-
"aria-label": t("browser.editPath"),
|
|
210
|
-
title: t("browser.editPath"),
|
|
211
|
-
disabled: loading,
|
|
212
|
-
onClick: openPathEditor,
|
|
213
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
|
|
214
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
215
|
-
className: "reflib-browserInput",
|
|
216
|
-
value: pathDraft,
|
|
217
|
-
"aria-label": t("browser.editPath"),
|
|
218
|
-
placeholder: t("browser.pathPlaceholder"),
|
|
219
|
-
autoFocus: true,
|
|
220
|
-
disabled: loading,
|
|
221
|
-
onChange: (event) => {
|
|
222
|
-
setPathDraft(event.currentTarget.value);
|
|
223
|
-
},
|
|
224
|
-
onCompositionStart: () => {
|
|
225
|
-
composing.current = true;
|
|
226
|
-
},
|
|
227
|
-
onCompositionEnd: () => {
|
|
228
|
-
composing.current = false;
|
|
229
|
-
},
|
|
230
|
-
onKeyDown: (event) => {
|
|
231
|
-
if (event.key === "Enter" && !composing.current) {
|
|
232
|
-
event.preventDefault();
|
|
233
|
-
commitPath();
|
|
234
|
-
}
|
|
235
|
-
if (event.key === "Escape") {
|
|
236
|
-
event.stopPropagation();
|
|
237
|
-
setPathDraft(null);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
|
-
}),
|
|
242
|
-
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
243
|
-
className: "reflib-status",
|
|
244
|
-
role: "status",
|
|
245
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
246
|
-
size: 20,
|
|
247
|
-
className: "reflib-spin reflib-statusIcon"
|
|
248
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
249
|
-
className: "reflib-statusText",
|
|
250
|
-
children: t("browser.loading")
|
|
251
|
-
})]
|
|
252
|
-
}) : listing !== null && listing.entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
253
|
-
className: "reflib-status",
|
|
254
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
255
|
-
size: 22,
|
|
256
|
-
className: "reflib-statusIcon"
|
|
257
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
258
|
-
className: "reflib-statusText",
|
|
259
|
-
children: t("browser.empty")
|
|
260
|
-
})]
|
|
261
|
-
}) : listing !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
262
|
-
className: "reflib-browserList",
|
|
263
|
-
children: listing.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
264
|
-
type: "button",
|
|
265
|
-
className: "reflib-browserRow",
|
|
266
|
-
"aria-label": t("browser.enter.aria", { name: entry.name }),
|
|
267
|
-
disabled: loading,
|
|
268
|
-
onClick: () => {
|
|
269
|
-
navigate(entry.path);
|
|
270
|
-
},
|
|
271
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {
|
|
272
|
-
size: 16,
|
|
273
|
-
className: "reflib-browserRowIcon"
|
|
274
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
275
|
-
className: "reflib-browserRowName",
|
|
276
|
-
children: entry.name
|
|
277
|
-
})]
|
|
278
|
-
}, entry.path))
|
|
279
|
-
}),
|
|
280
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
281
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
282
|
-
className: "reflib-browserFooter",
|
|
283
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
284
|
-
variant: "outline",
|
|
285
|
-
size: "sm",
|
|
286
|
-
disabled: loading,
|
|
287
|
-
onClick: onClose,
|
|
288
|
-
children: t("browser.cancel")
|
|
289
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
290
|
-
variant: "primary",
|
|
291
|
-
size: "sm",
|
|
292
|
-
disabled: loading || listing === null,
|
|
293
|
-
"aria-label": t("browser.open.aria"),
|
|
294
|
-
onClick: () => {
|
|
295
|
-
if (listing !== null) onOpen(listing.path);
|
|
296
|
-
},
|
|
297
|
-
children: t("browser.open")
|
|
298
|
-
})]
|
|
299
|
-
})
|
|
300
|
-
]
|
|
301
|
-
})
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
//#endregion
|
|
305
|
-
//#region src/client/RefLibPanel.tsx
|
|
91
|
+
//#region src/client/styles.ts
|
|
306
92
|
/**
|
|
307
|
-
*
|
|
93
|
+
* ref-lib client 样式注入:在文档头挂一张本插件专属的 <style data-plugin> 标签。
|
|
308
94
|
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const NOTE_MAX_LENGTH = 120;
|
|
318
|
-
/**
|
|
319
|
-
* 渲染参考库管理面板。
|
|
320
|
-
* @param props - 见 {@link RefLibPanelProps}。
|
|
321
|
-
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
322
|
-
*/
|
|
323
|
-
function RefLibPanel(props) {
|
|
324
|
-
const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onUpdateNote, onBrowse } = props;
|
|
325
|
-
const [draft, setDraft] = (0, react.useState)("");
|
|
326
|
-
const [noteDraft, setNoteDraft] = (0, react.useState)("");
|
|
327
|
-
const [detailId, setDetailId] = (0, react.useState)(null);
|
|
328
|
-
const [editNote, setEditNote] = (0, react.useState)("");
|
|
329
|
-
(0, react.useEffect)(() => {
|
|
330
|
-
if (!open) {
|
|
331
|
-
setDraft("");
|
|
332
|
-
setNoteDraft("");
|
|
333
|
-
setDetailId(null);
|
|
334
|
-
}
|
|
335
|
-
}, [open]);
|
|
336
|
-
const handleSubmit = async () => {
|
|
337
|
-
const trimmed = draft.trim();
|
|
338
|
-
if (trimmed === "") return;
|
|
339
|
-
try {
|
|
340
|
-
const note = noteDraft.trim();
|
|
341
|
-
await onAddPath(trimmed, note === "" ? void 0 : note);
|
|
342
|
-
setDraft("");
|
|
343
|
-
setNoteDraft("");
|
|
344
|
-
} catch {}
|
|
345
|
-
};
|
|
346
|
-
/** 浏览选中目录 → 填入路径字段(不直接添加,用户可补用途后提交)。 */
|
|
347
|
-
const handleBrowse = async () => {
|
|
348
|
-
const path = await onBrowse();
|
|
349
|
-
if (path !== null) setDraft(path);
|
|
350
|
-
};
|
|
351
|
-
/** 展开/收起条目详情;展开时载入当前用途草稿。 */
|
|
352
|
-
const toggleDetail = (entry) => {
|
|
353
|
-
if (detailId === entry.id) {
|
|
354
|
-
setDetailId(null);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
setDetailId(entry.id);
|
|
358
|
-
setEditNote(entry.note ?? "");
|
|
359
|
-
};
|
|
360
|
-
/** 保存详情编辑的用途说明;成功后收起详情。 */
|
|
361
|
-
const handleSaveNote = async () => {
|
|
362
|
-
if (detailId === null) return;
|
|
363
|
-
await onUpdateNote(detailId, editNote);
|
|
364
|
-
setDetailId(null);
|
|
365
|
-
};
|
|
366
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
367
|
-
open,
|
|
368
|
-
onClose,
|
|
369
|
-
title: t("panel.title"),
|
|
370
|
-
closeLabel: t("panel.close"),
|
|
371
|
-
description: t("panel.description", { count: String(libs.length) }),
|
|
372
|
-
className: "reflib-modal",
|
|
373
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
374
|
-
className: "reflib-panel",
|
|
375
|
-
children: [
|
|
376
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
377
|
-
className: "reflib-error",
|
|
378
|
-
role: "alert",
|
|
379
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
380
|
-
size: 14,
|
|
381
|
-
className: "reflib-errorIcon"
|
|
382
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
383
|
-
}),
|
|
384
|
-
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
385
|
-
className: "reflib-status",
|
|
386
|
-
role: "status",
|
|
387
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
388
|
-
size: 20,
|
|
389
|
-
className: "reflib-spin reflib-statusIcon"
|
|
390
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
391
|
-
className: "reflib-statusText",
|
|
392
|
-
children: t("list.loading")
|
|
393
|
-
})]
|
|
394
|
-
}) : libs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
395
|
-
className: "reflib-status",
|
|
396
|
-
children: [
|
|
397
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
398
|
-
size: 22,
|
|
399
|
-
className: "reflib-statusIcon"
|
|
400
|
-
}),
|
|
401
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
402
|
-
className: "reflib-statusText",
|
|
403
|
-
children: t("list.empty")
|
|
404
|
-
}),
|
|
405
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
406
|
-
className: "reflib-statusHint",
|
|
407
|
-
children: t("list.empty.hint")
|
|
408
|
-
})
|
|
409
|
-
]
|
|
410
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
411
|
-
className: "reflib-list",
|
|
412
|
-
children: libs.map((entry) => {
|
|
413
|
-
const name = libBasename(entry.path);
|
|
414
|
-
const removing = removingId === entry.id;
|
|
415
|
-
const detailOpen = detailId === entry.id;
|
|
416
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
417
|
-
className: "reflib-listItem",
|
|
418
|
-
"data-removing": removing || void 0,
|
|
419
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
420
|
-
className: "reflib-row",
|
|
421
|
-
children: [
|
|
422
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
423
|
-
size: 16,
|
|
424
|
-
className: "reflib-rowIcon"
|
|
425
|
-
}),
|
|
426
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
427
|
-
className: "reflib-rowBody",
|
|
428
|
-
children: [
|
|
429
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
430
|
-
className: "reflib-rowName",
|
|
431
|
-
title: entry.path,
|
|
432
|
-
children: name
|
|
433
|
-
}),
|
|
434
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
435
|
-
className: "reflib-rowPath",
|
|
436
|
-
title: entry.path,
|
|
437
|
-
children: entry.path
|
|
438
|
-
}),
|
|
439
|
-
entry.note !== void 0 && entry.note !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
440
|
-
className: "reflib-rowNote",
|
|
441
|
-
title: entry.note,
|
|
442
|
-
children: entry.note.replace(/\s+/g, " ")
|
|
443
|
-
})
|
|
444
|
-
]
|
|
445
|
-
}),
|
|
446
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
447
|
-
label: t("detail.open"),
|
|
448
|
-
side: "top",
|
|
449
|
-
delayMs: 400,
|
|
450
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
451
|
-
type: "button",
|
|
452
|
-
className: "reflib-rowAction",
|
|
453
|
-
"aria-label": t("detail.open.aria", { name }),
|
|
454
|
-
"aria-expanded": detailOpen || void 0,
|
|
455
|
-
disabled: busy || removingId !== null,
|
|
456
|
-
onClick: () => {
|
|
457
|
-
toggleDetail(entry);
|
|
458
|
-
},
|
|
459
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 14 })
|
|
460
|
-
})
|
|
461
|
-
}),
|
|
462
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
463
|
-
label: t("list.remove"),
|
|
464
|
-
side: "top",
|
|
465
|
-
delayMs: 400,
|
|
466
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
467
|
-
type: "button",
|
|
468
|
-
className: "reflib-rowRemove",
|
|
469
|
-
"aria-label": t("list.remove.aria", { name }),
|
|
470
|
-
disabled: busy || removingId !== null,
|
|
471
|
-
onClick: () => {
|
|
472
|
-
onRemove(entry.id);
|
|
473
|
-
},
|
|
474
|
-
children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
475
|
-
size: 14,
|
|
476
|
-
className: "reflib-spin"
|
|
477
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
478
|
-
})
|
|
479
|
-
})
|
|
480
|
-
]
|
|
481
|
-
}), detailOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
482
|
-
className: "reflib-detail",
|
|
483
|
-
children: [
|
|
484
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
485
|
-
className: "reflib-detailMeta",
|
|
486
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
487
|
-
className: "reflib-detailKey",
|
|
488
|
-
children: "ID"
|
|
489
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
490
|
-
className: "reflib-detailValue",
|
|
491
|
-
children: entry.id
|
|
492
|
-
})]
|
|
493
|
-
}),
|
|
494
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
495
|
-
className: "reflib-detailMeta",
|
|
496
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
497
|
-
className: "reflib-detailKey",
|
|
498
|
-
children: t("detail.path")
|
|
499
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
500
|
-
className: "reflib-detailValue",
|
|
501
|
-
title: entry.path,
|
|
502
|
-
children: entry.path
|
|
503
|
-
})]
|
|
504
|
-
}),
|
|
505
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
506
|
-
className: "reflib-detailKey",
|
|
507
|
-
children: t("add.note.label")
|
|
508
|
-
}),
|
|
509
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
510
|
-
className: "reflib-noteWrap",
|
|
511
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
512
|
-
className: "reflib-noteTextarea",
|
|
513
|
-
value: editNote,
|
|
514
|
-
placeholder: t("add.note.placeholder"),
|
|
515
|
-
"aria-label": t("add.note.label"),
|
|
516
|
-
disabled: busy,
|
|
517
|
-
rows: 3,
|
|
518
|
-
maxLength: NOTE_MAX_LENGTH,
|
|
519
|
-
onChange: (event) => {
|
|
520
|
-
setEditNote(event.currentTarget.value);
|
|
521
|
-
}
|
|
522
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
523
|
-
className: "reflib-noteCount",
|
|
524
|
-
children: [
|
|
525
|
-
editNote.length,
|
|
526
|
-
"/",
|
|
527
|
-
NOTE_MAX_LENGTH
|
|
528
|
-
]
|
|
529
|
-
})]
|
|
530
|
-
}),
|
|
531
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
532
|
-
className: "reflib-detailActions",
|
|
533
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
534
|
-
variant: "ghost",
|
|
535
|
-
size: "sm",
|
|
536
|
-
disabled: busy,
|
|
537
|
-
onClick: () => {
|
|
538
|
-
setDetailId(null);
|
|
539
|
-
},
|
|
540
|
-
children: t("detail.cancel")
|
|
541
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
542
|
-
variant: "primary",
|
|
543
|
-
size: "sm",
|
|
544
|
-
disabled: busy,
|
|
545
|
-
onClick: () => {
|
|
546
|
-
handleSaveNote();
|
|
547
|
-
},
|
|
548
|
-
children: t("detail.save")
|
|
549
|
-
})]
|
|
550
|
-
})
|
|
551
|
-
]
|
|
552
|
-
})]
|
|
553
|
-
}, entry.id);
|
|
554
|
-
})
|
|
555
|
-
}),
|
|
556
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
557
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
558
|
-
className: "reflib-add",
|
|
559
|
-
children: [
|
|
560
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
561
|
-
className: "reflib-addLabel",
|
|
562
|
-
children: t("add.label")
|
|
563
|
-
}),
|
|
564
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
565
|
-
className: "reflib-addRow",
|
|
566
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
567
|
-
className: "reflib-addInputWrap",
|
|
568
|
-
value: draft,
|
|
569
|
-
placeholder: t("add.placeholder"),
|
|
570
|
-
"aria-label": t("add.label"),
|
|
571
|
-
disabled: busy || picking,
|
|
572
|
-
onChange: (event) => {
|
|
573
|
-
setDraft(event.currentTarget.value);
|
|
574
|
-
},
|
|
575
|
-
onKeyDown: (event) => {
|
|
576
|
-
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
577
|
-
event.preventDefault();
|
|
578
|
-
handleSubmit();
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
582
|
-
variant: "outline",
|
|
583
|
-
size: "sm",
|
|
584
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
585
|
-
disabled: busy || picking,
|
|
586
|
-
onClick: () => {
|
|
587
|
-
handleBrowse();
|
|
588
|
-
},
|
|
589
|
-
children: t("add.browse")
|
|
590
|
-
})]
|
|
591
|
-
}),
|
|
592
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
593
|
-
className: "reflib-noteWrap",
|
|
594
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
595
|
-
className: "reflib-noteTextarea",
|
|
596
|
-
value: noteDraft,
|
|
597
|
-
placeholder: t("add.note.placeholder"),
|
|
598
|
-
"aria-label": t("add.note.label"),
|
|
599
|
-
disabled: busy || picking,
|
|
600
|
-
rows: 2,
|
|
601
|
-
maxLength: NOTE_MAX_LENGTH,
|
|
602
|
-
onChange: (event) => {
|
|
603
|
-
setNoteDraft(event.currentTarget.value);
|
|
604
|
-
}
|
|
605
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
606
|
-
className: "reflib-noteCount",
|
|
607
|
-
children: [
|
|
608
|
-
noteDraft.length,
|
|
609
|
-
"/",
|
|
610
|
-
NOTE_MAX_LENGTH
|
|
611
|
-
]
|
|
612
|
-
})]
|
|
613
|
-
}),
|
|
614
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
615
|
-
variant: "primary",
|
|
616
|
-
className: "reflib-addSubmit",
|
|
617
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
|
|
618
|
-
disabled: busy || picking || draft.trim() === "",
|
|
619
|
-
onClick: () => {
|
|
620
|
-
handleSubmit();
|
|
621
|
-
},
|
|
622
|
-
children: t("add.submit")
|
|
623
|
-
}),
|
|
624
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
625
|
-
className: "reflib-addHint",
|
|
626
|
-
children: t("add.hint")
|
|
627
|
-
})
|
|
628
|
-
]
|
|
629
|
-
})
|
|
630
|
-
]
|
|
631
|
-
})
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
//#endregion
|
|
635
|
-
//#region src/client/styles.ts
|
|
636
|
-
/**
|
|
637
|
-
* ref-lib client 样式注入:在文档头挂一张本插件专属的 <style data-plugin> 标签。
|
|
638
|
-
*
|
|
639
|
-
* 为什么不用 CSS Modules:ref-lib 的 tsdown 配置未移植 core 的 lightningcss
|
|
640
|
-
* CSS-Modules 插件(避免新增 native 依赖与构建面),而内联 style 无法表达
|
|
641
|
-
* hover/媒体查询/动画。因此采用运行时注入单张样式表——与 loader 的清理契约
|
|
642
|
-
* 兼容(`data-plugin` 标签在插件卸载/HMR 时由 loader 移除,重新物化时本模块
|
|
643
|
-
* 重新注入,幂等)。类名统一 `reflib-` 前缀防碰撞;颜色/字号/圆角全部走
|
|
644
|
-
* `--dsw-*` 设计令牌,随主题(浅色/深色)自动适配——这是"字体显示不一致、
|
|
645
|
-
* 没有设计感"的根因修复之一(旧实现硬编码 #d33 等,不随主题变化)。
|
|
646
|
-
* @module @hpyperry/dsh-ref-lib/src/client/styles
|
|
95
|
+
* 为什么不用 CSS Modules:ref-lib 的 tsdown 配置未移植 core 的 lightningcss
|
|
96
|
+
* CSS-Modules 插件(避免新增 native 依赖与构建面),而内联 style 无法表达
|
|
97
|
+
* hover/媒体查询/动画。因此采用运行时注入单张样式表——与 loader 的清理契约
|
|
98
|
+
* 兼容(`data-plugin` 标签在插件卸载/HMR 时由 loader 移除,重新物化时本模块
|
|
99
|
+
* 重新注入,幂等)。类名统一 `reflib-` 前缀防碰撞;颜色/字号/圆角全部走
|
|
100
|
+
* `--dsw-*` 设计令牌,随主题(浅色/深色)自动适配——这是"字体显示不一致、
|
|
101
|
+
* 没有设计感"的根因修复之一(旧实现硬编码 #d33 等,不随主题变化)。
|
|
102
|
+
* @module @hpyperry/dsh-ref-lib/src/client/styles
|
|
647
103
|
*/
|
|
648
104
|
/** 插件 id(与 package.json 的 bundle 手递一致,loader 按它清理样式)。 */
|
|
649
105
|
const PLUGIN_ID = "@hpyperry/dsh-ref-lib";
|
|
@@ -727,6 +183,22 @@ window.__ModuleLoader__.load({
|
|
|
727
183
|
font-weight: 500;
|
|
728
184
|
}
|
|
729
185
|
|
|
186
|
+
/* 失效条目计数角标(v9):红色系,与普通数量徽标区分。 */
|
|
187
|
+
.reflib-chipWarn {
|
|
188
|
+
display: inline-flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
justify-content: center;
|
|
191
|
+
min-width: 18px;
|
|
192
|
+
height: 18px;
|
|
193
|
+
padding: 0 5px;
|
|
194
|
+
border-radius: 9px;
|
|
195
|
+
background: var(--dsw-alias-state-error-primary);
|
|
196
|
+
color: #fff;
|
|
197
|
+
font-size: 11px;
|
|
198
|
+
line-height: 18px;
|
|
199
|
+
font-weight: 500;
|
|
200
|
+
}
|
|
201
|
+
|
|
730
202
|
/* ── 应用内目录浏览器(browse 后端)── */
|
|
731
203
|
.reflib-browser {
|
|
732
204
|
width: min(460px, 100%);
|
|
@@ -942,6 +414,11 @@ window.__ModuleLoader__.load({
|
|
|
942
414
|
color: var(--dsw-alias-label-tertiary);
|
|
943
415
|
}
|
|
944
416
|
|
|
417
|
+
/* 失效条目行图标(v9):警示色。 */
|
|
418
|
+
.reflib-rowIconWarn {
|
|
419
|
+
color: var(--dsw-alias-state-error-primary);
|
|
420
|
+
}
|
|
421
|
+
|
|
945
422
|
.reflib-rowBody {
|
|
946
423
|
display: flex;
|
|
947
424
|
flex: 1;
|
|
@@ -980,6 +457,16 @@ window.__ModuleLoader__.load({
|
|
|
980
457
|
white-space: nowrap;
|
|
981
458
|
}
|
|
982
459
|
|
|
460
|
+
/* 失效条目状态行(v9):红色警示文案,位于路径之下 */
|
|
461
|
+
.reflib-rowStatus {
|
|
462
|
+
overflow: hidden;
|
|
463
|
+
color: var(--dsw-alias-state-error-primary);
|
|
464
|
+
font-size: 12px;
|
|
465
|
+
line-height: 16px;
|
|
466
|
+
text-overflow: ellipsis;
|
|
467
|
+
white-space: nowrap;
|
|
468
|
+
}
|
|
469
|
+
|
|
983
470
|
.reflib-rowRemove {
|
|
984
471
|
display: inline-flex;
|
|
985
472
|
align-items: center;
|
|
@@ -1253,6 +740,86 @@ window.__ModuleLoader__.load({
|
|
|
1253
740
|
color: var(--dsw-alias-label-caption);
|
|
1254
741
|
}
|
|
1255
742
|
|
|
743
|
+
/* ── /ref-lib 命令结果卡片(conversation.chat.commandview)── */
|
|
744
|
+
.reflib-cmd {
|
|
745
|
+
display: flex;
|
|
746
|
+
flex-direction: column;
|
|
747
|
+
gap: 6px;
|
|
748
|
+
min-width: 0;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.reflib-cmdHead {
|
|
752
|
+
display: flex;
|
|
753
|
+
align-items: center;
|
|
754
|
+
gap: 8px;
|
|
755
|
+
min-width: 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.reflib-cmdHeadIcon {
|
|
759
|
+
flex: none;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.reflib-cmdName {
|
|
763
|
+
flex: none;
|
|
764
|
+
color: var(--dsw-alias-label-primary);
|
|
765
|
+
font-size: 14px;
|
|
766
|
+
line-height: 20px;
|
|
767
|
+
font-weight: 500;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* 摘要行 = 结果首行;过长单行省略(完整结果见下方 body) */
|
|
771
|
+
.reflib-cmdSummary {
|
|
772
|
+
min-width: 0;
|
|
773
|
+
flex: 1 1 auto;
|
|
774
|
+
overflow: hidden;
|
|
775
|
+
color: var(--dsw-alias-label-tertiary);
|
|
776
|
+
font-size: 13px;
|
|
777
|
+
line-height: 20px;
|
|
778
|
+
text-overflow: ellipsis;
|
|
779
|
+
white-space: nowrap;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.reflib-cmdSummary[data-error] {
|
|
783
|
+
color: var(--dsw-alias-state-error-primary);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.reflib-cmdCopy {
|
|
787
|
+
flex: none;
|
|
788
|
+
display: inline-flex;
|
|
789
|
+
align-items: center;
|
|
790
|
+
gap: 4px;
|
|
791
|
+
padding: 2px 8px;
|
|
792
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
793
|
+
border-radius: 6px;
|
|
794
|
+
background: transparent;
|
|
795
|
+
color: var(--dsw-alias-label-secondary);
|
|
796
|
+
font-size: 12px;
|
|
797
|
+
line-height: 18px;
|
|
798
|
+
cursor: pointer;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
.reflib-cmdCopy:hover {
|
|
802
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* 完整结果:默认全展开(无 max-height 截断),超宽自动换行 */
|
|
806
|
+
.reflib-cmdBody {
|
|
807
|
+
margin: 0;
|
|
808
|
+
padding: 10px 14px;
|
|
809
|
+
overflow: auto;
|
|
810
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
811
|
+
border-radius: 10px;
|
|
812
|
+
background: var(--dsw-alias-markdown-code-block);
|
|
813
|
+
color: var(--dsw-alias-label-primary);
|
|
814
|
+
font: var(--dsw-font-markdown-code-block-small);
|
|
815
|
+
white-space: pre-wrap;
|
|
816
|
+
word-break: break-word;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.reflib-cmdBody[data-error] {
|
|
820
|
+
color: var(--dsw-alias-state-error-primary);
|
|
821
|
+
}
|
|
822
|
+
|
|
1256
823
|
/* 窄视口:路径行换行,输入框占满一行 */
|
|
1257
824
|
@media (max-width: 440px) {
|
|
1258
825
|
.reflib-addRow {
|
|
@@ -1279,6 +846,705 @@ window.__ModuleLoader__.load({
|
|
|
1279
846
|
document.head.appendChild(tag);
|
|
1280
847
|
}
|
|
1281
848
|
//#endregion
|
|
849
|
+
//#region src/client/RefLibCommandCard.tsx
|
|
850
|
+
/**
|
|
851
|
+
* `/ref-lib` 命令结果专属卡片(conversation.chat.commandview,key='ref-lib')。
|
|
852
|
+
*
|
|
853
|
+
* 背景(历史 bug):官方 `GenericCommandCard` 对命令结果折叠态 `white-space:
|
|
854
|
+
* nowrap` + `text-overflow: ellipsis` 单行截断,展开态 `max-height: 260px` 内滚动
|
|
855
|
+
* ——`/ref-lib list` 等多行长结果会显示不全。本卡片注册在
|
|
856
|
+
* `conversation.chat.commandview`(keyed,按命令名分派)的 `ref-lib` 键上:
|
|
857
|
+
* **默认全展开展示完整结果**(无高度上限)+ 一键复制按钮。只影响 `/ref-lib`,
|
|
858
|
+
* 其他命令仍走官方卡片。
|
|
859
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibCommandCard
|
|
860
|
+
*/
|
|
861
|
+
ensureRefLibStyles();
|
|
862
|
+
/**
|
|
863
|
+
* `/ref-lib` 命令结果卡片:完整展示 `outcome.text`(pre-wrap、无高度截断)+
|
|
864
|
+
* 复制按钮。running 态显示执行中;error 态红色。
|
|
865
|
+
* @param props - node(owner)与本地化 seat。
|
|
866
|
+
* @returns 卡片元素。
|
|
867
|
+
*/
|
|
868
|
+
function RefLibCommandCard(props) {
|
|
869
|
+
const { node, t } = props;
|
|
870
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
871
|
+
const outcome = node.outcome;
|
|
872
|
+
const text = outcome?.text;
|
|
873
|
+
const running = outcome === null;
|
|
874
|
+
const error = outcome?.kind === "error";
|
|
875
|
+
/** 复制完整结果到剪贴板;成功后短暂显示"已复制"(权限失败静默)。 */
|
|
876
|
+
const copy = async () => {
|
|
877
|
+
if (text === void 0 || text === "") return;
|
|
878
|
+
try {
|
|
879
|
+
await navigator.clipboard.writeText(text);
|
|
880
|
+
setCopied(true);
|
|
881
|
+
window.setTimeout(() => {
|
|
882
|
+
setCopied(false);
|
|
883
|
+
}, 1500);
|
|
884
|
+
} catch {}
|
|
885
|
+
};
|
|
886
|
+
const summary = running ? t("command.running") : text !== void 0 && text !== "" ? text.split("\n")[0] : t("command.done");
|
|
887
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
888
|
+
className: "reflib-cmd",
|
|
889
|
+
"data-state": running ? "running" : error ? "error" : "ok",
|
|
890
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
891
|
+
className: "reflib-cmdHead",
|
|
892
|
+
children: [
|
|
893
|
+
running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
894
|
+
size: 14,
|
|
895
|
+
className: "reflib-spin reflib-cmdHeadIcon"
|
|
896
|
+
}),
|
|
897
|
+
!running && error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
898
|
+
size: 14,
|
|
899
|
+
className: "reflib-cmdHeadIcon"
|
|
900
|
+
}),
|
|
901
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
902
|
+
className: "reflib-cmdName",
|
|
903
|
+
children: "/ref-lib"
|
|
904
|
+
}),
|
|
905
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
906
|
+
className: "reflib-cmdSummary",
|
|
907
|
+
"data-error": error || void 0,
|
|
908
|
+
children: summary
|
|
909
|
+
}),
|
|
910
|
+
!running && text !== void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
911
|
+
type: "button",
|
|
912
|
+
className: "reflib-cmdCopy",
|
|
913
|
+
"aria-label": t("command.copy"),
|
|
914
|
+
title: t("command.copy"),
|
|
915
|
+
onClick: () => {
|
|
916
|
+
copy();
|
|
917
|
+
},
|
|
918
|
+
children: [copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 }), copied ? t("command.copied") : t("command.copy")]
|
|
919
|
+
})
|
|
920
|
+
]
|
|
921
|
+
}), !running && text !== void 0 && text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
922
|
+
className: "reflib-cmdBody",
|
|
923
|
+
"data-error": error || void 0,
|
|
924
|
+
children: text
|
|
925
|
+
})]
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/client/refresh-guard.ts
|
|
930
|
+
/**
|
|
931
|
+
* 并发读守卫(竞态控制核心):只接受"最后发起"的请求结果。
|
|
932
|
+
*
|
|
933
|
+
* RefLibDock 有多个并发"拉取列表"来源——挂载预载、打开面板、30s 轮询、操作后的
|
|
934
|
+
* 刷新——网络返回顺序不保证,旧快照可能覆盖新快照。守卫用递增编号标记每次发起:
|
|
935
|
+
* 返回时只有仍持有**最新编号**的请求结果才被应用,其余一律丢弃。
|
|
936
|
+
*
|
|
937
|
+
* 为什么"最后发起"就代表最新:`list` 返回的是请求发起时刻的服务端快照,发起越晚
|
|
938
|
+
* 快照越新,所以只信最后发起者 ≈ 只信最新快照。操作后的刷新总是最后发起(操作
|
|
939
|
+
* 完成后才发起),天然胜出;响应乱序时旧号被丢弃,最终一致。
|
|
940
|
+
*
|
|
941
|
+
* 纯 TS、零依赖、零 DOM——node 环境可直接单元测试(tests/refresh-guard.spec.ts)。
|
|
942
|
+
* @module @hpyperry/dsh-ref-lib/src/client/refresh-guard
|
|
943
|
+
*/
|
|
944
|
+
var RefreshGuard = class {
|
|
945
|
+
seq = 0;
|
|
946
|
+
/** 发起一次请求,返回本次请求的编号。 */
|
|
947
|
+
begin() {
|
|
948
|
+
this.seq += 1;
|
|
949
|
+
return this.seq;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* 该编号是否仍是最新发起者。
|
|
953
|
+
* @param mine - 发起时 {@link begin} 返回的编号。
|
|
954
|
+
* @returns true 表示应应用该请求的结果;false 表示期间又有新请求发起,结果已过期。
|
|
955
|
+
*/
|
|
956
|
+
isLatest(mine) {
|
|
957
|
+
return mine === this.seq;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* 作废所有 in-flight 请求的结果(会话切换 / 组件卸载时调用):seq 递增后,
|
|
961
|
+
* 任何旧编号的 {@link isLatest} 都返回 false。
|
|
962
|
+
*/
|
|
963
|
+
invalidate() {
|
|
964
|
+
this.seq += 1;
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region src/client/refresh-triggers.ts
|
|
969
|
+
/**
|
|
970
|
+
* 从会话快照节点列表派生刷新触发计数。
|
|
971
|
+
* @param nodes - `session.nodes`(窗口内全部会话节点,结构子集即可)。
|
|
972
|
+
* @returns 两个计数;空列表返回 `{ userMessageCount: 0, refLibCommandDone: 0 }`。
|
|
973
|
+
*/
|
|
974
|
+
function deriveRefreshTriggers(nodes) {
|
|
975
|
+
let userMessageCount = 0;
|
|
976
|
+
let refLibCommandDone = 0;
|
|
977
|
+
for (const node of nodes) if (node.kind === "user") userMessageCount += 1;
|
|
978
|
+
else if (node.kind === "command" && node.name === "ref-lib" && node.outcome !== null) refLibCommandDone += 1;
|
|
979
|
+
return {
|
|
980
|
+
userMessageCount,
|
|
981
|
+
refLibCommandDone
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/client/RefLibBrowser.tsx
|
|
986
|
+
/**
|
|
987
|
+
* 应用内目录浏览器(browse 后端可用时,「选择目录」的落地实现)。
|
|
988
|
+
*
|
|
989
|
+
* 为什么需要它:v6 起 profile 把 directory-picker 从 -auto(本机解析为 native,
|
|
990
|
+
* OS 对话框硬编码英文且每次拉 osascript 进程)切换为 -browse 后,
|
|
991
|
+
* `host.pickDirectory`(native 专用 RPC)不再可用;本组件用 `listDirectory`
|
|
992
|
+
* (Node stdlib 列目录,zh/en 本地化、无 OS 进程)提供等价的目录选择:
|
|
993
|
+
* 面包屑 + 路径编辑 + 子目录列表,点击下行进入,「选择此目录」把当前层级添加为
|
|
994
|
+
* 参考库。简化为单列(与官方 DirectoryBrowser 的 Miller 双列相比),
|
|
995
|
+
* 但覆盖添加参考库的核心路径。
|
|
996
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibBrowser
|
|
997
|
+
*/
|
|
998
|
+
/** 面包屑展示:主目录子树内以本地化 Home 开头;之外显示完整祖先链。 */
|
|
999
|
+
function displayCrumbs(listing, homeLabel) {
|
|
1000
|
+
const homeIndex = listing.crumbs.findIndex((crumb) => crumb.path === listing.home);
|
|
1001
|
+
if (homeIndex === -1) return listing.crumbs;
|
|
1002
|
+
const tail = listing.crumbs.slice(homeIndex + 1);
|
|
1003
|
+
return [{
|
|
1004
|
+
name: homeLabel,
|
|
1005
|
+
path: listing.home,
|
|
1006
|
+
hidden: false
|
|
1007
|
+
}, ...tail];
|
|
1008
|
+
}
|
|
1009
|
+
/** 平台分隔符(由宿主主目录推断,绝不从键入文本推断)。 */
|
|
1010
|
+
function separatorOf(listing) {
|
|
1011
|
+
return listing.home.includes("\\") ? "\\" : "/";
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 渲染应用内目录浏览器。
|
|
1015
|
+
* @param props - 见 {@link RefLibBrowserProps}。
|
|
1016
|
+
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
1017
|
+
*/
|
|
1018
|
+
function RefLibBrowser(props) {
|
|
1019
|
+
const { open, onClose, listDirectory, t, onOpen } = props;
|
|
1020
|
+
const [listing, setListing] = (0, react.useState)(null);
|
|
1021
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
1022
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1023
|
+
const [pathDraft, setPathDraft] = (0, react.useState)(null);
|
|
1024
|
+
const seq = (0, react.useRef)(0);
|
|
1025
|
+
const controller = (0, react.useRef)(null);
|
|
1026
|
+
const composing = (0, react.useRef)(false);
|
|
1027
|
+
/** 新意图胜出:中止在途扫描并使旧结果失效。 */
|
|
1028
|
+
const navigate = (target) => {
|
|
1029
|
+
controller.current?.abort();
|
|
1030
|
+
controller.current = null;
|
|
1031
|
+
const mine = ++seq.current;
|
|
1032
|
+
setLoading(true);
|
|
1033
|
+
setError(null);
|
|
1034
|
+
const ctrl = new AbortController();
|
|
1035
|
+
controller.current = ctrl;
|
|
1036
|
+
listDirectory(target, ctrl.signal).then((next) => {
|
|
1037
|
+
if (mine !== seq.current) return;
|
|
1038
|
+
setListing(next);
|
|
1039
|
+
setLoading(false);
|
|
1040
|
+
}, (cause) => {
|
|
1041
|
+
if (mine !== seq.current) return;
|
|
1042
|
+
setLoading(false);
|
|
1043
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
1044
|
+
});
|
|
1045
|
+
};
|
|
1046
|
+
(0, react.useEffect)(() => {
|
|
1047
|
+
if (open) navigate();
|
|
1048
|
+
else {
|
|
1049
|
+
seq.current += 1;
|
|
1050
|
+
controller.current?.abort();
|
|
1051
|
+
controller.current = null;
|
|
1052
|
+
setPathDraft(null);
|
|
1053
|
+
setError(null);
|
|
1054
|
+
}
|
|
1055
|
+
}, [open]);
|
|
1056
|
+
const openPathEditor = () => {
|
|
1057
|
+
if (listing === null) {
|
|
1058
|
+
setPathDraft("");
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const sep = separatorOf(listing);
|
|
1062
|
+
const base = listing.path;
|
|
1063
|
+
setPathDraft(base.endsWith(sep) ? base : base + sep);
|
|
1064
|
+
};
|
|
1065
|
+
const commitPath = () => {
|
|
1066
|
+
const draft = pathDraft ?? "";
|
|
1067
|
+
if (draft.trim() === "") return;
|
|
1068
|
+
navigate(draft);
|
|
1069
|
+
setPathDraft(null);
|
|
1070
|
+
};
|
|
1071
|
+
const crumbs = listing === null ? [] : displayCrumbs(listing, t("browser.home"));
|
|
1072
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1073
|
+
open,
|
|
1074
|
+
onClose,
|
|
1075
|
+
title: t("browser.title"),
|
|
1076
|
+
closeLabel: t("browser.cancel"),
|
|
1077
|
+
className: "reflib-browser",
|
|
1078
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1079
|
+
className: "reflib-panel",
|
|
1080
|
+
children: [
|
|
1081
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1082
|
+
className: "reflib-error",
|
|
1083
|
+
role: "alert",
|
|
1084
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1085
|
+
size: 14,
|
|
1086
|
+
className: "reflib-errorIcon"
|
|
1087
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
1088
|
+
}),
|
|
1089
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1090
|
+
className: "reflib-browserPath",
|
|
1091
|
+
children: pathDraft === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1092
|
+
className: "reflib-browserCrumbs",
|
|
1093
|
+
role: "navigation",
|
|
1094
|
+
children: crumbs.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1095
|
+
className: "reflib-browserCrumb",
|
|
1096
|
+
children: [index > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {
|
|
1097
|
+
size: 12,
|
|
1098
|
+
className: "reflib-browserCrumbChevron"
|
|
1099
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1100
|
+
type: "button",
|
|
1101
|
+
className: "reflib-browserCrumbBtn",
|
|
1102
|
+
disabled: loading,
|
|
1103
|
+
onClick: () => {
|
|
1104
|
+
navigate(crumb.path);
|
|
1105
|
+
},
|
|
1106
|
+
children: crumb.name
|
|
1107
|
+
})]
|
|
1108
|
+
}, crumb.path))
|
|
1109
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1110
|
+
type: "button",
|
|
1111
|
+
className: "reflib-browserEdit",
|
|
1112
|
+
"aria-label": t("browser.editPath"),
|
|
1113
|
+
title: t("browser.editPath"),
|
|
1114
|
+
disabled: loading,
|
|
1115
|
+
onClick: openPathEditor,
|
|
1116
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
|
|
1117
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1118
|
+
className: "reflib-browserInput",
|
|
1119
|
+
value: pathDraft,
|
|
1120
|
+
"aria-label": t("browser.editPath"),
|
|
1121
|
+
placeholder: t("browser.pathPlaceholder"),
|
|
1122
|
+
autoFocus: true,
|
|
1123
|
+
disabled: loading,
|
|
1124
|
+
onChange: (event) => {
|
|
1125
|
+
setPathDraft(event.currentTarget.value);
|
|
1126
|
+
},
|
|
1127
|
+
onCompositionStart: () => {
|
|
1128
|
+
composing.current = true;
|
|
1129
|
+
},
|
|
1130
|
+
onCompositionEnd: () => {
|
|
1131
|
+
composing.current = false;
|
|
1132
|
+
},
|
|
1133
|
+
onKeyDown: (event) => {
|
|
1134
|
+
if (event.key === "Enter" && !composing.current) {
|
|
1135
|
+
event.preventDefault();
|
|
1136
|
+
commitPath();
|
|
1137
|
+
}
|
|
1138
|
+
if (event.key === "Escape") {
|
|
1139
|
+
event.stopPropagation();
|
|
1140
|
+
setPathDraft(null);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
})
|
|
1144
|
+
}),
|
|
1145
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1146
|
+
className: "reflib-status",
|
|
1147
|
+
role: "status",
|
|
1148
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1149
|
+
size: 20,
|
|
1150
|
+
className: "reflib-spin reflib-statusIcon"
|
|
1151
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1152
|
+
className: "reflib-statusText",
|
|
1153
|
+
children: t("browser.loading")
|
|
1154
|
+
})]
|
|
1155
|
+
}) : listing !== null && listing.entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1156
|
+
className: "reflib-status",
|
|
1157
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1158
|
+
size: 22,
|
|
1159
|
+
className: "reflib-statusIcon"
|
|
1160
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1161
|
+
className: "reflib-statusText",
|
|
1162
|
+
children: t("browser.empty")
|
|
1163
|
+
})]
|
|
1164
|
+
}) : listing !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1165
|
+
className: "reflib-browserList",
|
|
1166
|
+
children: listing.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1167
|
+
type: "button",
|
|
1168
|
+
className: "reflib-browserRow",
|
|
1169
|
+
"aria-label": t("browser.enter.aria", { name: entry.name }),
|
|
1170
|
+
disabled: loading,
|
|
1171
|
+
onClick: () => {
|
|
1172
|
+
navigate(entry.path);
|
|
1173
|
+
},
|
|
1174
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {
|
|
1175
|
+
size: 16,
|
|
1176
|
+
className: "reflib-browserRowIcon"
|
|
1177
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1178
|
+
className: "reflib-browserRowName",
|
|
1179
|
+
children: entry.name
|
|
1180
|
+
})]
|
|
1181
|
+
}, entry.path))
|
|
1182
|
+
}),
|
|
1183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
1184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1185
|
+
className: "reflib-browserFooter",
|
|
1186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1187
|
+
variant: "outline",
|
|
1188
|
+
size: "sm",
|
|
1189
|
+
disabled: loading,
|
|
1190
|
+
onClick: onClose,
|
|
1191
|
+
children: t("browser.cancel")
|
|
1192
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1193
|
+
variant: "primary",
|
|
1194
|
+
size: "sm",
|
|
1195
|
+
disabled: loading || listing === null,
|
|
1196
|
+
"aria-label": t("browser.open.aria"),
|
|
1197
|
+
onClick: () => {
|
|
1198
|
+
if (listing !== null) onOpen(listing.path);
|
|
1199
|
+
},
|
|
1200
|
+
children: t("browser.open")
|
|
1201
|
+
})]
|
|
1202
|
+
})
|
|
1203
|
+
]
|
|
1204
|
+
})
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
//#endregion
|
|
1208
|
+
//#region src/client/RefLibPanel.tsx
|
|
1209
|
+
/**
|
|
1210
|
+
* 参考库管理面板(Modal):列表(基名 + 全路径、移除操作)+ 添加表单。
|
|
1211
|
+
*
|
|
1212
|
+
* 设计(优化点 1/2):全部样式走 “--dsw-*” 设计令牌(随浅/深主题适配),
|
|
1213
|
+
* 列表行以「基名为主行、完整路径为次行」呈现,移除为带危险 hover 的图标按钮,
|
|
1214
|
+
* 添加为主按钮 + 路径输入(支持直接粘贴路径,绕开卡顿的系统选择器)+ 系统选择器
|
|
1215
|
+
* 兜底;空态/加载态/错误态各有独立视觉。文案全部经 `t` 本地化(zh/en)。
|
|
1216
|
+
* 本组件为纯展示:异步操作与错误归集由 RefLibDock 持有。
|
|
1217
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibPanel
|
|
1218
|
+
*/
|
|
1219
|
+
/** 用途说明最大长度(与 node half service.NOTE_MAX_LENGTH 一致)。 */
|
|
1220
|
+
const NOTE_MAX_LENGTH = 120;
|
|
1221
|
+
/**
|
|
1222
|
+
* 渲染参考库管理面板。
|
|
1223
|
+
* @param props - 见 {@link RefLibPanelProps}。
|
|
1224
|
+
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
1225
|
+
*/
|
|
1226
|
+
function RefLibPanel(props) {
|
|
1227
|
+
const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onUpdateNote, onBrowse } = props;
|
|
1228
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
1229
|
+
const [noteDraft, setNoteDraft] = (0, react.useState)("");
|
|
1230
|
+
const [detailId, setDetailId] = (0, react.useState)(null);
|
|
1231
|
+
const [editNote, setEditNote] = (0, react.useState)("");
|
|
1232
|
+
(0, react.useEffect)(() => {
|
|
1233
|
+
if (!open) {
|
|
1234
|
+
setDraft("");
|
|
1235
|
+
setNoteDraft("");
|
|
1236
|
+
setDetailId(null);
|
|
1237
|
+
}
|
|
1238
|
+
}, [open]);
|
|
1239
|
+
const handleSubmit = async () => {
|
|
1240
|
+
const trimmed = draft.trim();
|
|
1241
|
+
if (trimmed === "") return;
|
|
1242
|
+
try {
|
|
1243
|
+
const note = noteDraft.trim();
|
|
1244
|
+
await onAddPath(trimmed, note === "" ? void 0 : note);
|
|
1245
|
+
setDraft("");
|
|
1246
|
+
setNoteDraft("");
|
|
1247
|
+
} catch {}
|
|
1248
|
+
};
|
|
1249
|
+
/** 浏览选中目录 → 填入路径字段(不直接添加,用户可补用途后提交)。 */
|
|
1250
|
+
const handleBrowse = async () => {
|
|
1251
|
+
const path = await onBrowse();
|
|
1252
|
+
if (path !== null) setDraft(path);
|
|
1253
|
+
};
|
|
1254
|
+
/** 展开/收起条目详情;展开时载入当前用途草稿。 */
|
|
1255
|
+
const toggleDetail = (entry) => {
|
|
1256
|
+
if (detailId === entry.id) {
|
|
1257
|
+
setDetailId(null);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
setDetailId(entry.id);
|
|
1261
|
+
setEditNote(entry.note ?? "");
|
|
1262
|
+
};
|
|
1263
|
+
/** 保存详情编辑的用途说明;成功后收起详情。 */
|
|
1264
|
+
const handleSaveNote = async () => {
|
|
1265
|
+
if (detailId === null) return;
|
|
1266
|
+
await onUpdateNote(detailId, editNote);
|
|
1267
|
+
setDetailId(null);
|
|
1268
|
+
};
|
|
1269
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1270
|
+
open,
|
|
1271
|
+
onClose,
|
|
1272
|
+
title: t("panel.title"),
|
|
1273
|
+
closeLabel: t("panel.close"),
|
|
1274
|
+
description: t("panel.description", { count: String(libs.length) }),
|
|
1275
|
+
className: "reflib-modal",
|
|
1276
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1277
|
+
className: "reflib-panel",
|
|
1278
|
+
children: [
|
|
1279
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1280
|
+
className: "reflib-error",
|
|
1281
|
+
role: "alert",
|
|
1282
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1283
|
+
size: 14,
|
|
1284
|
+
className: "reflib-errorIcon"
|
|
1285
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
1286
|
+
}),
|
|
1287
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1288
|
+
className: "reflib-status",
|
|
1289
|
+
role: "status",
|
|
1290
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1291
|
+
size: 20,
|
|
1292
|
+
className: "reflib-spin reflib-statusIcon"
|
|
1293
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1294
|
+
className: "reflib-statusText",
|
|
1295
|
+
children: t("list.loading")
|
|
1296
|
+
})]
|
|
1297
|
+
}) : libs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1298
|
+
className: "reflib-status",
|
|
1299
|
+
children: [
|
|
1300
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1301
|
+
size: 22,
|
|
1302
|
+
className: "reflib-statusIcon"
|
|
1303
|
+
}),
|
|
1304
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1305
|
+
className: "reflib-statusText",
|
|
1306
|
+
children: t("list.empty")
|
|
1307
|
+
}),
|
|
1308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1309
|
+
className: "reflib-statusHint",
|
|
1310
|
+
children: t("list.empty.hint")
|
|
1311
|
+
})
|
|
1312
|
+
]
|
|
1313
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1314
|
+
className: "reflib-list",
|
|
1315
|
+
children: libs.map((entry) => {
|
|
1316
|
+
const name = libBasename(entry.path);
|
|
1317
|
+
const removing = removingId === entry.id;
|
|
1318
|
+
const detailOpen = detailId === entry.id;
|
|
1319
|
+
const unavailable = entry.status !== void 0 && entry.status !== "available";
|
|
1320
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1321
|
+
className: "reflib-listItem",
|
|
1322
|
+
"data-removing": removing || void 0,
|
|
1323
|
+
"data-status": unavailable ? entry.status : void 0,
|
|
1324
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1325
|
+
className: "reflib-row",
|
|
1326
|
+
children: [
|
|
1327
|
+
unavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1328
|
+
size: 16,
|
|
1329
|
+
className: "reflib-rowIcon reflib-rowIconWarn"
|
|
1330
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1331
|
+
size: 16,
|
|
1332
|
+
className: "reflib-rowIcon"
|
|
1333
|
+
}),
|
|
1334
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1335
|
+
className: "reflib-rowBody",
|
|
1336
|
+
children: [
|
|
1337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1338
|
+
className: "reflib-rowName",
|
|
1339
|
+
title: entry.path,
|
|
1340
|
+
children: name
|
|
1341
|
+
}),
|
|
1342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1343
|
+
className: "reflib-rowPath",
|
|
1344
|
+
title: entry.path,
|
|
1345
|
+
children: entry.path
|
|
1346
|
+
}),
|
|
1347
|
+
unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1348
|
+
className: "reflib-rowStatus",
|
|
1349
|
+
role: "status",
|
|
1350
|
+
children: t(entry.status === "missing" ? "status.missing" : "status.notDirectory")
|
|
1351
|
+
}),
|
|
1352
|
+
entry.note !== void 0 && entry.note !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1353
|
+
className: "reflib-rowNote",
|
|
1354
|
+
title: entry.note,
|
|
1355
|
+
children: entry.note.replace(/\s+/g, " ")
|
|
1356
|
+
})
|
|
1357
|
+
]
|
|
1358
|
+
}),
|
|
1359
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1360
|
+
label: t("detail.open"),
|
|
1361
|
+
side: "top",
|
|
1362
|
+
delayMs: 400,
|
|
1363
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1364
|
+
type: "button",
|
|
1365
|
+
className: "reflib-rowAction",
|
|
1366
|
+
"aria-label": t("detail.open.aria", { name }),
|
|
1367
|
+
"aria-expanded": detailOpen || void 0,
|
|
1368
|
+
disabled: busy || removingId !== null || unavailable,
|
|
1369
|
+
onClick: () => {
|
|
1370
|
+
toggleDetail(entry);
|
|
1371
|
+
},
|
|
1372
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 14 })
|
|
1373
|
+
})
|
|
1374
|
+
}),
|
|
1375
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1376
|
+
label: t("list.remove"),
|
|
1377
|
+
side: "top",
|
|
1378
|
+
delayMs: 400,
|
|
1379
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1380
|
+
type: "button",
|
|
1381
|
+
className: "reflib-rowRemove",
|
|
1382
|
+
"aria-label": t("list.remove.aria", { name }),
|
|
1383
|
+
disabled: busy || removingId !== null,
|
|
1384
|
+
onClick: () => {
|
|
1385
|
+
onRemove(entry.id);
|
|
1386
|
+
},
|
|
1387
|
+
children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1388
|
+
size: 14,
|
|
1389
|
+
className: "reflib-spin"
|
|
1390
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
1391
|
+
})
|
|
1392
|
+
})
|
|
1393
|
+
]
|
|
1394
|
+
}), detailOpen && !unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1395
|
+
className: "reflib-detail",
|
|
1396
|
+
children: [
|
|
1397
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1398
|
+
className: "reflib-detailMeta",
|
|
1399
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1400
|
+
className: "reflib-detailKey",
|
|
1401
|
+
children: "ID"
|
|
1402
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1403
|
+
className: "reflib-detailValue",
|
|
1404
|
+
children: entry.id
|
|
1405
|
+
})]
|
|
1406
|
+
}),
|
|
1407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1408
|
+
className: "reflib-detailMeta",
|
|
1409
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1410
|
+
className: "reflib-detailKey",
|
|
1411
|
+
children: t("detail.path")
|
|
1412
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1413
|
+
className: "reflib-detailValue",
|
|
1414
|
+
title: entry.path,
|
|
1415
|
+
children: entry.path
|
|
1416
|
+
})]
|
|
1417
|
+
}),
|
|
1418
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1419
|
+
className: "reflib-detailKey",
|
|
1420
|
+
children: t("add.note.label")
|
|
1421
|
+
}),
|
|
1422
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1423
|
+
className: "reflib-noteWrap",
|
|
1424
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1425
|
+
className: "reflib-noteTextarea",
|
|
1426
|
+
value: editNote,
|
|
1427
|
+
placeholder: t("add.note.placeholder"),
|
|
1428
|
+
"aria-label": t("add.note.label"),
|
|
1429
|
+
disabled: busy,
|
|
1430
|
+
rows: 3,
|
|
1431
|
+
maxLength: NOTE_MAX_LENGTH,
|
|
1432
|
+
onChange: (event) => {
|
|
1433
|
+
setEditNote(event.currentTarget.value);
|
|
1434
|
+
}
|
|
1435
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1436
|
+
className: "reflib-noteCount",
|
|
1437
|
+
children: [
|
|
1438
|
+
editNote.length,
|
|
1439
|
+
"/",
|
|
1440
|
+
NOTE_MAX_LENGTH
|
|
1441
|
+
]
|
|
1442
|
+
})]
|
|
1443
|
+
}),
|
|
1444
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1445
|
+
className: "reflib-detailActions",
|
|
1446
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1447
|
+
variant: "ghost",
|
|
1448
|
+
size: "sm",
|
|
1449
|
+
disabled: busy,
|
|
1450
|
+
onClick: () => {
|
|
1451
|
+
setDetailId(null);
|
|
1452
|
+
},
|
|
1453
|
+
children: t("detail.cancel")
|
|
1454
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1455
|
+
variant: "primary",
|
|
1456
|
+
size: "sm",
|
|
1457
|
+
disabled: busy,
|
|
1458
|
+
onClick: () => {
|
|
1459
|
+
handleSaveNote();
|
|
1460
|
+
},
|
|
1461
|
+
children: t("detail.save")
|
|
1462
|
+
})]
|
|
1463
|
+
})
|
|
1464
|
+
]
|
|
1465
|
+
})]
|
|
1466
|
+
}, entry.id);
|
|
1467
|
+
})
|
|
1468
|
+
}),
|
|
1469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
1470
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1471
|
+
className: "reflib-add",
|
|
1472
|
+
children: [
|
|
1473
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1474
|
+
className: "reflib-addLabel",
|
|
1475
|
+
children: t("add.label")
|
|
1476
|
+
}),
|
|
1477
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1478
|
+
className: "reflib-addRow",
|
|
1479
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1480
|
+
className: "reflib-addInputWrap",
|
|
1481
|
+
value: draft,
|
|
1482
|
+
placeholder: t("add.placeholder"),
|
|
1483
|
+
"aria-label": t("add.label"),
|
|
1484
|
+
disabled: busy || picking,
|
|
1485
|
+
onChange: (event) => {
|
|
1486
|
+
setDraft(event.currentTarget.value);
|
|
1487
|
+
},
|
|
1488
|
+
onKeyDown: (event) => {
|
|
1489
|
+
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
1490
|
+
event.preventDefault();
|
|
1491
|
+
handleSubmit();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1495
|
+
variant: "outline",
|
|
1496
|
+
size: "sm",
|
|
1497
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
1498
|
+
disabled: busy || picking,
|
|
1499
|
+
onClick: () => {
|
|
1500
|
+
handleBrowse();
|
|
1501
|
+
},
|
|
1502
|
+
children: t("add.browse")
|
|
1503
|
+
})]
|
|
1504
|
+
}),
|
|
1505
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1506
|
+
className: "reflib-noteWrap",
|
|
1507
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1508
|
+
className: "reflib-noteTextarea",
|
|
1509
|
+
value: noteDraft,
|
|
1510
|
+
placeholder: t("add.note.placeholder"),
|
|
1511
|
+
"aria-label": t("add.note.label"),
|
|
1512
|
+
disabled: busy || picking,
|
|
1513
|
+
rows: 2,
|
|
1514
|
+
maxLength: NOTE_MAX_LENGTH,
|
|
1515
|
+
onChange: (event) => {
|
|
1516
|
+
setNoteDraft(event.currentTarget.value);
|
|
1517
|
+
}
|
|
1518
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1519
|
+
className: "reflib-noteCount",
|
|
1520
|
+
children: [
|
|
1521
|
+
noteDraft.length,
|
|
1522
|
+
"/",
|
|
1523
|
+
NOTE_MAX_LENGTH
|
|
1524
|
+
]
|
|
1525
|
+
})]
|
|
1526
|
+
}),
|
|
1527
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1528
|
+
variant: "primary",
|
|
1529
|
+
className: "reflib-addSubmit",
|
|
1530
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
|
|
1531
|
+
disabled: busy || picking || draft.trim() === "",
|
|
1532
|
+
onClick: () => {
|
|
1533
|
+
handleSubmit();
|
|
1534
|
+
},
|
|
1535
|
+
children: t("add.submit")
|
|
1536
|
+
}),
|
|
1537
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1538
|
+
className: "reflib-addHint",
|
|
1539
|
+
children: t("add.hint")
|
|
1540
|
+
})
|
|
1541
|
+
]
|
|
1542
|
+
})
|
|
1543
|
+
]
|
|
1544
|
+
})
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
//#endregion
|
|
1282
1548
|
//#region src/client/RefLibDock.tsx
|
|
1283
1549
|
/**
|
|
1284
1550
|
* 参考库入口(conversation.input.dock,输入卡正上方)+ 面板状态持有者。
|
|
@@ -1297,12 +1563,16 @@ window.__ModuleLoader__.load({
|
|
|
1297
1563
|
* 目录选择走系统原生选择器,另提供路径输入直加(绕开卡顿的原生对话框)。
|
|
1298
1564
|
* @module @hpyperry/dsh-ref-lib/src/client/RefLibDock
|
|
1299
1565
|
*/
|
|
1566
|
+
/** 挂载预载失败重试:间隔与上限(重启后会话 live 通常 <1s,重试 1–2 次即成功)。 */
|
|
1567
|
+
const MOUNT_RETRY_DELAY_MS = 800;
|
|
1568
|
+
const MOUNT_RETRY_MAX = 5;
|
|
1300
1569
|
/** wire 错误码 → 本地化文案键(未知码回退原始消息)。 */
|
|
1301
1570
|
const ERROR_KEYS = {
|
|
1302
1571
|
"ref-lib/missing": "error.missing",
|
|
1303
1572
|
"ref-lib/not-directory": "error.notDirectory",
|
|
1304
1573
|
"ref-lib/unsafe": "error.unsafe",
|
|
1305
|
-
"ref-lib/unknown-id": "error.unknownId"
|
|
1574
|
+
"ref-lib/unknown-id": "error.unknownId",
|
|
1575
|
+
"ref-lib/unavailable": "error.unavailable"
|
|
1306
1576
|
};
|
|
1307
1577
|
/** 把未知错误规整为可展示文案。 */
|
|
1308
1578
|
function messageOf(cause) {
|
|
@@ -1327,7 +1597,7 @@ window.__ModuleLoader__.load({
|
|
|
1327
1597
|
* @returns 胶囊入口(+ 打开时的管理面板)。
|
|
1328
1598
|
*/
|
|
1329
1599
|
function RefLibDock(props) {
|
|
1330
|
-
const { sessionId, load, add, remove, setNote, pickDirectory, listDirectory, t } = props;
|
|
1600
|
+
const { sessionId, session, load, add, remove, setNote, pickDirectory, listDirectory, t } = props;
|
|
1331
1601
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1332
1602
|
const [libs, setLibs] = (0, react.useState)([]);
|
|
1333
1603
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -1335,7 +1605,7 @@ window.__ModuleLoader__.load({
|
|
|
1335
1605
|
const [picking, setPicking] = (0, react.useState)(false);
|
|
1336
1606
|
const [removingId, setRemovingId] = (0, react.useState)(null);
|
|
1337
1607
|
const [error, setError] = (0, react.useState)(null);
|
|
1338
|
-
const
|
|
1608
|
+
const guard = (0, react.useRef)(new RefreshGuard());
|
|
1339
1609
|
const [pickerMode, setPickerMode] = (0, react.useState)(null);
|
|
1340
1610
|
const [browserOpen, setBrowserOpen] = (0, react.useState)(false);
|
|
1341
1611
|
const pendingPick = (0, react.useRef)(null);
|
|
@@ -1351,23 +1621,57 @@ window.__ModuleLoader__.load({
|
|
|
1351
1621
|
setPickerMode(mode);
|
|
1352
1622
|
return mode;
|
|
1353
1623
|
};
|
|
1354
|
-
|
|
1355
|
-
|
|
1624
|
+
/**
|
|
1625
|
+
* 拉取会话参考库列表并更新 UI。
|
|
1626
|
+
* @param silent - 静默模式(轮询用):失败不写入错误槽;成功也**不碰 error/loading**
|
|
1627
|
+
* ——错误与加载态只由用户操作/打开面板管理,避免后台轮询吞掉操作错误提示或
|
|
1628
|
+
* 提前结束面板加载态(轮询仅更新 libs 数据)。
|
|
1629
|
+
*/
|
|
1630
|
+
const refresh = async (silent = false) => {
|
|
1631
|
+
const mine = guard.current.begin();
|
|
1356
1632
|
try {
|
|
1357
1633
|
const next = await load(sessionId);
|
|
1358
|
-
if (
|
|
1634
|
+
if (!guard.current.isLatest(mine)) return;
|
|
1359
1635
|
setLibs(next);
|
|
1360
|
-
setError(null);
|
|
1636
|
+
if (!silent) setError(null);
|
|
1361
1637
|
} catch (cause) {
|
|
1362
|
-
if (
|
|
1363
|
-
setError(formatError(cause, t));
|
|
1638
|
+
if (!guard.current.isLatest(mine)) return;
|
|
1639
|
+
if (!silent) setError(formatError(cause, t));
|
|
1364
1640
|
} finally {
|
|
1365
|
-
if (mine
|
|
1641
|
+
if (guard.current.isLatest(mine) && !silent) setLoading(false);
|
|
1366
1642
|
}
|
|
1367
1643
|
};
|
|
1368
1644
|
(0, react.useEffect)(() => {
|
|
1369
1645
|
ensureRefLibStyles();
|
|
1370
|
-
|
|
1646
|
+
let stopped = false;
|
|
1647
|
+
let retries = 0;
|
|
1648
|
+
let timer;
|
|
1649
|
+
const attempt = async () => {
|
|
1650
|
+
const mine = guard.current.begin();
|
|
1651
|
+
try {
|
|
1652
|
+
const next = await load(sessionId);
|
|
1653
|
+
if (stopped || !guard.current.isLatest(mine)) return;
|
|
1654
|
+
setLibs(next);
|
|
1655
|
+
setError(null);
|
|
1656
|
+
} catch (cause) {
|
|
1657
|
+
if (stopped || !guard.current.isLatest(mine)) return;
|
|
1658
|
+
if (retries < MOUNT_RETRY_MAX) {
|
|
1659
|
+
retries += 1;
|
|
1660
|
+
timer = window.setTimeout(() => {
|
|
1661
|
+
attempt();
|
|
1662
|
+
}, MOUNT_RETRY_DELAY_MS);
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
setError(formatError(cause, t));
|
|
1666
|
+
} finally {
|
|
1667
|
+
if (!stopped && guard.current.isLatest(mine)) setLoading(false);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
attempt();
|
|
1671
|
+
return () => {
|
|
1672
|
+
stopped = true;
|
|
1673
|
+
if (timer !== void 0) window.clearTimeout(timer);
|
|
1674
|
+
};
|
|
1371
1675
|
}, [sessionId]);
|
|
1372
1676
|
(0, react.useEffect)(() => {
|
|
1373
1677
|
if (open) {
|
|
@@ -1375,6 +1679,13 @@ window.__ModuleLoader__.load({
|
|
|
1375
1679
|
refresh();
|
|
1376
1680
|
}
|
|
1377
1681
|
}, [open]);
|
|
1682
|
+
const { userMessageCount, refLibCommandDone } = deriveRefreshTriggers(session.nodes);
|
|
1683
|
+
(0, react.useEffect)(() => {
|
|
1684
|
+
refresh(true);
|
|
1685
|
+
}, [userMessageCount]);
|
|
1686
|
+
(0, react.useEffect)(() => {
|
|
1687
|
+
refresh(true);
|
|
1688
|
+
}, [refLibCommandDone]);
|
|
1378
1689
|
const handleRemove = async (id) => {
|
|
1379
1690
|
setRemovingId(id);
|
|
1380
1691
|
setBusy(true);
|
|
@@ -1456,6 +1767,14 @@ window.__ModuleLoader__.load({
|
|
|
1456
1767
|
pendingPick.current = null;
|
|
1457
1768
|
resolve?.(null);
|
|
1458
1769
|
};
|
|
1770
|
+
/**
|
|
1771
|
+
* 胶囊计数(v9):徽标显示**可用**数量(失效不计入),红色角标显示失效数量
|
|
1772
|
+
* (总数 − 可用数),仅存在失效条目时显示。注意:UI 数据只在打开面板/操作后
|
|
1773
|
+
* 刷新,外部变更(如删除目录)不会自动反向同步到界面(与命令修改后不刷新同属
|
|
1774
|
+
* 待解决的 UI 数据同步问题,见设计文档"遗留备注")。
|
|
1775
|
+
*/
|
|
1776
|
+
const availableCount = libs.filter((entry) => entry.status === "available").length;
|
|
1777
|
+
const unavailableCount = libs.length - availableCount;
|
|
1459
1778
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1460
1779
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1461
1780
|
className: "reflib-dock",
|
|
@@ -1465,7 +1784,7 @@ window.__ModuleLoader__.load({
|
|
|
1465
1784
|
"data-active": open || void 0,
|
|
1466
1785
|
"aria-haspopup": "dialog",
|
|
1467
1786
|
"aria-expanded": open,
|
|
1468
|
-
"aria-label":
|
|
1787
|
+
"aria-label": unavailableCount > 0 ? t("dock.unavailable", { count: String(unavailableCount) }) : availableCount > 0 ? t("dock.count.aria", { count: String(availableCount) }) : t("dock.aria"),
|
|
1469
1788
|
title: t("dock.aria"),
|
|
1470
1789
|
onClick: () => {
|
|
1471
1790
|
setOpen((value) => !value);
|
|
@@ -1479,9 +1798,14 @@ window.__ModuleLoader__.load({
|
|
|
1479
1798
|
className: "reflib-chipLabel",
|
|
1480
1799
|
children: t("dock.label")
|
|
1481
1800
|
}),
|
|
1482
|
-
|
|
1801
|
+
availableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1483
1802
|
className: "reflib-chipBadge",
|
|
1484
|
-
children:
|
|
1803
|
+
children: availableCount
|
|
1804
|
+
}),
|
|
1805
|
+
unavailableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1806
|
+
className: "reflib-chipWarn",
|
|
1807
|
+
title: t("dock.unavailable", { count: String(unavailableCount) }),
|
|
1808
|
+
children: unavailableCount
|
|
1485
1809
|
})
|
|
1486
1810
|
]
|
|
1487
1811
|
})
|
|
@@ -1530,6 +1854,9 @@ window.__ModuleLoader__.load({
|
|
|
1530
1854
|
"dock.label": "参考库",
|
|
1531
1855
|
"dock.aria": "管理只读参考库",
|
|
1532
1856
|
"dock.count.aria": "参考库,共 {count} 个",
|
|
1857
|
+
"dock.unavailable": "参考库,{count} 个失效",
|
|
1858
|
+
"status.missing": "目录已删除或不可访问",
|
|
1859
|
+
"status.notDirectory": "路径不再是目录",
|
|
1533
1860
|
"panel.title": "参考库",
|
|
1534
1861
|
"panel.close": "关闭",
|
|
1535
1862
|
"panel.description": "共 {count} 个只读参考库:供 agent 读取参考,禁止修改其中文件",
|
|
@@ -1563,13 +1890,21 @@ window.__ModuleLoader__.load({
|
|
|
1563
1890
|
"error.missing": "目录不存在:{path}",
|
|
1564
1891
|
"error.notDirectory": "不是目录:{path}",
|
|
1565
1892
|
"error.unsafe": "路径包含控制字符,已拒绝:{path}",
|
|
1566
|
-
"error.unknownId": "未找到参考库条目:{id}"
|
|
1893
|
+
"error.unknownId": "未找到参考库条目:{id}",
|
|
1894
|
+
"error.unavailable": "参考库目录不可用(仅允许移除)",
|
|
1895
|
+
"command.running": "执行中…",
|
|
1896
|
+
"command.done": "完成",
|
|
1897
|
+
"command.copy": "复制",
|
|
1898
|
+
"command.copied": "已复制"
|
|
1567
1899
|
};
|
|
1568
1900
|
/** 英文词典,与 zh 键集逐键对齐(编译期校验)。 */
|
|
1569
1901
|
const en = {
|
|
1570
1902
|
"dock.label": "Reference Library",
|
|
1571
1903
|
"dock.aria": "Manage read-only reference libraries",
|
|
1572
1904
|
"dock.count.aria": "Reference library, {count} total",
|
|
1905
|
+
"dock.unavailable": "Reference library, {count} unavailable",
|
|
1906
|
+
"status.missing": "Directory deleted or unavailable",
|
|
1907
|
+
"status.notDirectory": "Path is no longer a directory",
|
|
1573
1908
|
"panel.title": "Reference Library",
|
|
1574
1909
|
"panel.close": "Close",
|
|
1575
1910
|
"panel.description": "{count} read-only reference libraries: the agent may read them for reference, but must not modify any files",
|
|
@@ -1603,7 +1938,12 @@ window.__ModuleLoader__.load({
|
|
|
1603
1938
|
"error.missing": "Directory does not exist: {path}",
|
|
1604
1939
|
"error.notDirectory": "Not a directory: {path}",
|
|
1605
1940
|
"error.unsafe": "Path contains control characters and was rejected: {path}",
|
|
1606
|
-
"error.unknownId": "Reference library entry not found: {id}"
|
|
1941
|
+
"error.unknownId": "Reference library entry not found: {id}",
|
|
1942
|
+
"error.unavailable": "Reference library directory unavailable (remove only)",
|
|
1943
|
+
"command.running": "Running…",
|
|
1944
|
+
"command.done": "Done",
|
|
1945
|
+
"command.copy": "Copy",
|
|
1946
|
+
"command.copied": "Copied"
|
|
1607
1947
|
};
|
|
1608
1948
|
//#endregion
|
|
1609
1949
|
//#region src/client/index.ts
|
|
@@ -1678,6 +2018,11 @@ window.__ModuleLoader__.load({
|
|
|
1678
2018
|
locale: NS,
|
|
1679
2019
|
inject: injected
|
|
1680
2020
|
}, RefLibDock));
|
|
2021
|
+
ctx.slots.inject("conversation.chat.commandview", () => ctx.slots.register({
|
|
2022
|
+
name: "conversation.chat.commandview",
|
|
2023
|
+
key: "ref-lib",
|
|
2024
|
+
locale: NS
|
|
2025
|
+
}, RefLibCommandCard));
|
|
1681
2026
|
}
|
|
1682
2027
|
//#endregion
|
|
1683
2028
|
exports.apply = apply;
|