@hpyperry/dsh-ref-lib 0.7.0 → 0.9.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 +13 -14
- 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 +4 -2
- package/lib/client/RefLibDock.d.ts.map +1 -1
- package/lib/client/RefLibDock.js +121 -37
- package/lib/client/RefLibDock.js.map +1 -1
- package/lib/client/RefLibPanel.d.ts +6 -4
- package/lib/client/RefLibPanel.d.ts.map +1 -1
- package/lib/client/RefLibPanel.js +69 -20
- package/lib/client/RefLibPanel.js.map +1 -1
- package/lib/client/index.d.ts.map +1 -1
- package/lib/client/index.js +13 -2
- package/lib/client/index.js.map +1 -1
- package/lib/client/locales.d.ts +30 -2
- package/lib/client/locales.d.ts.map +1 -1
- package/lib/client/locales.js +42 -8
- 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/styles.d.ts.map +1 -1
- package/lib/client/styles.js +269 -23
- package/lib/client/styles.js.map +1 -1
- package/lib/client.js +1141 -500
- package/lib/client.js.map +1 -1
- package/lib/commands.d.ts +2 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +15 -3
- package/lib/commands.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +9 -7
- package/lib/index.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 +10 -4
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +113 -16
- package/lib/render.js.map +1 -1
- package/lib/routes.d.ts.map +1 -1
- package/lib/routes.js +46 -2
- package/lib/routes.js.map +1 -1
- package/lib/service.d.ts +52 -3
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +172 -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 +17 -9
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,412 +88,6 @@ window.__ModuleLoader__.load({
|
|
|
79
88
|
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
80
89
|
}
|
|
81
90
|
//#endregion
|
|
82
|
-
//#region src/client/RefLibBrowser.tsx
|
|
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
|
|
306
|
-
/**
|
|
307
|
-
* 参考库管理面板(Modal):列表(基名 + 全路径、移除操作)+ 添加表单。
|
|
308
|
-
*
|
|
309
|
-
* 设计(优化点 1/2):全部样式走 “--dsw-*” 设计令牌(随浅/深主题适配),
|
|
310
|
-
* 列表行以「基名为主行、完整路径为次行」呈现,移除为带危险 hover 的图标按钮,
|
|
311
|
-
* 添加为主按钮 + 路径输入(支持直接粘贴路径,绕开卡顿的系统选择器)+ 系统选择器
|
|
312
|
-
* 兜底;空态/加载态/错误态各有独立视觉。文案全部经 `t` 本地化(zh/en)。
|
|
313
|
-
* 本组件为纯展示:异步操作与错误归集由 RefLibDock 持有。
|
|
314
|
-
* @module @hpyperry/dsh-ref-lib/src/client/RefLibPanel
|
|
315
|
-
*/
|
|
316
|
-
/**
|
|
317
|
-
* 渲染参考库管理面板。
|
|
318
|
-
* @param props - 见 {@link RefLibPanelProps}。
|
|
319
|
-
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
320
|
-
*/
|
|
321
|
-
function RefLibPanel(props) {
|
|
322
|
-
const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onBrowse } = props;
|
|
323
|
-
const [draft, setDraft] = (0, react.useState)("");
|
|
324
|
-
(0, react.useEffect)(() => {
|
|
325
|
-
if (!open) setDraft("");
|
|
326
|
-
}, [open]);
|
|
327
|
-
const handleSubmit = async () => {
|
|
328
|
-
const trimmed = draft.trim();
|
|
329
|
-
if (trimmed === "") return;
|
|
330
|
-
try {
|
|
331
|
-
await onAddPath(trimmed);
|
|
332
|
-
setDraft("");
|
|
333
|
-
} catch {}
|
|
334
|
-
};
|
|
335
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
336
|
-
open,
|
|
337
|
-
onClose,
|
|
338
|
-
title: t("panel.title"),
|
|
339
|
-
closeLabel: t("panel.close"),
|
|
340
|
-
description: t("panel.description", { count: String(libs.length) }),
|
|
341
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
342
|
-
className: "reflib-panel",
|
|
343
|
-
children: [
|
|
344
|
-
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
345
|
-
className: "reflib-error",
|
|
346
|
-
role: "alert",
|
|
347
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
348
|
-
size: 14,
|
|
349
|
-
className: "reflib-errorIcon"
|
|
350
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
351
|
-
}),
|
|
352
|
-
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
353
|
-
className: "reflib-status",
|
|
354
|
-
role: "status",
|
|
355
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
356
|
-
size: 20,
|
|
357
|
-
className: "reflib-spin reflib-statusIcon"
|
|
358
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
359
|
-
className: "reflib-statusText",
|
|
360
|
-
children: t("list.loading")
|
|
361
|
-
})]
|
|
362
|
-
}) : libs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
363
|
-
className: "reflib-status",
|
|
364
|
-
children: [
|
|
365
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
366
|
-
size: 22,
|
|
367
|
-
className: "reflib-statusIcon"
|
|
368
|
-
}),
|
|
369
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
370
|
-
className: "reflib-statusText",
|
|
371
|
-
children: t("list.empty")
|
|
372
|
-
}),
|
|
373
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
374
|
-
className: "reflib-statusHint",
|
|
375
|
-
children: t("list.empty.hint")
|
|
376
|
-
})
|
|
377
|
-
]
|
|
378
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
379
|
-
className: "reflib-list",
|
|
380
|
-
children: libs.map((entry) => {
|
|
381
|
-
const name = libBasename(entry.path);
|
|
382
|
-
const removing = removingId === entry.id;
|
|
383
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
384
|
-
className: "reflib-row",
|
|
385
|
-
"data-removing": removing || void 0,
|
|
386
|
-
children: [
|
|
387
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
388
|
-
size: 16,
|
|
389
|
-
className: "reflib-rowIcon"
|
|
390
|
-
}),
|
|
391
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
392
|
-
className: "reflib-rowBody",
|
|
393
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
394
|
-
className: "reflib-rowName",
|
|
395
|
-
title: entry.path,
|
|
396
|
-
children: name
|
|
397
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
398
|
-
className: "reflib-rowPath",
|
|
399
|
-
title: entry.path,
|
|
400
|
-
children: entry.path
|
|
401
|
-
})]
|
|
402
|
-
}),
|
|
403
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
404
|
-
label: t("list.remove"),
|
|
405
|
-
side: "top",
|
|
406
|
-
delayMs: 400,
|
|
407
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
408
|
-
type: "button",
|
|
409
|
-
className: "reflib-rowRemove",
|
|
410
|
-
"aria-label": t("list.remove.aria", { name }),
|
|
411
|
-
disabled: busy || removingId !== null,
|
|
412
|
-
onClick: () => {
|
|
413
|
-
onRemove(entry.id);
|
|
414
|
-
},
|
|
415
|
-
children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
416
|
-
size: 14,
|
|
417
|
-
className: "reflib-spin"
|
|
418
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
419
|
-
})
|
|
420
|
-
})
|
|
421
|
-
]
|
|
422
|
-
}, entry.id);
|
|
423
|
-
})
|
|
424
|
-
}),
|
|
425
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
426
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
427
|
-
className: "reflib-add",
|
|
428
|
-
children: [
|
|
429
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
430
|
-
className: "reflib-addLabel",
|
|
431
|
-
children: t("add.label")
|
|
432
|
-
}),
|
|
433
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
434
|
-
variant: "primary",
|
|
435
|
-
className: "reflib-addBrowseMain",
|
|
436
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 16 }),
|
|
437
|
-
disabled: busy || picking,
|
|
438
|
-
onClick: () => {
|
|
439
|
-
onBrowse();
|
|
440
|
-
},
|
|
441
|
-
children: t("add.browse")
|
|
442
|
-
}),
|
|
443
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
444
|
-
className: "reflib-addManual",
|
|
445
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
446
|
-
className: "reflib-addManualLabel",
|
|
447
|
-
children: t("add.manual")
|
|
448
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
449
|
-
className: "reflib-addRow",
|
|
450
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
451
|
-
className: "reflib-addInputWrap",
|
|
452
|
-
value: draft,
|
|
453
|
-
placeholder: t("add.placeholder"),
|
|
454
|
-
"aria-label": t("add.label"),
|
|
455
|
-
disabled: busy || picking,
|
|
456
|
-
onChange: (event) => {
|
|
457
|
-
setDraft(event.currentTarget.value);
|
|
458
|
-
},
|
|
459
|
-
onKeyDown: (event) => {
|
|
460
|
-
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
461
|
-
event.preventDefault();
|
|
462
|
-
handleSubmit();
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
466
|
-
variant: "primary",
|
|
467
|
-
size: "sm",
|
|
468
|
-
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
|
|
469
|
-
disabled: busy || picking || draft.trim() === "",
|
|
470
|
-
onClick: () => {
|
|
471
|
-
handleSubmit();
|
|
472
|
-
},
|
|
473
|
-
children: t("add.submit")
|
|
474
|
-
})]
|
|
475
|
-
})]
|
|
476
|
-
}),
|
|
477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
478
|
-
className: "reflib-addHint",
|
|
479
|
-
children: t("add.hint")
|
|
480
|
-
})
|
|
481
|
-
]
|
|
482
|
-
})
|
|
483
|
-
]
|
|
484
|
-
})
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
//#endregion
|
|
488
91
|
//#region src/client/styles.ts
|
|
489
92
|
/**
|
|
490
93
|
* ref-lib client 样式注入:在文档头挂一张本插件专属的 <style data-plugin> 标签。
|
|
@@ -580,6 +183,22 @@ window.__ModuleLoader__.load({
|
|
|
580
183
|
font-weight: 500;
|
|
581
184
|
}
|
|
582
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
|
+
|
|
583
202
|
/* ── 应用内目录浏览器(browse 后端)── */
|
|
584
203
|
.reflib-browser {
|
|
585
204
|
width: min(460px, 100%);
|
|
@@ -763,6 +382,12 @@ window.__ModuleLoader__.load({
|
|
|
763
382
|
padding: 0 8px;
|
|
764
383
|
}
|
|
765
384
|
|
|
385
|
+
.reflib-listItem {
|
|
386
|
+
display: flex;
|
|
387
|
+
flex-direction: column;
|
|
388
|
+
min-width: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
766
391
|
.reflib-row {
|
|
767
392
|
display: flex;
|
|
768
393
|
align-items: center;
|
|
@@ -778,7 +403,7 @@ window.__ModuleLoader__.load({
|
|
|
778
403
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
779
404
|
}
|
|
780
405
|
|
|
781
|
-
.reflib-
|
|
406
|
+
.reflib-listItem[data-removing='true'] {
|
|
782
407
|
opacity: 0.55;
|
|
783
408
|
pointer-events: none;
|
|
784
409
|
}
|
|
@@ -789,6 +414,11 @@ window.__ModuleLoader__.load({
|
|
|
789
414
|
color: var(--dsw-alias-label-tertiary);
|
|
790
415
|
}
|
|
791
416
|
|
|
417
|
+
/* 失效条目行图标(v9):警示色。 */
|
|
418
|
+
.reflib-rowIconWarn {
|
|
419
|
+
color: var(--dsw-alias-state-error-primary);
|
|
420
|
+
}
|
|
421
|
+
|
|
792
422
|
.reflib-rowBody {
|
|
793
423
|
display: flex;
|
|
794
424
|
flex: 1;
|
|
@@ -817,30 +447,164 @@ window.__ModuleLoader__.load({
|
|
|
817
447
|
white-space: nowrap;
|
|
818
448
|
}
|
|
819
449
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
450
|
+
/* 列表行用途说明(Description,v8):单行截断,与路径同属次行层级 */
|
|
451
|
+
.reflib-rowNote {
|
|
452
|
+
overflow: hidden;
|
|
453
|
+
color: var(--dsw-alias-label-secondary);
|
|
454
|
+
font-size: 12px;
|
|
455
|
+
line-height: 16px;
|
|
456
|
+
text-overflow: ellipsis;
|
|
457
|
+
white-space: nowrap;
|
|
458
|
+
}
|
|
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
|
+
|
|
470
|
+
.reflib-rowRemove {
|
|
471
|
+
display: inline-flex;
|
|
472
|
+
align-items: center;
|
|
473
|
+
justify-content: center;
|
|
474
|
+
flex: none;
|
|
475
|
+
width: 28px;
|
|
476
|
+
height: 28px;
|
|
477
|
+
padding: 0;
|
|
478
|
+
border: none;
|
|
479
|
+
border-radius: 8px;
|
|
480
|
+
background: transparent;
|
|
481
|
+
color: var(--dsw-alias-label-tertiary);
|
|
482
|
+
cursor: pointer;
|
|
483
|
+
transition: background-color 120ms ease, color 120ms ease;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.reflib-rowRemove:hover:not(:disabled) {
|
|
487
|
+
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
488
|
+
color: var(--dsw-alias-state-error-primary);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.reflib-rowRemove:disabled {
|
|
492
|
+
opacity: 0.4;
|
|
493
|
+
cursor: default;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* 详情/编辑入口按钮(与移除同尺寸,非危险 hover) */
|
|
497
|
+
.reflib-rowAction {
|
|
498
|
+
display: inline-flex;
|
|
499
|
+
align-items: center;
|
|
500
|
+
justify-content: center;
|
|
501
|
+
flex: none;
|
|
502
|
+
width: 28px;
|
|
503
|
+
height: 28px;
|
|
504
|
+
padding: 0;
|
|
505
|
+
border: none;
|
|
506
|
+
border-radius: 8px;
|
|
507
|
+
background: transparent;
|
|
508
|
+
color: var(--dsw-alias-label-tertiary);
|
|
509
|
+
cursor: pointer;
|
|
510
|
+
transition: background-color 120ms ease, color 120ms ease;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.reflib-rowAction:hover:not(:disabled) {
|
|
514
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
515
|
+
color: var(--dsw-alias-label-primary);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.reflib-rowAction:disabled {
|
|
519
|
+
opacity: 0.4;
|
|
520
|
+
cursor: default;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/* 条目详情展开区(ID / 路径 / 用途编辑) */
|
|
524
|
+
.reflib-detail {
|
|
525
|
+
display: flex;
|
|
526
|
+
flex-direction: column;
|
|
527
|
+
gap: 5px;
|
|
528
|
+
margin: 2px 0 6px;
|
|
529
|
+
padding: 8px 10px 10px;
|
|
530
|
+
border-radius: 10px;
|
|
531
|
+
background: var(--dsw-alias-surface-inset, var(--dsw-alias-surface-raised));
|
|
532
|
+
min-width: 0;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.reflib-detailMeta {
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: baseline;
|
|
538
|
+
gap: 8px;
|
|
539
|
+
min-width: 0;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.reflib-detailKey {
|
|
543
|
+
flex: none;
|
|
544
|
+
font-size: 11px;
|
|
545
|
+
line-height: 16px;
|
|
546
|
+
color: var(--dsw-alias-label-caption);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.reflib-detailValue {
|
|
550
|
+
overflow: hidden;
|
|
551
|
+
min-width: 0;
|
|
552
|
+
font-size: 12px;
|
|
553
|
+
line-height: 16px;
|
|
554
|
+
color: var(--dsw-alias-label-secondary);
|
|
555
|
+
text-overflow: ellipsis;
|
|
556
|
+
white-space: nowrap;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.reflib-detailActions {
|
|
560
|
+
display: flex;
|
|
561
|
+
justify-content: flex-end;
|
|
562
|
+
gap: 6px;
|
|
563
|
+
margin-top: 2px;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/* 用途说明多行输入(添加表单 + 详情编辑共用) */
|
|
567
|
+
.reflib-noteWrap {
|
|
568
|
+
position: relative;
|
|
569
|
+
display: flex;
|
|
570
|
+
min-width: 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.reflib-noteTextarea {
|
|
574
|
+
width: 100%;
|
|
575
|
+
box-sizing: border-box;
|
|
576
|
+
padding: 6px 8px;
|
|
577
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
578
|
+
border-radius: 8px;
|
|
579
|
+
background: var(--dsw-alias-surface-field);
|
|
580
|
+
color: var(--dsw-alias-label-primary);
|
|
581
|
+
font: inherit;
|
|
582
|
+
font-size: 13px;
|
|
583
|
+
line-height: 18px;
|
|
584
|
+
resize: vertical;
|
|
585
|
+
min-height: 40px;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.reflib-noteTextarea:focus {
|
|
589
|
+
outline: 2px solid var(--dsw-alias-focus-ring, var(--dsw-alias-state-info-primary));
|
|
590
|
+
outline-offset: -1px;
|
|
834
591
|
}
|
|
835
592
|
|
|
836
|
-
.reflib-
|
|
837
|
-
|
|
838
|
-
color: var(--dsw-alias-state-error-primary);
|
|
593
|
+
.reflib-noteTextarea:disabled {
|
|
594
|
+
opacity: 0.5;
|
|
839
595
|
}
|
|
840
596
|
|
|
841
|
-
.reflib-
|
|
842
|
-
|
|
843
|
-
|
|
597
|
+
.reflib-noteCount {
|
|
598
|
+
position: absolute;
|
|
599
|
+
right: 8px;
|
|
600
|
+
bottom: 6px;
|
|
601
|
+
padding: 0 4px;
|
|
602
|
+
border-radius: 4px;
|
|
603
|
+
background: var(--dsw-alias-surface-field);
|
|
604
|
+
font-size: 10px;
|
|
605
|
+
line-height: 14px;
|
|
606
|
+
color: var(--dsw-alias-label-caption);
|
|
607
|
+
pointer-events: none;
|
|
844
608
|
}
|
|
845
609
|
|
|
846
610
|
/* 空态 / 加载态 / 错误态 */
|
|
@@ -908,9 +672,28 @@ window.__ModuleLoader__.load({
|
|
|
908
672
|
background: var(--dsw-alias-border-l2);
|
|
909
673
|
}
|
|
910
674
|
|
|
675
|
+
/* ── 管理面板 Modal(v8 优化)──
|
|
676
|
+
宽度放宽到约 1/3 屏(上限 560px);进入动画为柔和缓出(easeOutQuint 风格:
|
|
677
|
+
280ms 浮起 + 淡入,位移 12px→0、scale 0.96→1),无回弹——更从容、高级感。 */
|
|
678
|
+
.reflib-modal {
|
|
679
|
+
width: min(33vw, 560px);
|
|
680
|
+
animation: reflib-pop 280ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
@keyframes reflib-pop {
|
|
684
|
+
0% {
|
|
685
|
+
opacity: 0;
|
|
686
|
+
transform: scale(0.96) translateY(12px);
|
|
687
|
+
}
|
|
688
|
+
100% {
|
|
689
|
+
opacity: 1;
|
|
690
|
+
transform: scale(1) translateY(0);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
911
694
|
/* ── 添加表单 ──
|
|
912
|
-
|
|
913
|
-
|
|
695
|
+
统一表单(v8 UI 重构):路径(可输入 / 浏览填充)+ 用途(可选)同一容器,
|
|
696
|
+
同一「添加」按钮提交——用途与路径强关联,浏览不再直接添加。 */
|
|
914
697
|
.reflib-add {
|
|
915
698
|
display: flex;
|
|
916
699
|
flex-direction: column;
|
|
@@ -925,25 +708,6 @@ window.__ModuleLoader__.load({
|
|
|
925
708
|
color: var(--dsw-alias-label-secondary);
|
|
926
709
|
}
|
|
927
710
|
|
|
928
|
-
/* 主方式:选择目录 —— 全宽主按钮(优先级最高) */
|
|
929
|
-
.reflib-addBrowseMain {
|
|
930
|
-
width: 100%;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
/* 备选:手动输入路径组 */
|
|
934
|
-
.reflib-addManual {
|
|
935
|
-
display: flex;
|
|
936
|
-
flex-direction: column;
|
|
937
|
-
gap: 6px;
|
|
938
|
-
min-width: 0;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
.reflib-addManualLabel {
|
|
942
|
-
font-size: 12px;
|
|
943
|
-
line-height: 16px;
|
|
944
|
-
color: var(--dsw-alias-label-caption);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
711
|
.reflib-addRow {
|
|
948
712
|
display: flex;
|
|
949
713
|
align-items: center;
|
|
@@ -965,13 +729,98 @@ window.__ModuleLoader__.load({
|
|
|
965
729
|
line-height: 18px;
|
|
966
730
|
}
|
|
967
731
|
|
|
732
|
+
/* 全宽「添加」主按钮 */
|
|
733
|
+
.reflib-addSubmit {
|
|
734
|
+
width: 100%;
|
|
735
|
+
}
|
|
736
|
+
|
|
968
737
|
.reflib-addHint {
|
|
969
738
|
font-size: 11px;
|
|
970
739
|
line-height: 16px;
|
|
971
740
|
color: var(--dsw-alias-label-caption);
|
|
972
741
|
}
|
|
973
742
|
|
|
974
|
-
/*
|
|
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
|
+
|
|
823
|
+
/* 窄视口:路径行换行,输入框占满一行 */
|
|
975
824
|
@media (max-width: 440px) {
|
|
976
825
|
.reflib-addRow {
|
|
977
826
|
flex-wrap: wrap;
|
|
@@ -997,6 +846,688 @@ window.__ModuleLoader__.load({
|
|
|
997
846
|
document.head.appendChild(tag);
|
|
998
847
|
}
|
|
999
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/RefLibBrowser.tsx
|
|
969
|
+
/**
|
|
970
|
+
* 应用内目录浏览器(browse 后端可用时,「选择目录」的落地实现)。
|
|
971
|
+
*
|
|
972
|
+
* 为什么需要它:v6 起 profile 把 directory-picker 从 -auto(本机解析为 native,
|
|
973
|
+
* OS 对话框硬编码英文且每次拉 osascript 进程)切换为 -browse 后,
|
|
974
|
+
* `host.pickDirectory`(native 专用 RPC)不再可用;本组件用 `listDirectory`
|
|
975
|
+
* (Node stdlib 列目录,zh/en 本地化、无 OS 进程)提供等价的目录选择:
|
|
976
|
+
* 面包屑 + 路径编辑 + 子目录列表,点击下行进入,「选择此目录」把当前层级添加为
|
|
977
|
+
* 参考库。简化为单列(与官方 DirectoryBrowser 的 Miller 双列相比),
|
|
978
|
+
* 但覆盖添加参考库的核心路径。
|
|
979
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibBrowser
|
|
980
|
+
*/
|
|
981
|
+
/** 面包屑展示:主目录子树内以本地化 Home 开头;之外显示完整祖先链。 */
|
|
982
|
+
function displayCrumbs(listing, homeLabel) {
|
|
983
|
+
const homeIndex = listing.crumbs.findIndex((crumb) => crumb.path === listing.home);
|
|
984
|
+
if (homeIndex === -1) return listing.crumbs;
|
|
985
|
+
const tail = listing.crumbs.slice(homeIndex + 1);
|
|
986
|
+
return [{
|
|
987
|
+
name: homeLabel,
|
|
988
|
+
path: listing.home,
|
|
989
|
+
hidden: false
|
|
990
|
+
}, ...tail];
|
|
991
|
+
}
|
|
992
|
+
/** 平台分隔符(由宿主主目录推断,绝不从键入文本推断)。 */
|
|
993
|
+
function separatorOf(listing) {
|
|
994
|
+
return listing.home.includes("\\") ? "\\" : "/";
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* 渲染应用内目录浏览器。
|
|
998
|
+
* @param props - 见 {@link RefLibBrowserProps}。
|
|
999
|
+
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
1000
|
+
*/
|
|
1001
|
+
function RefLibBrowser(props) {
|
|
1002
|
+
const { open, onClose, listDirectory, t, onOpen } = props;
|
|
1003
|
+
const [listing, setListing] = (0, react.useState)(null);
|
|
1004
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
1005
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1006
|
+
const [pathDraft, setPathDraft] = (0, react.useState)(null);
|
|
1007
|
+
const seq = (0, react.useRef)(0);
|
|
1008
|
+
const controller = (0, react.useRef)(null);
|
|
1009
|
+
const composing = (0, react.useRef)(false);
|
|
1010
|
+
/** 新意图胜出:中止在途扫描并使旧结果失效。 */
|
|
1011
|
+
const navigate = (target) => {
|
|
1012
|
+
controller.current?.abort();
|
|
1013
|
+
controller.current = null;
|
|
1014
|
+
const mine = ++seq.current;
|
|
1015
|
+
setLoading(true);
|
|
1016
|
+
setError(null);
|
|
1017
|
+
const ctrl = new AbortController();
|
|
1018
|
+
controller.current = ctrl;
|
|
1019
|
+
listDirectory(target, ctrl.signal).then((next) => {
|
|
1020
|
+
if (mine !== seq.current) return;
|
|
1021
|
+
setListing(next);
|
|
1022
|
+
setLoading(false);
|
|
1023
|
+
}, (cause) => {
|
|
1024
|
+
if (mine !== seq.current) return;
|
|
1025
|
+
setLoading(false);
|
|
1026
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
1027
|
+
});
|
|
1028
|
+
};
|
|
1029
|
+
(0, react.useEffect)(() => {
|
|
1030
|
+
if (open) navigate();
|
|
1031
|
+
else {
|
|
1032
|
+
seq.current += 1;
|
|
1033
|
+
controller.current?.abort();
|
|
1034
|
+
controller.current = null;
|
|
1035
|
+
setPathDraft(null);
|
|
1036
|
+
setError(null);
|
|
1037
|
+
}
|
|
1038
|
+
}, [open]);
|
|
1039
|
+
const openPathEditor = () => {
|
|
1040
|
+
if (listing === null) {
|
|
1041
|
+
setPathDraft("");
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const sep = separatorOf(listing);
|
|
1045
|
+
const base = listing.path;
|
|
1046
|
+
setPathDraft(base.endsWith(sep) ? base : base + sep);
|
|
1047
|
+
};
|
|
1048
|
+
const commitPath = () => {
|
|
1049
|
+
const draft = pathDraft ?? "";
|
|
1050
|
+
if (draft.trim() === "") return;
|
|
1051
|
+
navigate(draft);
|
|
1052
|
+
setPathDraft(null);
|
|
1053
|
+
};
|
|
1054
|
+
const crumbs = listing === null ? [] : displayCrumbs(listing, t("browser.home"));
|
|
1055
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1056
|
+
open,
|
|
1057
|
+
onClose,
|
|
1058
|
+
title: t("browser.title"),
|
|
1059
|
+
closeLabel: t("browser.cancel"),
|
|
1060
|
+
className: "reflib-browser",
|
|
1061
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1062
|
+
className: "reflib-panel",
|
|
1063
|
+
children: [
|
|
1064
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1065
|
+
className: "reflib-error",
|
|
1066
|
+
role: "alert",
|
|
1067
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1068
|
+
size: 14,
|
|
1069
|
+
className: "reflib-errorIcon"
|
|
1070
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
1071
|
+
}),
|
|
1072
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1073
|
+
className: "reflib-browserPath",
|
|
1074
|
+
children: pathDraft === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1075
|
+
className: "reflib-browserCrumbs",
|
|
1076
|
+
role: "navigation",
|
|
1077
|
+
children: crumbs.map((crumb, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1078
|
+
className: "reflib-browserCrumb",
|
|
1079
|
+
children: [index > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {
|
|
1080
|
+
size: 12,
|
|
1081
|
+
className: "reflib-browserCrumbChevron"
|
|
1082
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1083
|
+
type: "button",
|
|
1084
|
+
className: "reflib-browserCrumbBtn",
|
|
1085
|
+
disabled: loading,
|
|
1086
|
+
onClick: () => {
|
|
1087
|
+
navigate(crumb.path);
|
|
1088
|
+
},
|
|
1089
|
+
children: crumb.name
|
|
1090
|
+
})]
|
|
1091
|
+
}, crumb.path))
|
|
1092
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1093
|
+
type: "button",
|
|
1094
|
+
className: "reflib-browserEdit",
|
|
1095
|
+
"aria-label": t("browser.editPath"),
|
|
1096
|
+
title: t("browser.editPath"),
|
|
1097
|
+
disabled: loading,
|
|
1098
|
+
onClick: openPathEditor,
|
|
1099
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 })
|
|
1100
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1101
|
+
className: "reflib-browserInput",
|
|
1102
|
+
value: pathDraft,
|
|
1103
|
+
"aria-label": t("browser.editPath"),
|
|
1104
|
+
placeholder: t("browser.pathPlaceholder"),
|
|
1105
|
+
autoFocus: true,
|
|
1106
|
+
disabled: loading,
|
|
1107
|
+
onChange: (event) => {
|
|
1108
|
+
setPathDraft(event.currentTarget.value);
|
|
1109
|
+
},
|
|
1110
|
+
onCompositionStart: () => {
|
|
1111
|
+
composing.current = true;
|
|
1112
|
+
},
|
|
1113
|
+
onCompositionEnd: () => {
|
|
1114
|
+
composing.current = false;
|
|
1115
|
+
},
|
|
1116
|
+
onKeyDown: (event) => {
|
|
1117
|
+
if (event.key === "Enter" && !composing.current) {
|
|
1118
|
+
event.preventDefault();
|
|
1119
|
+
commitPath();
|
|
1120
|
+
}
|
|
1121
|
+
if (event.key === "Escape") {
|
|
1122
|
+
event.stopPropagation();
|
|
1123
|
+
setPathDraft(null);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
})
|
|
1127
|
+
}),
|
|
1128
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1129
|
+
className: "reflib-status",
|
|
1130
|
+
role: "status",
|
|
1131
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1132
|
+
size: 20,
|
|
1133
|
+
className: "reflib-spin reflib-statusIcon"
|
|
1134
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1135
|
+
className: "reflib-statusText",
|
|
1136
|
+
children: t("browser.loading")
|
|
1137
|
+
})]
|
|
1138
|
+
}) : listing !== null && listing.entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1139
|
+
className: "reflib-status",
|
|
1140
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1141
|
+
size: 22,
|
|
1142
|
+
className: "reflib-statusIcon"
|
|
1143
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1144
|
+
className: "reflib-statusText",
|
|
1145
|
+
children: t("browser.empty")
|
|
1146
|
+
})]
|
|
1147
|
+
}) : listing !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1148
|
+
className: "reflib-browserList",
|
|
1149
|
+
children: listing.entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1150
|
+
type: "button",
|
|
1151
|
+
className: "reflib-browserRow",
|
|
1152
|
+
"aria-label": t("browser.enter.aria", { name: entry.name }),
|
|
1153
|
+
disabled: loading,
|
|
1154
|
+
onClick: () => {
|
|
1155
|
+
navigate(entry.path);
|
|
1156
|
+
},
|
|
1157
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {
|
|
1158
|
+
size: 16,
|
|
1159
|
+
className: "reflib-browserRowIcon"
|
|
1160
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1161
|
+
className: "reflib-browserRowName",
|
|
1162
|
+
children: entry.name
|
|
1163
|
+
})]
|
|
1164
|
+
}, entry.path))
|
|
1165
|
+
}),
|
|
1166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
1167
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1168
|
+
className: "reflib-browserFooter",
|
|
1169
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1170
|
+
variant: "outline",
|
|
1171
|
+
size: "sm",
|
|
1172
|
+
disabled: loading,
|
|
1173
|
+
onClick: onClose,
|
|
1174
|
+
children: t("browser.cancel")
|
|
1175
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1176
|
+
variant: "primary",
|
|
1177
|
+
size: "sm",
|
|
1178
|
+
disabled: loading || listing === null,
|
|
1179
|
+
"aria-label": t("browser.open.aria"),
|
|
1180
|
+
onClick: () => {
|
|
1181
|
+
if (listing !== null) onOpen(listing.path);
|
|
1182
|
+
},
|
|
1183
|
+
children: t("browser.open")
|
|
1184
|
+
})]
|
|
1185
|
+
})
|
|
1186
|
+
]
|
|
1187
|
+
})
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/client/RefLibPanel.tsx
|
|
1192
|
+
/**
|
|
1193
|
+
* 参考库管理面板(Modal):列表(基名 + 全路径、移除操作)+ 添加表单。
|
|
1194
|
+
*
|
|
1195
|
+
* 设计(优化点 1/2):全部样式走 “--dsw-*” 设计令牌(随浅/深主题适配),
|
|
1196
|
+
* 列表行以「基名为主行、完整路径为次行」呈现,移除为带危险 hover 的图标按钮,
|
|
1197
|
+
* 添加为主按钮 + 路径输入(支持直接粘贴路径,绕开卡顿的系统选择器)+ 系统选择器
|
|
1198
|
+
* 兜底;空态/加载态/错误态各有独立视觉。文案全部经 `t` 本地化(zh/en)。
|
|
1199
|
+
* 本组件为纯展示:异步操作与错误归集由 RefLibDock 持有。
|
|
1200
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibPanel
|
|
1201
|
+
*/
|
|
1202
|
+
/** 用途说明最大长度(与 node half service.NOTE_MAX_LENGTH 一致)。 */
|
|
1203
|
+
const NOTE_MAX_LENGTH = 120;
|
|
1204
|
+
/**
|
|
1205
|
+
* 渲染参考库管理面板。
|
|
1206
|
+
* @param props - 见 {@link RefLibPanelProps}。
|
|
1207
|
+
* @returns 对话框元素(关闭时 Modal 返回 null)。
|
|
1208
|
+
*/
|
|
1209
|
+
function RefLibPanel(props) {
|
|
1210
|
+
const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onUpdateNote, onBrowse } = props;
|
|
1211
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
1212
|
+
const [noteDraft, setNoteDraft] = (0, react.useState)("");
|
|
1213
|
+
const [detailId, setDetailId] = (0, react.useState)(null);
|
|
1214
|
+
const [editNote, setEditNote] = (0, react.useState)("");
|
|
1215
|
+
(0, react.useEffect)(() => {
|
|
1216
|
+
if (!open) {
|
|
1217
|
+
setDraft("");
|
|
1218
|
+
setNoteDraft("");
|
|
1219
|
+
setDetailId(null);
|
|
1220
|
+
}
|
|
1221
|
+
}, [open]);
|
|
1222
|
+
const handleSubmit = async () => {
|
|
1223
|
+
const trimmed = draft.trim();
|
|
1224
|
+
if (trimmed === "") return;
|
|
1225
|
+
try {
|
|
1226
|
+
const note = noteDraft.trim();
|
|
1227
|
+
await onAddPath(trimmed, note === "" ? void 0 : note);
|
|
1228
|
+
setDraft("");
|
|
1229
|
+
setNoteDraft("");
|
|
1230
|
+
} catch {}
|
|
1231
|
+
};
|
|
1232
|
+
/** 浏览选中目录 → 填入路径字段(不直接添加,用户可补用途后提交)。 */
|
|
1233
|
+
const handleBrowse = async () => {
|
|
1234
|
+
const path = await onBrowse();
|
|
1235
|
+
if (path !== null) setDraft(path);
|
|
1236
|
+
};
|
|
1237
|
+
/** 展开/收起条目详情;展开时载入当前用途草稿。 */
|
|
1238
|
+
const toggleDetail = (entry) => {
|
|
1239
|
+
if (detailId === entry.id) {
|
|
1240
|
+
setDetailId(null);
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
setDetailId(entry.id);
|
|
1244
|
+
setEditNote(entry.note ?? "");
|
|
1245
|
+
};
|
|
1246
|
+
/** 保存详情编辑的用途说明;成功后收起详情。 */
|
|
1247
|
+
const handleSaveNote = async () => {
|
|
1248
|
+
if (detailId === null) return;
|
|
1249
|
+
await onUpdateNote(detailId, editNote);
|
|
1250
|
+
setDetailId(null);
|
|
1251
|
+
};
|
|
1252
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1253
|
+
open,
|
|
1254
|
+
onClose,
|
|
1255
|
+
title: t("panel.title"),
|
|
1256
|
+
closeLabel: t("panel.close"),
|
|
1257
|
+
description: t("panel.description", { count: String(libs.length) }),
|
|
1258
|
+
className: "reflib-modal",
|
|
1259
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1260
|
+
className: "reflib-panel",
|
|
1261
|
+
children: [
|
|
1262
|
+
error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1263
|
+
className: "reflib-error",
|
|
1264
|
+
role: "alert",
|
|
1265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1266
|
+
size: 14,
|
|
1267
|
+
className: "reflib-errorIcon"
|
|
1268
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
|
|
1269
|
+
}),
|
|
1270
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1271
|
+
className: "reflib-status",
|
|
1272
|
+
role: "status",
|
|
1273
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1274
|
+
size: 20,
|
|
1275
|
+
className: "reflib-spin reflib-statusIcon"
|
|
1276
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1277
|
+
className: "reflib-statusText",
|
|
1278
|
+
children: t("list.loading")
|
|
1279
|
+
})]
|
|
1280
|
+
}) : libs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1281
|
+
className: "reflib-status",
|
|
1282
|
+
children: [
|
|
1283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1284
|
+
size: 22,
|
|
1285
|
+
className: "reflib-statusIcon"
|
|
1286
|
+
}),
|
|
1287
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1288
|
+
className: "reflib-statusText",
|
|
1289
|
+
children: t("list.empty")
|
|
1290
|
+
}),
|
|
1291
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1292
|
+
className: "reflib-statusHint",
|
|
1293
|
+
children: t("list.empty.hint")
|
|
1294
|
+
})
|
|
1295
|
+
]
|
|
1296
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1297
|
+
className: "reflib-list",
|
|
1298
|
+
children: libs.map((entry) => {
|
|
1299
|
+
const name = libBasename(entry.path);
|
|
1300
|
+
const removing = removingId === entry.id;
|
|
1301
|
+
const detailOpen = detailId === entry.id;
|
|
1302
|
+
const unavailable = entry.status !== void 0 && entry.status !== "available";
|
|
1303
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1304
|
+
className: "reflib-listItem",
|
|
1305
|
+
"data-removing": removing || void 0,
|
|
1306
|
+
"data-status": unavailable ? entry.status : void 0,
|
|
1307
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1308
|
+
className: "reflib-row",
|
|
1309
|
+
children: [
|
|
1310
|
+
unavailable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {
|
|
1311
|
+
size: 16,
|
|
1312
|
+
className: "reflib-rowIcon reflib-rowIconWarn"
|
|
1313
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
|
|
1314
|
+
size: 16,
|
|
1315
|
+
className: "reflib-rowIcon"
|
|
1316
|
+
}),
|
|
1317
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1318
|
+
className: "reflib-rowBody",
|
|
1319
|
+
children: [
|
|
1320
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1321
|
+
className: "reflib-rowName",
|
|
1322
|
+
title: entry.path,
|
|
1323
|
+
children: name
|
|
1324
|
+
}),
|
|
1325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1326
|
+
className: "reflib-rowPath",
|
|
1327
|
+
title: entry.path,
|
|
1328
|
+
children: entry.path
|
|
1329
|
+
}),
|
|
1330
|
+
unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1331
|
+
className: "reflib-rowStatus",
|
|
1332
|
+
role: "status",
|
|
1333
|
+
children: t(entry.status === "missing" ? "status.missing" : "status.notDirectory")
|
|
1334
|
+
}),
|
|
1335
|
+
entry.note !== void 0 && entry.note !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1336
|
+
className: "reflib-rowNote",
|
|
1337
|
+
title: entry.note,
|
|
1338
|
+
children: entry.note.replace(/\s+/g, " ")
|
|
1339
|
+
})
|
|
1340
|
+
]
|
|
1341
|
+
}),
|
|
1342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1343
|
+
label: t("detail.open"),
|
|
1344
|
+
side: "top",
|
|
1345
|
+
delayMs: 400,
|
|
1346
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1347
|
+
type: "button",
|
|
1348
|
+
className: "reflib-rowAction",
|
|
1349
|
+
"aria-label": t("detail.open.aria", { name }),
|
|
1350
|
+
"aria-expanded": detailOpen || void 0,
|
|
1351
|
+
disabled: busy || removingId !== null || unavailable,
|
|
1352
|
+
onClick: () => {
|
|
1353
|
+
toggleDetail(entry);
|
|
1354
|
+
},
|
|
1355
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 14 })
|
|
1356
|
+
})
|
|
1357
|
+
}),
|
|
1358
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1359
|
+
label: t("list.remove"),
|
|
1360
|
+
side: "top",
|
|
1361
|
+
delayMs: 400,
|
|
1362
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1363
|
+
type: "button",
|
|
1364
|
+
className: "reflib-rowRemove",
|
|
1365
|
+
"aria-label": t("list.remove.aria", { name }),
|
|
1366
|
+
disabled: busy || removingId !== null,
|
|
1367
|
+
onClick: () => {
|
|
1368
|
+
onRemove(entry.id);
|
|
1369
|
+
},
|
|
1370
|
+
children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
|
|
1371
|
+
size: 14,
|
|
1372
|
+
className: "reflib-spin"
|
|
1373
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
1374
|
+
})
|
|
1375
|
+
})
|
|
1376
|
+
]
|
|
1377
|
+
}), detailOpen && !unavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1378
|
+
className: "reflib-detail",
|
|
1379
|
+
children: [
|
|
1380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1381
|
+
className: "reflib-detailMeta",
|
|
1382
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1383
|
+
className: "reflib-detailKey",
|
|
1384
|
+
children: "ID"
|
|
1385
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1386
|
+
className: "reflib-detailValue",
|
|
1387
|
+
children: entry.id
|
|
1388
|
+
})]
|
|
1389
|
+
}),
|
|
1390
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1391
|
+
className: "reflib-detailMeta",
|
|
1392
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1393
|
+
className: "reflib-detailKey",
|
|
1394
|
+
children: t("detail.path")
|
|
1395
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1396
|
+
className: "reflib-detailValue",
|
|
1397
|
+
title: entry.path,
|
|
1398
|
+
children: entry.path
|
|
1399
|
+
})]
|
|
1400
|
+
}),
|
|
1401
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1402
|
+
className: "reflib-detailKey",
|
|
1403
|
+
children: t("add.note.label")
|
|
1404
|
+
}),
|
|
1405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1406
|
+
className: "reflib-noteWrap",
|
|
1407
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1408
|
+
className: "reflib-noteTextarea",
|
|
1409
|
+
value: editNote,
|
|
1410
|
+
placeholder: t("add.note.placeholder"),
|
|
1411
|
+
"aria-label": t("add.note.label"),
|
|
1412
|
+
disabled: busy,
|
|
1413
|
+
rows: 3,
|
|
1414
|
+
maxLength: NOTE_MAX_LENGTH,
|
|
1415
|
+
onChange: (event) => {
|
|
1416
|
+
setEditNote(event.currentTarget.value);
|
|
1417
|
+
}
|
|
1418
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1419
|
+
className: "reflib-noteCount",
|
|
1420
|
+
children: [
|
|
1421
|
+
editNote.length,
|
|
1422
|
+
"/",
|
|
1423
|
+
NOTE_MAX_LENGTH
|
|
1424
|
+
]
|
|
1425
|
+
})]
|
|
1426
|
+
}),
|
|
1427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1428
|
+
className: "reflib-detailActions",
|
|
1429
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1430
|
+
variant: "ghost",
|
|
1431
|
+
size: "sm",
|
|
1432
|
+
disabled: busy,
|
|
1433
|
+
onClick: () => {
|
|
1434
|
+
setDetailId(null);
|
|
1435
|
+
},
|
|
1436
|
+
children: t("detail.cancel")
|
|
1437
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1438
|
+
variant: "primary",
|
|
1439
|
+
size: "sm",
|
|
1440
|
+
disabled: busy,
|
|
1441
|
+
onClick: () => {
|
|
1442
|
+
handleSaveNote();
|
|
1443
|
+
},
|
|
1444
|
+
children: t("detail.save")
|
|
1445
|
+
})]
|
|
1446
|
+
})
|
|
1447
|
+
]
|
|
1448
|
+
})]
|
|
1449
|
+
}, entry.id);
|
|
1450
|
+
})
|
|
1451
|
+
}),
|
|
1452
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "reflib-divider" }),
|
|
1453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1454
|
+
className: "reflib-add",
|
|
1455
|
+
children: [
|
|
1456
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1457
|
+
className: "reflib-addLabel",
|
|
1458
|
+
children: t("add.label")
|
|
1459
|
+
}),
|
|
1460
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1461
|
+
className: "reflib-addRow",
|
|
1462
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
1463
|
+
className: "reflib-addInputWrap",
|
|
1464
|
+
value: draft,
|
|
1465
|
+
placeholder: t("add.placeholder"),
|
|
1466
|
+
"aria-label": t("add.label"),
|
|
1467
|
+
disabled: busy || picking,
|
|
1468
|
+
onChange: (event) => {
|
|
1469
|
+
setDraft(event.currentTarget.value);
|
|
1470
|
+
},
|
|
1471
|
+
onKeyDown: (event) => {
|
|
1472
|
+
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
1473
|
+
event.preventDefault();
|
|
1474
|
+
handleSubmit();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1478
|
+
variant: "outline",
|
|
1479
|
+
size: "sm",
|
|
1480
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
|
|
1481
|
+
disabled: busy || picking,
|
|
1482
|
+
onClick: () => {
|
|
1483
|
+
handleBrowse();
|
|
1484
|
+
},
|
|
1485
|
+
children: t("add.browse")
|
|
1486
|
+
})]
|
|
1487
|
+
}),
|
|
1488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1489
|
+
className: "reflib-noteWrap",
|
|
1490
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1491
|
+
className: "reflib-noteTextarea",
|
|
1492
|
+
value: noteDraft,
|
|
1493
|
+
placeholder: t("add.note.placeholder"),
|
|
1494
|
+
"aria-label": t("add.note.label"),
|
|
1495
|
+
disabled: busy || picking,
|
|
1496
|
+
rows: 2,
|
|
1497
|
+
maxLength: NOTE_MAX_LENGTH,
|
|
1498
|
+
onChange: (event) => {
|
|
1499
|
+
setNoteDraft(event.currentTarget.value);
|
|
1500
|
+
}
|
|
1501
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1502
|
+
className: "reflib-noteCount",
|
|
1503
|
+
children: [
|
|
1504
|
+
noteDraft.length,
|
|
1505
|
+
"/",
|
|
1506
|
+
NOTE_MAX_LENGTH
|
|
1507
|
+
]
|
|
1508
|
+
})]
|
|
1509
|
+
}),
|
|
1510
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1511
|
+
variant: "primary",
|
|
1512
|
+
className: "reflib-addSubmit",
|
|
1513
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
|
|
1514
|
+
disabled: busy || picking || draft.trim() === "",
|
|
1515
|
+
onClick: () => {
|
|
1516
|
+
handleSubmit();
|
|
1517
|
+
},
|
|
1518
|
+
children: t("add.submit")
|
|
1519
|
+
}),
|
|
1520
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1521
|
+
className: "reflib-addHint",
|
|
1522
|
+
children: t("add.hint")
|
|
1523
|
+
})
|
|
1524
|
+
]
|
|
1525
|
+
})
|
|
1526
|
+
]
|
|
1527
|
+
})
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
//#endregion
|
|
1000
1531
|
//#region src/client/RefLibDock.tsx
|
|
1001
1532
|
/**
|
|
1002
1533
|
* 参考库入口(conversation.input.dock,输入卡正上方)+ 面板状态持有者。
|
|
@@ -1015,12 +1546,16 @@ window.__ModuleLoader__.load({
|
|
|
1015
1546
|
* 目录选择走系统原生选择器,另提供路径输入直加(绕开卡顿的原生对话框)。
|
|
1016
1547
|
* @module @hpyperry/dsh-ref-lib/src/client/RefLibDock
|
|
1017
1548
|
*/
|
|
1549
|
+
/** 挂载预载失败重试:间隔与上限(重启后会话 live 通常 <1s,重试 1–2 次即成功)。 */
|
|
1550
|
+
const MOUNT_RETRY_DELAY_MS = 800;
|
|
1551
|
+
const MOUNT_RETRY_MAX = 5;
|
|
1018
1552
|
/** wire 错误码 → 本地化文案键(未知码回退原始消息)。 */
|
|
1019
1553
|
const ERROR_KEYS = {
|
|
1020
1554
|
"ref-lib/missing": "error.missing",
|
|
1021
1555
|
"ref-lib/not-directory": "error.notDirectory",
|
|
1022
1556
|
"ref-lib/unsafe": "error.unsafe",
|
|
1023
|
-
"ref-lib/unknown-id": "error.unknownId"
|
|
1557
|
+
"ref-lib/unknown-id": "error.unknownId",
|
|
1558
|
+
"ref-lib/unavailable": "error.unavailable"
|
|
1024
1559
|
};
|
|
1025
1560
|
/** 把未知错误规整为可展示文案。 */
|
|
1026
1561
|
function messageOf(cause) {
|
|
@@ -1045,7 +1580,7 @@ window.__ModuleLoader__.load({
|
|
|
1045
1580
|
* @returns 胶囊入口(+ 打开时的管理面板)。
|
|
1046
1581
|
*/
|
|
1047
1582
|
function RefLibDock(props) {
|
|
1048
|
-
const { sessionId, load, add, remove, pickDirectory, listDirectory, t } = props;
|
|
1583
|
+
const { sessionId, session, load, add, remove, setNote, pickDirectory, listDirectory, t } = props;
|
|
1049
1584
|
const [open, setOpen] = (0, react.useState)(false);
|
|
1050
1585
|
const [libs, setLibs] = (0, react.useState)([]);
|
|
1051
1586
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -1053,9 +1588,10 @@ window.__ModuleLoader__.load({
|
|
|
1053
1588
|
const [picking, setPicking] = (0, react.useState)(false);
|
|
1054
1589
|
const [removingId, setRemovingId] = (0, react.useState)(null);
|
|
1055
1590
|
const [error, setError] = (0, react.useState)(null);
|
|
1056
|
-
const
|
|
1591
|
+
const guard = (0, react.useRef)(new RefreshGuard());
|
|
1057
1592
|
const [pickerMode, setPickerMode] = (0, react.useState)(null);
|
|
1058
1593
|
const [browserOpen, setBrowserOpen] = (0, react.useState)(false);
|
|
1594
|
+
const pendingPick = (0, react.useRef)(null);
|
|
1059
1595
|
const detectPicker = async () => {
|
|
1060
1596
|
if (pickerMode !== null) return pickerMode;
|
|
1061
1597
|
let mode;
|
|
@@ -1068,23 +1604,57 @@ window.__ModuleLoader__.load({
|
|
|
1068
1604
|
setPickerMode(mode);
|
|
1069
1605
|
return mode;
|
|
1070
1606
|
};
|
|
1071
|
-
|
|
1072
|
-
|
|
1607
|
+
/**
|
|
1608
|
+
* 拉取会话参考库列表并更新 UI。
|
|
1609
|
+
* @param silent - 静默模式(轮询用):失败不写入错误槽;成功也**不碰 error/loading**
|
|
1610
|
+
* ——错误与加载态只由用户操作/打开面板管理,避免后台轮询吞掉操作错误提示或
|
|
1611
|
+
* 提前结束面板加载态(轮询仅更新 libs 数据)。
|
|
1612
|
+
*/
|
|
1613
|
+
const refresh = async (silent = false) => {
|
|
1614
|
+
const mine = guard.current.begin();
|
|
1073
1615
|
try {
|
|
1074
1616
|
const next = await load(sessionId);
|
|
1075
|
-
if (
|
|
1617
|
+
if (!guard.current.isLatest(mine)) return;
|
|
1076
1618
|
setLibs(next);
|
|
1077
|
-
setError(null);
|
|
1619
|
+
if (!silent) setError(null);
|
|
1078
1620
|
} catch (cause) {
|
|
1079
|
-
if (
|
|
1080
|
-
setError(formatError(cause, t));
|
|
1621
|
+
if (!guard.current.isLatest(mine)) return;
|
|
1622
|
+
if (!silent) setError(formatError(cause, t));
|
|
1081
1623
|
} finally {
|
|
1082
|
-
if (mine
|
|
1624
|
+
if (guard.current.isLatest(mine) && !silent) setLoading(false);
|
|
1083
1625
|
}
|
|
1084
1626
|
};
|
|
1085
1627
|
(0, react.useEffect)(() => {
|
|
1086
1628
|
ensureRefLibStyles();
|
|
1087
|
-
|
|
1629
|
+
let stopped = false;
|
|
1630
|
+
let retries = 0;
|
|
1631
|
+
let timer;
|
|
1632
|
+
const attempt = async () => {
|
|
1633
|
+
const mine = guard.current.begin();
|
|
1634
|
+
try {
|
|
1635
|
+
const next = await load(sessionId);
|
|
1636
|
+
if (stopped || !guard.current.isLatest(mine)) return;
|
|
1637
|
+
setLibs(next);
|
|
1638
|
+
setError(null);
|
|
1639
|
+
} catch (cause) {
|
|
1640
|
+
if (stopped || !guard.current.isLatest(mine)) return;
|
|
1641
|
+
if (retries < MOUNT_RETRY_MAX) {
|
|
1642
|
+
retries += 1;
|
|
1643
|
+
timer = window.setTimeout(() => {
|
|
1644
|
+
attempt();
|
|
1645
|
+
}, MOUNT_RETRY_DELAY_MS);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
setError(formatError(cause, t));
|
|
1649
|
+
} finally {
|
|
1650
|
+
if (!stopped && guard.current.isLatest(mine)) setLoading(false);
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
attempt();
|
|
1654
|
+
return () => {
|
|
1655
|
+
stopped = true;
|
|
1656
|
+
if (timer !== void 0) window.clearTimeout(timer);
|
|
1657
|
+
};
|
|
1088
1658
|
}, [sessionId]);
|
|
1089
1659
|
(0, react.useEffect)(() => {
|
|
1090
1660
|
if (open) {
|
|
@@ -1092,6 +1662,14 @@ window.__ModuleLoader__.load({
|
|
|
1092
1662
|
refresh();
|
|
1093
1663
|
}
|
|
1094
1664
|
}, [open]);
|
|
1665
|
+
const userMessageCount = session.nodes.filter((node) => node.kind === "user").length;
|
|
1666
|
+
(0, react.useEffect)(() => {
|
|
1667
|
+
refresh(true);
|
|
1668
|
+
}, [userMessageCount]);
|
|
1669
|
+
const refLibCommandDone = session.nodes.filter((node) => node.kind === "command" && node.name === "ref-lib" && node.outcome !== null).length;
|
|
1670
|
+
(0, react.useEffect)(() => {
|
|
1671
|
+
refresh(true);
|
|
1672
|
+
}, [refLibCommandDone]);
|
|
1095
1673
|
const handleRemove = async (id) => {
|
|
1096
1674
|
setRemovingId(id);
|
|
1097
1675
|
setBusy(true);
|
|
@@ -1106,11 +1684,11 @@ window.__ModuleLoader__.load({
|
|
|
1106
1684
|
setBusy(false);
|
|
1107
1685
|
}
|
|
1108
1686
|
};
|
|
1109
|
-
const handleAddPath = async (path) => {
|
|
1687
|
+
const handleAddPath = async (path, note) => {
|
|
1110
1688
|
setBusy(true);
|
|
1111
1689
|
setError(null);
|
|
1112
1690
|
try {
|
|
1113
|
-
await add(sessionId, path);
|
|
1691
|
+
await add(sessionId, path, note);
|
|
1114
1692
|
await refresh();
|
|
1115
1693
|
} catch (cause) {
|
|
1116
1694
|
setError(formatError(cause, t));
|
|
@@ -1119,48 +1697,68 @@ window.__ModuleLoader__.load({
|
|
|
1119
1697
|
setBusy(false);
|
|
1120
1698
|
}
|
|
1121
1699
|
};
|
|
1122
|
-
/**
|
|
1123
|
-
const
|
|
1700
|
+
/** 更新条目用途说明(编辑详情保存);失败入错误槽、不抛出(编辑区保持打开)。 */
|
|
1701
|
+
const handleUpdateNote = async (id, note) => {
|
|
1124
1702
|
setBusy(true);
|
|
1125
1703
|
setError(null);
|
|
1126
1704
|
try {
|
|
1127
|
-
await
|
|
1705
|
+
await setNote(sessionId, id, note);
|
|
1128
1706
|
await refresh();
|
|
1129
|
-
return true;
|
|
1130
1707
|
} catch (cause) {
|
|
1131
1708
|
setError(formatError(cause, t));
|
|
1132
|
-
return false;
|
|
1133
1709
|
} finally {
|
|
1134
1710
|
setBusy(false);
|
|
1135
1711
|
}
|
|
1136
1712
|
};
|
|
1137
|
-
|
|
1713
|
+
/** 唤起目录选择(browse 应用内浏览器 / native 系统对话框)并返回选中的路径;
|
|
1714
|
+
* 取消或失败返回 null——**不直接添加**,由面板把路径填入表单(用户可补用途后提交)。 */
|
|
1715
|
+
const pickPath = async () => {
|
|
1138
1716
|
setError(null);
|
|
1139
1717
|
let mode;
|
|
1140
1718
|
try {
|
|
1141
1719
|
mode = await detectPicker();
|
|
1142
1720
|
} catch (cause) {
|
|
1143
1721
|
setError(formatError(cause, t));
|
|
1144
|
-
return;
|
|
1722
|
+
return null;
|
|
1145
1723
|
}
|
|
1146
1724
|
if (mode === "browse") {
|
|
1147
1725
|
setBrowserOpen(true);
|
|
1148
|
-
return
|
|
1726
|
+
return await new Promise((resolve) => {
|
|
1727
|
+
pendingPick.current = resolve;
|
|
1728
|
+
});
|
|
1149
1729
|
}
|
|
1150
1730
|
setPicking(true);
|
|
1151
1731
|
try {
|
|
1152
|
-
|
|
1153
|
-
if (picked !== null) await addPath(picked);
|
|
1732
|
+
return await pickDirectory();
|
|
1154
1733
|
} catch (cause) {
|
|
1155
1734
|
setError(formatError(cause, t));
|
|
1735
|
+
return null;
|
|
1156
1736
|
} finally {
|
|
1157
1737
|
setPicking(false);
|
|
1158
1738
|
}
|
|
1159
1739
|
};
|
|
1160
|
-
/** 应用内浏览器选定目录 →
|
|
1161
|
-
const handleBrowserPick =
|
|
1162
|
-
|
|
1740
|
+
/** 应用内浏览器选定目录 → 把路径交给等待中的 pickPath 调用方并关闭浏览器。 */
|
|
1741
|
+
const handleBrowserPick = (path) => {
|
|
1742
|
+
setBrowserOpen(false);
|
|
1743
|
+
const resolve = pendingPick.current;
|
|
1744
|
+
pendingPick.current = null;
|
|
1745
|
+
resolve?.(path);
|
|
1163
1746
|
};
|
|
1747
|
+
/** 应用内浏览器取消 → 以 null 结束等待中的 pickPath。 */
|
|
1748
|
+
const handleBrowserClose = () => {
|
|
1749
|
+
setBrowserOpen(false);
|
|
1750
|
+
const resolve = pendingPick.current;
|
|
1751
|
+
pendingPick.current = null;
|
|
1752
|
+
resolve?.(null);
|
|
1753
|
+
};
|
|
1754
|
+
/**
|
|
1755
|
+
* 胶囊计数(v9):徽标显示**可用**数量(失效不计入),红色角标显示失效数量
|
|
1756
|
+
* (总数 − 可用数),仅存在失效条目时显示。注意:UI 数据只在打开面板/操作后
|
|
1757
|
+
* 刷新,外部变更(如删除目录)不会自动反向同步到界面(与命令修改后不刷新同属
|
|
1758
|
+
* 待解决的 UI 数据同步问题,见设计文档"遗留备注")。
|
|
1759
|
+
*/
|
|
1760
|
+
const availableCount = libs.filter((entry) => entry.status === "available").length;
|
|
1761
|
+
const unavailableCount = libs.length - availableCount;
|
|
1164
1762
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1165
1763
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1166
1764
|
className: "reflib-dock",
|
|
@@ -1170,7 +1768,7 @@ window.__ModuleLoader__.load({
|
|
|
1170
1768
|
"data-active": open || void 0,
|
|
1171
1769
|
"aria-haspopup": "dialog",
|
|
1172
1770
|
"aria-expanded": open,
|
|
1173
|
-
"aria-label":
|
|
1771
|
+
"aria-label": unavailableCount > 0 ? t("dock.unavailable", { count: String(unavailableCount) }) : availableCount > 0 ? t("dock.count.aria", { count: String(availableCount) }) : t("dock.aria"),
|
|
1174
1772
|
title: t("dock.aria"),
|
|
1175
1773
|
onClick: () => {
|
|
1176
1774
|
setOpen((value) => !value);
|
|
@@ -1184,9 +1782,14 @@ window.__ModuleLoader__.load({
|
|
|
1184
1782
|
className: "reflib-chipLabel",
|
|
1185
1783
|
children: t("dock.label")
|
|
1186
1784
|
}),
|
|
1187
|
-
|
|
1785
|
+
availableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1188
1786
|
className: "reflib-chipBadge",
|
|
1189
|
-
children:
|
|
1787
|
+
children: availableCount
|
|
1788
|
+
}),
|
|
1789
|
+
unavailableCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1790
|
+
className: "reflib-chipWarn",
|
|
1791
|
+
title: t("dock.unavailable", { count: String(unavailableCount) }),
|
|
1792
|
+
children: unavailableCount
|
|
1190
1793
|
})
|
|
1191
1794
|
]
|
|
1192
1795
|
})
|
|
@@ -1207,18 +1810,15 @@ window.__ModuleLoader__.load({
|
|
|
1207
1810
|
handleRemove(id);
|
|
1208
1811
|
},
|
|
1209
1812
|
onAddPath: handleAddPath,
|
|
1210
|
-
|
|
1813
|
+
onUpdateNote: handleUpdateNote,
|
|
1814
|
+
onBrowse: pickPath
|
|
1211
1815
|
}),
|
|
1212
1816
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefLibBrowser, {
|
|
1213
1817
|
open: browserOpen,
|
|
1214
|
-
onClose:
|
|
1215
|
-
setBrowserOpen(false);
|
|
1216
|
-
},
|
|
1818
|
+
onClose: handleBrowserClose,
|
|
1217
1819
|
listDirectory,
|
|
1218
1820
|
t,
|
|
1219
|
-
onOpen:
|
|
1220
|
-
handleBrowserPick(path);
|
|
1221
|
-
}
|
|
1821
|
+
onOpen: handleBrowserPick
|
|
1222
1822
|
})
|
|
1223
1823
|
] });
|
|
1224
1824
|
}
|
|
@@ -1238,20 +1838,29 @@ window.__ModuleLoader__.load({
|
|
|
1238
1838
|
"dock.label": "参考库",
|
|
1239
1839
|
"dock.aria": "管理只读参考库",
|
|
1240
1840
|
"dock.count.aria": "参考库,共 {count} 个",
|
|
1841
|
+
"dock.unavailable": "参考库,{count} 个失效",
|
|
1842
|
+
"status.missing": "目录已删除或不可访问",
|
|
1843
|
+
"status.notDirectory": "路径不再是目录",
|
|
1241
1844
|
"panel.title": "参考库",
|
|
1242
1845
|
"panel.close": "关闭",
|
|
1243
1846
|
"panel.description": "共 {count} 个只读参考库:供 agent 读取参考,禁止修改其中文件",
|
|
1244
1847
|
"list.remove": "移除",
|
|
1245
1848
|
"list.remove.aria": "移除参考库 {name}",
|
|
1246
1849
|
"list.empty": "还没有参考库",
|
|
1247
|
-
"list.empty.hint": "
|
|
1850
|
+
"list.empty.hint": "在下方输入目录路径,或点击「浏览」选择目录",
|
|
1248
1851
|
"list.loading": "正在加载…",
|
|
1852
|
+
"detail.open": "查看详情",
|
|
1853
|
+
"detail.open.aria": "查看 {name} 的详情",
|
|
1854
|
+
"detail.path": "路径",
|
|
1855
|
+
"detail.cancel": "取消",
|
|
1856
|
+
"detail.save": "保存",
|
|
1249
1857
|
"add.label": "添加参考库",
|
|
1250
1858
|
"add.placeholder": "输入目录路径(支持 ~ 与相对)",
|
|
1251
1859
|
"add.submit": "添加",
|
|
1252
|
-
"add.
|
|
1253
|
-
"add.
|
|
1254
|
-
"add.
|
|
1860
|
+
"add.browse": "浏览",
|
|
1861
|
+
"add.note.label": "用途说明",
|
|
1862
|
+
"add.note.placeholder": "用途说明(可选,帮助 agent 判断相关性)",
|
|
1863
|
+
"add.hint": "目录仅作只读参考;用途不填时自动提取 README 标题",
|
|
1255
1864
|
"browser.title": "选择目录",
|
|
1256
1865
|
"browser.home": "主目录",
|
|
1257
1866
|
"browser.open": "选择此目录",
|
|
@@ -1265,27 +1874,41 @@ window.__ModuleLoader__.load({
|
|
|
1265
1874
|
"error.missing": "目录不存在:{path}",
|
|
1266
1875
|
"error.notDirectory": "不是目录:{path}",
|
|
1267
1876
|
"error.unsafe": "路径包含控制字符,已拒绝:{path}",
|
|
1268
|
-
"error.unknownId": "未找到参考库条目:{id}"
|
|
1877
|
+
"error.unknownId": "未找到参考库条目:{id}",
|
|
1878
|
+
"error.unavailable": "参考库目录不可用(仅允许移除)",
|
|
1879
|
+
"command.running": "执行中…",
|
|
1880
|
+
"command.done": "完成",
|
|
1881
|
+
"command.copy": "复制",
|
|
1882
|
+
"command.copied": "已复制"
|
|
1269
1883
|
};
|
|
1270
1884
|
/** 英文词典,与 zh 键集逐键对齐(编译期校验)。 */
|
|
1271
1885
|
const en = {
|
|
1272
1886
|
"dock.label": "Reference Library",
|
|
1273
1887
|
"dock.aria": "Manage read-only reference libraries",
|
|
1274
1888
|
"dock.count.aria": "Reference library, {count} total",
|
|
1889
|
+
"dock.unavailable": "Reference library, {count} unavailable",
|
|
1890
|
+
"status.missing": "Directory deleted or unavailable",
|
|
1891
|
+
"status.notDirectory": "Path is no longer a directory",
|
|
1275
1892
|
"panel.title": "Reference Library",
|
|
1276
1893
|
"panel.close": "Close",
|
|
1277
1894
|
"panel.description": "{count} read-only reference libraries: the agent may read them for reference, but must not modify any files",
|
|
1278
1895
|
"list.remove": "Remove",
|
|
1279
1896
|
"list.remove.aria": "Remove reference library {name}",
|
|
1280
1897
|
"list.empty": "No reference libraries yet",
|
|
1281
|
-
"list.empty.hint": "Enter a directory path below, or click “
|
|
1898
|
+
"list.empty.hint": "Enter a directory path below, or click “Browse” to pick one",
|
|
1282
1899
|
"list.loading": "Loading…",
|
|
1900
|
+
"detail.open": "View details",
|
|
1901
|
+
"detail.open.aria": "View details of {name}",
|
|
1902
|
+
"detail.path": "Path",
|
|
1903
|
+
"detail.cancel": "Cancel",
|
|
1904
|
+
"detail.save": "Save",
|
|
1283
1905
|
"add.label": "Add a reference library",
|
|
1284
1906
|
"add.placeholder": "Enter a directory path (~ or relative)",
|
|
1285
1907
|
"add.submit": "Add",
|
|
1286
|
-
"add.
|
|
1287
|
-
"add.
|
|
1288
|
-
"add.
|
|
1908
|
+
"add.browse": "Browse",
|
|
1909
|
+
"add.note.label": "Description",
|
|
1910
|
+
"add.note.placeholder": "Description (optional; helps the agent judge relevance)",
|
|
1911
|
+
"add.hint": "Directories are read-only references; if no description is given, the README title is used",
|
|
1289
1912
|
"browser.title": "Select Directory",
|
|
1290
1913
|
"browser.home": "Home",
|
|
1291
1914
|
"browser.open": "Select this folder",
|
|
@@ -1299,7 +1922,12 @@ window.__ModuleLoader__.load({
|
|
|
1299
1922
|
"error.missing": "Directory does not exist: {path}",
|
|
1300
1923
|
"error.notDirectory": "Not a directory: {path}",
|
|
1301
1924
|
"error.unsafe": "Path contains control characters and was rejected: {path}",
|
|
1302
|
-
"error.unknownId": "Reference library entry not found: {id}"
|
|
1925
|
+
"error.unknownId": "Reference library entry not found: {id}",
|
|
1926
|
+
"error.unavailable": "Reference library directory unavailable (remove only)",
|
|
1927
|
+
"command.running": "Running…",
|
|
1928
|
+
"command.done": "Done",
|
|
1929
|
+
"command.copy": "Copy",
|
|
1930
|
+
"command.copied": "Copied"
|
|
1303
1931
|
};
|
|
1304
1932
|
//#endregion
|
|
1305
1933
|
//#region src/client/index.ts
|
|
@@ -1344,10 +1972,11 @@ window.__ModuleLoader__.load({
|
|
|
1344
1972
|
}), "ref-lib: dictionaries");
|
|
1345
1973
|
const injected = () => ({
|
|
1346
1974
|
load: async (sessionId) => parseLibsPayload(await api(`/list?session=${encodeURIComponent(sessionId)}`, void 0, "GET")),
|
|
1347
|
-
add: async (sessionId, path) => {
|
|
1975
|
+
add: async (sessionId, path, note) => {
|
|
1348
1976
|
await api("/add", {
|
|
1349
1977
|
session: sessionId,
|
|
1350
|
-
path
|
|
1978
|
+
path,
|
|
1979
|
+
...note === void 0 ? {} : { note }
|
|
1351
1980
|
});
|
|
1352
1981
|
},
|
|
1353
1982
|
remove: async (sessionId, id) => {
|
|
@@ -1356,6 +1985,13 @@ window.__ModuleLoader__.load({
|
|
|
1356
1985
|
id
|
|
1357
1986
|
});
|
|
1358
1987
|
},
|
|
1988
|
+
setNote: async (sessionId, id, note) => {
|
|
1989
|
+
await api("/note", {
|
|
1990
|
+
session: sessionId,
|
|
1991
|
+
id,
|
|
1992
|
+
note
|
|
1993
|
+
});
|
|
1994
|
+
},
|
|
1359
1995
|
pickDirectory: () => ctx.workspaces.pickDirectory(),
|
|
1360
1996
|
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal)
|
|
1361
1997
|
});
|
|
@@ -1366,6 +2002,11 @@ window.__ModuleLoader__.load({
|
|
|
1366
2002
|
locale: NS,
|
|
1367
2003
|
inject: injected
|
|
1368
2004
|
}, RefLibDock));
|
|
2005
|
+
ctx.slots.inject("conversation.chat.commandview", () => ctx.slots.register({
|
|
2006
|
+
name: "conversation.chat.commandview",
|
|
2007
|
+
key: "ref-lib",
|
|
2008
|
+
locale: NS
|
|
2009
|
+
}, RefLibCommandCard));
|
|
1369
2010
|
}
|
|
1370
2011
|
//#endregion
|
|
1371
2012
|
exports.apply = apply;
|