@hpyperry/dsh-ref-lib 0.7.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/LICENSE +21 -0
- package/README.md +91 -0
- package/cordis.patch.yml +6 -0
- package/lib/client/RefLibBrowser.d.ts +35 -0
- package/lib/client/RefLibBrowser.d.ts.map +1 -0
- package/lib/client/RefLibBrowser.js +119 -0
- package/lib/client/RefLibBrowser.js.map +1 -0
- package/lib/client/RefLibDock.d.ts +44 -0
- package/lib/client/RefLibDock.d.ts.map +1 -0
- package/lib/client/RefLibDock.js +218 -0
- package/lib/client/RefLibDock.js.map +1 -0
- package/lib/client/RefLibPanel.d.ts +47 -0
- package/lib/client/RefLibPanel.d.ts.map +1 -0
- package/lib/client/RefLibPanel.js +59 -0
- package/lib/client/RefLibPanel.js.map +1 -0
- package/lib/client/data.d.ts +53 -0
- package/lib/client/data.d.ts.map +1 -0
- package/lib/client/data.js +74 -0
- package/lib/client/data.js.map +1 -0
- package/lib/client/index.d.ts +32 -0
- package/lib/client/index.d.ts.map +1 -0
- package/lib/client/index.js +80 -0
- package/lib/client/index.js.map +1 -0
- package/lib/client/layout.d.ts +34 -0
- package/lib/client/layout.d.ts.map +1 -0
- package/lib/client/layout.js +65 -0
- package/lib/client/layout.js.map +1 -0
- package/lib/client/locales.d.ts +80 -0
- package/lib/client/locales.d.ts.map +1 -0
- package/lib/client/locales.js +84 -0
- package/lib/client/locales.js.map +1 -0
- package/lib/client/styles.d.ts +15 -0
- package/lib/client/styles.d.ts.map +1 -0
- package/lib/client/styles.js +513 -0
- package/lib/client/styles.js.map +1 -0
- package/lib/client.js +1377 -0
- package/lib/client.js.map +1 -0
- package/lib/commands.d.ts +38 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +49 -0
- package/lib/commands.js.map +1 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +94 -0
- package/lib/index.js.map +1 -0
- package/lib/logic.d.ts +32 -0
- package/lib/logic.d.ts.map +1 -0
- package/lib/logic.js +48 -0
- package/lib/logic.js.map +1 -0
- package/lib/render.d.ts +18 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +40 -0
- package/lib/render.js.map +1 -0
- package/lib/routes.d.ts +39 -0
- package/lib/routes.d.ts.map +1 -0
- package/lib/routes.js +210 -0
- package/lib/routes.js.map +1 -0
- package/lib/service.d.ts +88 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +212 -0
- package/lib/service.js.map +1 -0
- package/lib/spec.d.ts +33 -0
- package/lib/spec.d.ts.map +1 -0
- package/lib/spec.js +13 -0
- package/lib/spec.js.map +1 -0
- package/lib/validate.d.ts +17 -0
- package/lib/validate.d.ts.map +1 -0
- package/lib/validate.js +35 -0
- package/lib/validate.js.map +1 -0
- package/package.json +64 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1377 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@hpyperry/dsh-ref-lib",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/validate.ts
|
|
11
|
+
/** 校验一个未知对象是否为合法条目(宽松:仅要求 id/path 字符串、note 可选字符串)。 */
|
|
12
|
+
function isRefLibEntry(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null) return false;
|
|
14
|
+
const { id, path, note } = value;
|
|
15
|
+
if (typeof id !== "string" || typeof path !== "string") return false;
|
|
16
|
+
return note === void 0 || typeof note === "string";
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/client/data.ts
|
|
20
|
+
/** 带 wire code 的 API 错误:code 用于本地化文案映射,message 为原始消息兜底。 */
|
|
21
|
+
var RefLibApiError = class extends Error {
|
|
22
|
+
code;
|
|
23
|
+
details;
|
|
24
|
+
/**
|
|
25
|
+
* @param code - wire 错误码(如 ref-lib/missing)。
|
|
26
|
+
* @param message - 服务端原始错误消息。
|
|
27
|
+
* @param details - 附加字段(path/id)。
|
|
28
|
+
*/
|
|
29
|
+
constructor(code, message, details = {}) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.details = details;
|
|
33
|
+
this.name = "RefLibApiError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 从失败的 /api/ref-lib/* 响应体解析错误码;无 code 的畸形体返回 null。
|
|
38
|
+
* @param value - 非 2xx 响应体(可为任意 JSON)。
|
|
39
|
+
* @returns 错误码 + 原始消息 + 附加字段;无法解析时为 null。
|
|
40
|
+
*/
|
|
41
|
+
function parseApiErrorPayload(value) {
|
|
42
|
+
if (typeof value !== "object" || value === null) return null;
|
|
43
|
+
const { error, code, path, id } = value;
|
|
44
|
+
if (typeof code !== "string" || code === "") return null;
|
|
45
|
+
const details = {
|
|
46
|
+
...typeof path === "string" ? { path } : {},
|
|
47
|
+
...typeof id === "string" ? { id } : {}
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
code,
|
|
51
|
+
message: typeof error === "string" ? error : code,
|
|
52
|
+
details
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 从 /api/ref-lib/list 路由响应({ libs: [...] })解析参考库列表;
|
|
57
|
+
* 缺失/畸形返回空列表。
|
|
58
|
+
* @param value - 路由响应体。
|
|
59
|
+
* @returns 当前参考库列表。
|
|
60
|
+
*/
|
|
61
|
+
function parseLibsPayload(value) {
|
|
62
|
+
if (typeof value !== "object" || value === null) return [];
|
|
63
|
+
const libs = value.libs;
|
|
64
|
+
if (!Array.isArray(libs)) return [];
|
|
65
|
+
return libs.filter(isRefLibEntry);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 提取目录路径的展示用基名(列表行主行的可读标题)。
|
|
69
|
+
* 兼容 POSIX 与 Windows 分隔符;去掉尾部分隔符后取最后一段;
|
|
70
|
+
* 根路径(/、C:\)原样返回。
|
|
71
|
+
* @param path - 规范化绝对路径。
|
|
72
|
+
* @returns 基名(如 /a/b/core → core)。
|
|
73
|
+
*/
|
|
74
|
+
function libBasename(path) {
|
|
75
|
+
const trimmed = path.replace(/[\\/]+$/, "");
|
|
76
|
+
if (trimmed === "") return path;
|
|
77
|
+
if (/^[A-Za-z]:$/.test(trimmed)) return path;
|
|
78
|
+
const idx = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
79
|
+
return idx === -1 ? trimmed : trimmed.slice(idx + 1);
|
|
80
|
+
}
|
|
81
|
+
//#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
|
+
//#region src/client/styles.ts
|
|
489
|
+
/**
|
|
490
|
+
* ref-lib client 样式注入:在文档头挂一张本插件专属的 <style data-plugin> 标签。
|
|
491
|
+
*
|
|
492
|
+
* 为什么不用 CSS Modules:ref-lib 的 tsdown 配置未移植 core 的 lightningcss
|
|
493
|
+
* CSS-Modules 插件(避免新增 native 依赖与构建面),而内联 style 无法表达
|
|
494
|
+
* hover/媒体查询/动画。因此采用运行时注入单张样式表——与 loader 的清理契约
|
|
495
|
+
* 兼容(`data-plugin` 标签在插件卸载/HMR 时由 loader 移除,重新物化时本模块
|
|
496
|
+
* 重新注入,幂等)。类名统一 `reflib-` 前缀防碰撞;颜色/字号/圆角全部走
|
|
497
|
+
* `--dsw-*` 设计令牌,随主题(浅色/深色)自动适配——这是"字体显示不一致、
|
|
498
|
+
* 没有设计感"的根因修复之一(旧实现硬编码 #d33 等,不随主题变化)。
|
|
499
|
+
* @module @hpyperry/dsh-ref-lib/src/client/styles
|
|
500
|
+
*/
|
|
501
|
+
/** 插件 id(与 package.json 的 bundle 手递一致,loader 按它清理样式)。 */
|
|
502
|
+
const PLUGIN_ID = "@hpyperry/dsh-ref-lib";
|
|
503
|
+
/** 样式标签稳定 id(幂等注入的判据)。 */
|
|
504
|
+
const STYLE_TAG_ID = "@hpyperry/dsh-ref-lib/styles";
|
|
505
|
+
const CSS = `
|
|
506
|
+
/* ── 输入卡正上方的入口胶囊行(dock)──
|
|
507
|
+
独立一行,横跨整个 composer 列;chip 与输入卡左缘对齐——用官方设计令牌
|
|
508
|
+
(--dsh-composer-side-clearance / --dsh-composer-card-max-width)纯 CSS 计算,
|
|
509
|
+
零 JS 测量(v7 起取消 hero 相位测量/绝对定位);hero 与 active 一致,
|
|
510
|
+
plan/model 等工具行座位的启停不影响本行位置。 */
|
|
511
|
+
.reflib-dock {
|
|
512
|
+
box-sizing: border-box;
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
min-width: 0;
|
|
516
|
+
/* 镜像输入卡居中:chip 落在输入卡左缘之上(宽视口 = 居中余量,窄视口 = clearance) */
|
|
517
|
+
padding-left: max(
|
|
518
|
+
var(--dsh-composer-side-clearance, 16px),
|
|
519
|
+
calc((100% - var(--dsh-composer-card-max-width, 100%)) / 2)
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.reflib-chip {
|
|
524
|
+
display: inline-flex;
|
|
525
|
+
align-items: center;
|
|
526
|
+
gap: 6px;
|
|
527
|
+
max-width: 100%;
|
|
528
|
+
height: 30px;
|
|
529
|
+
padding: 0 10px 0 8px;
|
|
530
|
+
border: 1px solid var(--dsw-alias-border-l1);
|
|
531
|
+
border-radius: 15px;
|
|
532
|
+
background: transparent;
|
|
533
|
+
color: var(--dsw-alias-label-secondary);
|
|
534
|
+
font-size: 12px;
|
|
535
|
+
line-height: 18px;
|
|
536
|
+
font-weight: 500;
|
|
537
|
+
cursor: pointer;
|
|
538
|
+
transition: background-color 120ms ease, border-color 120ms ease;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.reflib-chip:hover {
|
|
542
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
543
|
+
color: var(--dsw-alias-label-primary);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.reflib-chip:active {
|
|
547
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.reflib-chip[data-active='true'] {
|
|
551
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
552
|
+
border-color: var(--dsw-alias-border-l2);
|
|
553
|
+
color: var(--dsw-alias-label-primary);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.reflib-chipIcon {
|
|
557
|
+
display: inline-flex;
|
|
558
|
+
flex: none;
|
|
559
|
+
color: var(--dsw-alias-label-tertiary);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.reflib-chipLabel {
|
|
563
|
+
overflow: hidden;
|
|
564
|
+
text-overflow: ellipsis;
|
|
565
|
+
white-space: nowrap;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.reflib-chipBadge {
|
|
569
|
+
display: inline-flex;
|
|
570
|
+
align-items: center;
|
|
571
|
+
justify-content: center;
|
|
572
|
+
min-width: 18px;
|
|
573
|
+
height: 18px;
|
|
574
|
+
padding: 0 5px;
|
|
575
|
+
border-radius: 9px;
|
|
576
|
+
background: var(--dsw-alias-state-business-tertiary);
|
|
577
|
+
color: var(--dsw-alias-label-primary-bluish);
|
|
578
|
+
font-size: 11px;
|
|
579
|
+
line-height: 18px;
|
|
580
|
+
font-weight: 500;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* ── 应用内目录浏览器(browse 后端)── */
|
|
584
|
+
.reflib-browser {
|
|
585
|
+
width: min(460px, 100%);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.reflib-browserPath {
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
gap: 6px;
|
|
592
|
+
min-width: 0;
|
|
593
|
+
height: 32px;
|
|
594
|
+
padding: 0 8px;
|
|
595
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
596
|
+
border-radius: 8px;
|
|
597
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.reflib-browserCrumbs {
|
|
601
|
+
display: flex;
|
|
602
|
+
flex: 1;
|
|
603
|
+
align-items: center;
|
|
604
|
+
min-width: 0;
|
|
605
|
+
overflow-x: auto;
|
|
606
|
+
white-space: nowrap;
|
|
607
|
+
scrollbar-width: none;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.reflib-browserCrumbs::-webkit-scrollbar {
|
|
611
|
+
display: none;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.reflib-browserCrumb {
|
|
615
|
+
display: inline-flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
flex: none;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.reflib-browserCrumbChevron {
|
|
621
|
+
flex: none;
|
|
622
|
+
color: var(--dsw-alias-label-caption);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.reflib-browserCrumbBtn {
|
|
626
|
+
display: inline-flex;
|
|
627
|
+
align-items: center;
|
|
628
|
+
max-width: 160px;
|
|
629
|
+
padding: 2px 6px;
|
|
630
|
+
border: none;
|
|
631
|
+
border-radius: 6px;
|
|
632
|
+
background: transparent;
|
|
633
|
+
color: var(--dsw-alias-label-secondary);
|
|
634
|
+
font-size: 12px;
|
|
635
|
+
line-height: 18px;
|
|
636
|
+
overflow: hidden;
|
|
637
|
+
text-overflow: ellipsis;
|
|
638
|
+
cursor: pointer;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.reflib-browserCrumbBtn:hover:not(:disabled) {
|
|
642
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
643
|
+
color: var(--dsw-alias-label-primary);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.reflib-browserCrumbBtn:disabled {
|
|
647
|
+
opacity: 0.5;
|
|
648
|
+
cursor: default;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.reflib-browserEdit {
|
|
652
|
+
display: inline-flex;
|
|
653
|
+
align-items: center;
|
|
654
|
+
justify-content: center;
|
|
655
|
+
flex: none;
|
|
656
|
+
width: 24px;
|
|
657
|
+
height: 24px;
|
|
658
|
+
padding: 0;
|
|
659
|
+
border: none;
|
|
660
|
+
border-radius: 6px;
|
|
661
|
+
background: transparent;
|
|
662
|
+
color: var(--dsw-alias-label-tertiary);
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.reflib-browserEdit:hover:not(:disabled) {
|
|
667
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
668
|
+
color: var(--dsw-alias-label-primary);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.reflib-browserEdit:disabled {
|
|
672
|
+
opacity: 0.5;
|
|
673
|
+
cursor: default;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.reflib-browserInput {
|
|
677
|
+
flex: 1;
|
|
678
|
+
min-width: 0;
|
|
679
|
+
border: none;
|
|
680
|
+
outline: none;
|
|
681
|
+
background: transparent;
|
|
682
|
+
color: var(--dsw-alias-label-primary);
|
|
683
|
+
font-size: 12px;
|
|
684
|
+
line-height: 18px;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.reflib-browserInput::placeholder {
|
|
688
|
+
color: var(--dsw-alias-label-dimmed);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.reflib-browserList {
|
|
692
|
+
display: flex;
|
|
693
|
+
flex-direction: column;
|
|
694
|
+
gap: 2px;
|
|
695
|
+
max-height: 264px;
|
|
696
|
+
min-height: 60px;
|
|
697
|
+
overflow-y: auto;
|
|
698
|
+
margin: 0 -8px;
|
|
699
|
+
padding: 0 8px;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.reflib-browserRow {
|
|
703
|
+
display: flex;
|
|
704
|
+
align-items: center;
|
|
705
|
+
gap: 8px;
|
|
706
|
+
min-width: 0;
|
|
707
|
+
padding: 7px 8px;
|
|
708
|
+
border: none;
|
|
709
|
+
border-radius: 8px;
|
|
710
|
+
background: transparent;
|
|
711
|
+
color: var(--dsw-alias-label-primary);
|
|
712
|
+
font-size: 13px;
|
|
713
|
+
line-height: 18px;
|
|
714
|
+
cursor: pointer;
|
|
715
|
+
text-align: left;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.reflib-browserRow:hover:not(:disabled) {
|
|
719
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.reflib-browserRow:disabled {
|
|
723
|
+
opacity: 0.5;
|
|
724
|
+
cursor: default;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.reflib-browserRowIcon {
|
|
728
|
+
flex: none;
|
|
729
|
+
color: var(--dsw-alias-label-tertiary);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.reflib-browserRowName {
|
|
733
|
+
overflow: hidden;
|
|
734
|
+
text-overflow: ellipsis;
|
|
735
|
+
white-space: nowrap;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.reflib-browserFooter {
|
|
739
|
+
display: flex;
|
|
740
|
+
align-items: center;
|
|
741
|
+
justify-content: flex-end;
|
|
742
|
+
gap: 8px;
|
|
743
|
+
min-width: 0;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/* ── 管理面板 ── */
|
|
747
|
+
.reflib-panel {
|
|
748
|
+
display: flex;
|
|
749
|
+
flex-direction: column;
|
|
750
|
+
gap: 10px;
|
|
751
|
+
min-width: 0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/* 列表:固定最大高度 + 内滚动(旧实现整个面板滚动、无设计,此处分层) */
|
|
755
|
+
.reflib-list {
|
|
756
|
+
display: flex;
|
|
757
|
+
flex-direction: column;
|
|
758
|
+
gap: 2px;
|
|
759
|
+
max-height: 264px;
|
|
760
|
+
min-height: 0;
|
|
761
|
+
overflow-y: auto;
|
|
762
|
+
margin: 0 -8px;
|
|
763
|
+
padding: 0 8px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.reflib-row {
|
|
767
|
+
display: flex;
|
|
768
|
+
align-items: center;
|
|
769
|
+
gap: 8px;
|
|
770
|
+
min-width: 0;
|
|
771
|
+
padding: 7px 8px;
|
|
772
|
+
border-radius: 10px;
|
|
773
|
+
background: transparent;
|
|
774
|
+
transition: background-color 120ms ease;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.reflib-row:hover {
|
|
778
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.reflib-row[data-removing='true'] {
|
|
782
|
+
opacity: 0.55;
|
|
783
|
+
pointer-events: none;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.reflib-rowIcon {
|
|
787
|
+
display: inline-flex;
|
|
788
|
+
flex: none;
|
|
789
|
+
color: var(--dsw-alias-label-tertiary);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.reflib-rowBody {
|
|
793
|
+
display: flex;
|
|
794
|
+
flex: 1;
|
|
795
|
+
flex-direction: column;
|
|
796
|
+
gap: 1px;
|
|
797
|
+
min-width: 0;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.reflib-rowName {
|
|
801
|
+
overflow: hidden;
|
|
802
|
+
color: var(--dsw-alias-label-primary);
|
|
803
|
+
font-size: 13px;
|
|
804
|
+
line-height: 18px;
|
|
805
|
+
font-weight: 500;
|
|
806
|
+
text-overflow: ellipsis;
|
|
807
|
+
white-space: nowrap;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
.reflib-rowPath {
|
|
811
|
+
overflow: hidden;
|
|
812
|
+
color: var(--dsw-alias-label-tertiary);
|
|
813
|
+
font-family: var(--ds-font-family-code);
|
|
814
|
+
font-size: 11px;
|
|
815
|
+
line-height: 16px;
|
|
816
|
+
text-overflow: ellipsis;
|
|
817
|
+
white-space: nowrap;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.reflib-rowRemove {
|
|
821
|
+
display: inline-flex;
|
|
822
|
+
align-items: center;
|
|
823
|
+
justify-content: center;
|
|
824
|
+
flex: none;
|
|
825
|
+
width: 28px;
|
|
826
|
+
height: 28px;
|
|
827
|
+
padding: 0;
|
|
828
|
+
border: none;
|
|
829
|
+
border-radius: 8px;
|
|
830
|
+
background: transparent;
|
|
831
|
+
color: var(--dsw-alias-label-tertiary);
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
transition: background-color 120ms ease, color 120ms ease;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.reflib-rowRemove:hover:not(:disabled) {
|
|
837
|
+
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
838
|
+
color: var(--dsw-alias-state-error-primary);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.reflib-rowRemove:disabled {
|
|
842
|
+
opacity: 0.4;
|
|
843
|
+
cursor: default;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/* 空态 / 加载态 / 错误态 */
|
|
847
|
+
.reflib-status {
|
|
848
|
+
display: flex;
|
|
849
|
+
flex-direction: column;
|
|
850
|
+
align-items: center;
|
|
851
|
+
gap: 6px;
|
|
852
|
+
padding: 22px 8px;
|
|
853
|
+
color: var(--dsw-alias-label-caption);
|
|
854
|
+
text-align: center;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.reflib-statusIcon {
|
|
858
|
+
display: inline-flex;
|
|
859
|
+
color: var(--dsw-alias-label-tertiary);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.reflib-statusText {
|
|
863
|
+
font-size: 13px;
|
|
864
|
+
line-height: 18px;
|
|
865
|
+
font-weight: 500;
|
|
866
|
+
color: var(--dsw-alias-label-secondary);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.reflib-statusHint {
|
|
870
|
+
max-width: 260px;
|
|
871
|
+
font-size: 12px;
|
|
872
|
+
line-height: 18px;
|
|
873
|
+
color: var(--dsw-alias-label-caption);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.reflib-error {
|
|
877
|
+
display: flex;
|
|
878
|
+
align-items: flex-start;
|
|
879
|
+
gap: 6px;
|
|
880
|
+
padding: 6px 10px;
|
|
881
|
+
border-radius: 8px;
|
|
882
|
+
background: var(--dsw-alias-interactive-bg-hover-danger);
|
|
883
|
+
color: var(--dsw-alias-state-error-primary);
|
|
884
|
+
font-size: 12px;
|
|
885
|
+
line-height: 18px;
|
|
886
|
+
word-break: break-word;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.reflib-errorIcon {
|
|
890
|
+
display: inline-flex;
|
|
891
|
+
flex: none;
|
|
892
|
+
margin-top: 1px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.reflib-spin {
|
|
896
|
+
animation: reflib-spin 800ms linear infinite;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@keyframes reflib-spin {
|
|
900
|
+
to {
|
|
901
|
+
transform: rotate(360deg);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
.reflib-divider {
|
|
906
|
+
height: 1px;
|
|
907
|
+
margin: 2px 0;
|
|
908
|
+
background: var(--dsw-alias-border-l2);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/* ── 添加表单 ──
|
|
912
|
+
两组操作分离(优化点 2.1):手动输入路径 + 「添加」是一组(主路径);
|
|
913
|
+
「选择目录」是另一组备选方式,独占一行,避免并排造成困惑。 */
|
|
914
|
+
.reflib-add {
|
|
915
|
+
display: flex;
|
|
916
|
+
flex-direction: column;
|
|
917
|
+
gap: 6px;
|
|
918
|
+
min-width: 0;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.reflib-addLabel {
|
|
922
|
+
font-size: 12px;
|
|
923
|
+
line-height: 16px;
|
|
924
|
+
font-weight: 500;
|
|
925
|
+
color: var(--dsw-alias-label-secondary);
|
|
926
|
+
}
|
|
927
|
+
|
|
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
|
+
.reflib-addRow {
|
|
948
|
+
display: flex;
|
|
949
|
+
align-items: center;
|
|
950
|
+
gap: 8px;
|
|
951
|
+
min-width: 0;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.reflib-addInputWrap {
|
|
955
|
+
flex: 1 1 180px;
|
|
956
|
+
min-width: 0;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/* 输入字体与面板一致(13px,同列表行名),placeholder 不再比整体大;
|
|
960
|
+
文案已缩短保证完整显示 */
|
|
961
|
+
.reflib-addInputWrap > input {
|
|
962
|
+
width: 100%;
|
|
963
|
+
box-sizing: border-box;
|
|
964
|
+
font-size: 13px;
|
|
965
|
+
line-height: 18px;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.reflib-addHint {
|
|
969
|
+
font-size: 11px;
|
|
970
|
+
line-height: 16px;
|
|
971
|
+
color: var(--dsw-alias-label-caption);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/* 窄视口:主路径行换行,输入框占满一行 */
|
|
975
|
+
@media (max-width: 440px) {
|
|
976
|
+
.reflib-addRow {
|
|
977
|
+
flex-wrap: wrap;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.reflib-addInputWrap {
|
|
981
|
+
flex: 1 1 100%;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.reflib-addRow > button:last-child {
|
|
985
|
+
margin-left: auto;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
`;
|
|
989
|
+
/** 注入/确保样式表存在(幂等;HMR 重执行时旧标签已被 loader 移除,重新注入)。 */
|
|
990
|
+
function ensureRefLibStyles() {
|
|
991
|
+
if (typeof document === "undefined") return;
|
|
992
|
+
if (document.querySelector("style[data-plugin-css=\"@hpyperry/dsh-ref-lib/styles\"]") !== null) return;
|
|
993
|
+
const tag = document.createElement("style");
|
|
994
|
+
tag.dataset.plugin = PLUGIN_ID;
|
|
995
|
+
tag.dataset.pluginCss = STYLE_TAG_ID;
|
|
996
|
+
tag.textContent = CSS;
|
|
997
|
+
document.head.appendChild(tag);
|
|
998
|
+
}
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/client/RefLibDock.tsx
|
|
1001
|
+
/**
|
|
1002
|
+
* 参考库入口(conversation.input.dock,输入卡正上方)+ 面板状态持有者。
|
|
1003
|
+
*
|
|
1004
|
+
* 位置(v7):v5/v6 曾在 hero 相位把胶囊**测量定位**提入官方 hero 行(紧跟"标准
|
|
1005
|
+
* 模式"chip 之后)。该像素测量与 hero 行的异步内容存在竞态——模式 chip 的 roster
|
|
1006
|
+
* 经 RPC 异步挂载、Web 字体异步加载、其他插件 chip 异步加入——而重测触发面
|
|
1007
|
+
* (hero 行/槽位出口/栈均为固定盒或 display:contents)对"行内内容变化"是盲区,
|
|
1008
|
+
* 导致偶发与模式按钮重叠。v7 起**取消测量与绝对定位**:胶囊统一渲染在 dock 槽位
|
|
1009
|
+
* (输入卡正上方)的独立一行,左缘经官方设计令牌(--dsh-composer-side-clearance /
|
|
1010
|
+
* --dsh-composer-card-max-width)纯 CSS 与输入卡左缘对齐——hero/active 一致、
|
|
1011
|
+
* 零 JS 测量、零竞态;plan/model 等工具行座位的启停不影响本行位置;与其他 dock
|
|
1012
|
+
* 条带(todo/goal/queue)按 order 流式堆叠,天然不重叠。
|
|
1013
|
+
*
|
|
1014
|
+
* 数据(v4/v5):读/写经 /api/ref-lib/* 路由(静默、无命令卡片);
|
|
1015
|
+
* 目录选择走系统原生选择器,另提供路径输入直加(绕开卡顿的原生对话框)。
|
|
1016
|
+
* @module @hpyperry/dsh-ref-lib/src/client/RefLibDock
|
|
1017
|
+
*/
|
|
1018
|
+
/** wire 错误码 → 本地化文案键(未知码回退原始消息)。 */
|
|
1019
|
+
const ERROR_KEYS = {
|
|
1020
|
+
"ref-lib/missing": "error.missing",
|
|
1021
|
+
"ref-lib/not-directory": "error.notDirectory",
|
|
1022
|
+
"ref-lib/unsafe": "error.unsafe",
|
|
1023
|
+
"ref-lib/unknown-id": "error.unknownId"
|
|
1024
|
+
};
|
|
1025
|
+
/** 把未知错误规整为可展示文案。 */
|
|
1026
|
+
function messageOf(cause) {
|
|
1027
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* 把 API 错误本地化:已知 wire code 映射为当前语言文案(带 path/id 参数),
|
|
1031
|
+
* 其余错误原样展示服务端消息。
|
|
1032
|
+
*/
|
|
1033
|
+
function formatError(cause, t) {
|
|
1034
|
+
if (cause instanceof RefLibApiError) {
|
|
1035
|
+
const key = ERROR_KEYS[cause.code];
|
|
1036
|
+
if (key !== void 0) return t(key, cause.details.path !== void 0 ? { path: cause.details.path } : cause.details.id !== void 0 ? { id: cause.details.id } : void 0);
|
|
1037
|
+
}
|
|
1038
|
+
return messageOf(cause);
|
|
1039
|
+
}
|
|
1040
|
+
ensureRefLibStyles();
|
|
1041
|
+
/**
|
|
1042
|
+
* 输入卡正上方的参考库入口:胶囊按钮(图标 + 文案 + 数量徽标)唤起管理面板。
|
|
1043
|
+
* 位置由 .reflib-dock 的纯 CSS padding 与输入卡左缘对齐,无任何 JS 测量。
|
|
1044
|
+
* @param props - sessionId 与注入面。
|
|
1045
|
+
* @returns 胶囊入口(+ 打开时的管理面板)。
|
|
1046
|
+
*/
|
|
1047
|
+
function RefLibDock(props) {
|
|
1048
|
+
const { sessionId, load, add, remove, pickDirectory, listDirectory, t } = props;
|
|
1049
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1050
|
+
const [libs, setLibs] = (0, react.useState)([]);
|
|
1051
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
1052
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1053
|
+
const [picking, setPicking] = (0, react.useState)(false);
|
|
1054
|
+
const [removingId, setRemovingId] = (0, react.useState)(null);
|
|
1055
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1056
|
+
const seq = (0, react.useRef)(0);
|
|
1057
|
+
const [pickerMode, setPickerMode] = (0, react.useState)(null);
|
|
1058
|
+
const [browserOpen, setBrowserOpen] = (0, react.useState)(false);
|
|
1059
|
+
const detectPicker = async () => {
|
|
1060
|
+
if (pickerMode !== null) return pickerMode;
|
|
1061
|
+
let mode;
|
|
1062
|
+
try {
|
|
1063
|
+
await listDirectory(void 0, new AbortController().signal);
|
|
1064
|
+
mode = "browse";
|
|
1065
|
+
} catch {
|
|
1066
|
+
mode = "native";
|
|
1067
|
+
}
|
|
1068
|
+
setPickerMode(mode);
|
|
1069
|
+
return mode;
|
|
1070
|
+
};
|
|
1071
|
+
const refresh = async () => {
|
|
1072
|
+
const mine = ++seq.current;
|
|
1073
|
+
try {
|
|
1074
|
+
const next = await load(sessionId);
|
|
1075
|
+
if (mine !== seq.current) return;
|
|
1076
|
+
setLibs(next);
|
|
1077
|
+
setError(null);
|
|
1078
|
+
} catch (cause) {
|
|
1079
|
+
if (mine !== seq.current) return;
|
|
1080
|
+
setError(formatError(cause, t));
|
|
1081
|
+
} finally {
|
|
1082
|
+
if (mine === seq.current) setLoading(false);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
(0, react.useEffect)(() => {
|
|
1086
|
+
ensureRefLibStyles();
|
|
1087
|
+
refresh();
|
|
1088
|
+
}, [sessionId]);
|
|
1089
|
+
(0, react.useEffect)(() => {
|
|
1090
|
+
if (open) {
|
|
1091
|
+
setLoading(true);
|
|
1092
|
+
refresh();
|
|
1093
|
+
}
|
|
1094
|
+
}, [open]);
|
|
1095
|
+
const handleRemove = async (id) => {
|
|
1096
|
+
setRemovingId(id);
|
|
1097
|
+
setBusy(true);
|
|
1098
|
+
setError(null);
|
|
1099
|
+
try {
|
|
1100
|
+
await remove(sessionId, id);
|
|
1101
|
+
await refresh();
|
|
1102
|
+
} catch (cause) {
|
|
1103
|
+
setError(formatError(cause, t));
|
|
1104
|
+
} finally {
|
|
1105
|
+
setRemovingId(null);
|
|
1106
|
+
setBusy(false);
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
const handleAddPath = async (path) => {
|
|
1110
|
+
setBusy(true);
|
|
1111
|
+
setError(null);
|
|
1112
|
+
try {
|
|
1113
|
+
await add(sessionId, path);
|
|
1114
|
+
await refresh();
|
|
1115
|
+
} catch (cause) {
|
|
1116
|
+
setError(formatError(cause, t));
|
|
1117
|
+
throw cause;
|
|
1118
|
+
} finally {
|
|
1119
|
+
setBusy(false);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
/** 添加一个已选定目录并刷新列表;返回是否成功(失败时错误已入错误槽,不抛出)。 */
|
|
1123
|
+
const addPath = async (path) => {
|
|
1124
|
+
setBusy(true);
|
|
1125
|
+
setError(null);
|
|
1126
|
+
try {
|
|
1127
|
+
await add(sessionId, path);
|
|
1128
|
+
await refresh();
|
|
1129
|
+
return true;
|
|
1130
|
+
} catch (cause) {
|
|
1131
|
+
setError(formatError(cause, t));
|
|
1132
|
+
return false;
|
|
1133
|
+
} finally {
|
|
1134
|
+
setBusy(false);
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
const handleBrowse = async () => {
|
|
1138
|
+
setError(null);
|
|
1139
|
+
let mode;
|
|
1140
|
+
try {
|
|
1141
|
+
mode = await detectPicker();
|
|
1142
|
+
} catch (cause) {
|
|
1143
|
+
setError(formatError(cause, t));
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
if (mode === "browse") {
|
|
1147
|
+
setBrowserOpen(true);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
setPicking(true);
|
|
1151
|
+
try {
|
|
1152
|
+
const picked = await pickDirectory();
|
|
1153
|
+
if (picked !== null) await addPath(picked);
|
|
1154
|
+
} catch (cause) {
|
|
1155
|
+
setError(formatError(cause, t));
|
|
1156
|
+
} finally {
|
|
1157
|
+
setPicking(false);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
/** 应用内浏览器选定目录 → 添加成功才关闭(失败时错误显示在面板,浏览器保持打开便于重试)。 */
|
|
1161
|
+
const handleBrowserPick = async (path) => {
|
|
1162
|
+
if (await addPath(path)) setBrowserOpen(false);
|
|
1163
|
+
};
|
|
1164
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1165
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1166
|
+
className: "reflib-dock",
|
|
1167
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1168
|
+
type: "button",
|
|
1169
|
+
className: "reflib-chip",
|
|
1170
|
+
"data-active": open || void 0,
|
|
1171
|
+
"aria-haspopup": "dialog",
|
|
1172
|
+
"aria-expanded": open,
|
|
1173
|
+
"aria-label": libs.length > 0 ? t("dock.count.aria", { count: String(libs.length) }) : t("dock.aria"),
|
|
1174
|
+
title: t("dock.aria"),
|
|
1175
|
+
onClick: () => {
|
|
1176
|
+
setOpen((value) => !value);
|
|
1177
|
+
},
|
|
1178
|
+
children: [
|
|
1179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1180
|
+
className: "reflib-chipIcon",
|
|
1181
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, { size: 14 })
|
|
1182
|
+
}),
|
|
1183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1184
|
+
className: "reflib-chipLabel",
|
|
1185
|
+
children: t("dock.label")
|
|
1186
|
+
}),
|
|
1187
|
+
libs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1188
|
+
className: "reflib-chipBadge",
|
|
1189
|
+
children: libs.length
|
|
1190
|
+
})
|
|
1191
|
+
]
|
|
1192
|
+
})
|
|
1193
|
+
}),
|
|
1194
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefLibPanel, {
|
|
1195
|
+
open,
|
|
1196
|
+
onClose: () => {
|
|
1197
|
+
setOpen(false);
|
|
1198
|
+
},
|
|
1199
|
+
libs,
|
|
1200
|
+
loading,
|
|
1201
|
+
busy,
|
|
1202
|
+
picking,
|
|
1203
|
+
removingId,
|
|
1204
|
+
error,
|
|
1205
|
+
t,
|
|
1206
|
+
onRemove: (id) => {
|
|
1207
|
+
handleRemove(id);
|
|
1208
|
+
},
|
|
1209
|
+
onAddPath: handleAddPath,
|
|
1210
|
+
onBrowse: handleBrowse
|
|
1211
|
+
}),
|
|
1212
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefLibBrowser, {
|
|
1213
|
+
open: browserOpen,
|
|
1214
|
+
onClose: () => {
|
|
1215
|
+
setBrowserOpen(false);
|
|
1216
|
+
},
|
|
1217
|
+
listDirectory,
|
|
1218
|
+
t,
|
|
1219
|
+
onOpen: (path) => {
|
|
1220
|
+
handleBrowserPick(path);
|
|
1221
|
+
}
|
|
1222
|
+
})
|
|
1223
|
+
] });
|
|
1224
|
+
}
|
|
1225
|
+
//#endregion
|
|
1226
|
+
//#region src/client/locales.ts
|
|
1227
|
+
/**
|
|
1228
|
+
* ref-lib client 本地化字典(zh 为键集唯一来源,en 与之逐键对齐)。
|
|
1229
|
+
*
|
|
1230
|
+
* 语言适配(优化点 2):面板、入口胶囊与添加表单的全部文案经
|
|
1231
|
+
* `ctx.locale.register(NS, { zh, en })` 注册,槽位注册声明 `locale: NS`,
|
|
1232
|
+
* 组件经框架注入的 `t` seat 取词(活动语言实时切换,缺省回退 zh)。
|
|
1233
|
+
* 键对等由 `satisfies Record<RefLibKey, string>` 在编译期强制。
|
|
1234
|
+
* @module @hpyperry/dsh-ref-lib/src/client/locales
|
|
1235
|
+
*/
|
|
1236
|
+
/** 简体中文字典(键集唯一来源)。 */
|
|
1237
|
+
const zh = {
|
|
1238
|
+
"dock.label": "参考库",
|
|
1239
|
+
"dock.aria": "管理只读参考库",
|
|
1240
|
+
"dock.count.aria": "参考库,共 {count} 个",
|
|
1241
|
+
"panel.title": "参考库",
|
|
1242
|
+
"panel.close": "关闭",
|
|
1243
|
+
"panel.description": "共 {count} 个只读参考库:供 agent 读取参考,禁止修改其中文件",
|
|
1244
|
+
"list.remove": "移除",
|
|
1245
|
+
"list.remove.aria": "移除参考库 {name}",
|
|
1246
|
+
"list.empty": "还没有参考库",
|
|
1247
|
+
"list.empty.hint": "在下方输入目录路径,或点击「选择目录」从系统选择",
|
|
1248
|
+
"list.loading": "正在加载…",
|
|
1249
|
+
"add.label": "添加参考库",
|
|
1250
|
+
"add.placeholder": "输入目录路径(支持 ~ 与相对)",
|
|
1251
|
+
"add.submit": "添加",
|
|
1252
|
+
"add.manual": "或手动输入路径",
|
|
1253
|
+
"add.browse": "选择目录",
|
|
1254
|
+
"add.hint": "目录仅作只读参考;位于工作区之外时由系统强制只读",
|
|
1255
|
+
"browser.title": "选择目录",
|
|
1256
|
+
"browser.home": "主目录",
|
|
1257
|
+
"browser.open": "选择此目录",
|
|
1258
|
+
"browser.open.aria": "添加当前目录为参考库",
|
|
1259
|
+
"browser.enter.aria": "进入目录 {name}",
|
|
1260
|
+
"browser.editPath": "编辑路径",
|
|
1261
|
+
"browser.pathPlaceholder": "输入绝对路径,回车跳转",
|
|
1262
|
+
"browser.empty": "该目录下没有子目录",
|
|
1263
|
+
"browser.loading": "加载中…",
|
|
1264
|
+
"browser.cancel": "取消",
|
|
1265
|
+
"error.missing": "目录不存在:{path}",
|
|
1266
|
+
"error.notDirectory": "不是目录:{path}",
|
|
1267
|
+
"error.unsafe": "路径包含控制字符,已拒绝:{path}",
|
|
1268
|
+
"error.unknownId": "未找到参考库条目:{id}"
|
|
1269
|
+
};
|
|
1270
|
+
/** 英文词典,与 zh 键集逐键对齐(编译期校验)。 */
|
|
1271
|
+
const en = {
|
|
1272
|
+
"dock.label": "Reference Library",
|
|
1273
|
+
"dock.aria": "Manage read-only reference libraries",
|
|
1274
|
+
"dock.count.aria": "Reference library, {count} total",
|
|
1275
|
+
"panel.title": "Reference Library",
|
|
1276
|
+
"panel.close": "Close",
|
|
1277
|
+
"panel.description": "{count} read-only reference libraries: the agent may read them for reference, but must not modify any files",
|
|
1278
|
+
"list.remove": "Remove",
|
|
1279
|
+
"list.remove.aria": "Remove reference library {name}",
|
|
1280
|
+
"list.empty": "No reference libraries yet",
|
|
1281
|
+
"list.empty.hint": "Enter a directory path below, or click “Choose directory” to pick one",
|
|
1282
|
+
"list.loading": "Loading…",
|
|
1283
|
+
"add.label": "Add a reference library",
|
|
1284
|
+
"add.placeholder": "Enter a directory path (~ or relative)",
|
|
1285
|
+
"add.submit": "Add",
|
|
1286
|
+
"add.manual": "or enter a path manually",
|
|
1287
|
+
"add.browse": "Choose directory",
|
|
1288
|
+
"add.hint": "Directories are read-only references; outside the workspace they are enforced read-only by the system",
|
|
1289
|
+
"browser.title": "Select Directory",
|
|
1290
|
+
"browser.home": "Home",
|
|
1291
|
+
"browser.open": "Select this folder",
|
|
1292
|
+
"browser.open.aria": "Add the current directory as a reference library",
|
|
1293
|
+
"browser.enter.aria": "Open folder {name}",
|
|
1294
|
+
"browser.editPath": "Edit path",
|
|
1295
|
+
"browser.pathPlaceholder": "Type an absolute path and press Enter",
|
|
1296
|
+
"browser.empty": "No subdirectories here",
|
|
1297
|
+
"browser.loading": "Loading…",
|
|
1298
|
+
"browser.cancel": "Cancel",
|
|
1299
|
+
"error.missing": "Directory does not exist: {path}",
|
|
1300
|
+
"error.notDirectory": "Not a directory: {path}",
|
|
1301
|
+
"error.unsafe": "Path contains control characters and was rejected: {path}",
|
|
1302
|
+
"error.unknownId": "Reference library entry not found: {id}"
|
|
1303
|
+
};
|
|
1304
|
+
//#endregion
|
|
1305
|
+
//#region src/client/index.ts
|
|
1306
|
+
/** 所需服务(cordis fiber inject)。 */
|
|
1307
|
+
const inject = [
|
|
1308
|
+
"slots",
|
|
1309
|
+
"workspaces",
|
|
1310
|
+
"locale"
|
|
1311
|
+
];
|
|
1312
|
+
/** 本插件文案命名空间。 */
|
|
1313
|
+
const NS = "ref-lib";
|
|
1314
|
+
/**
|
|
1315
|
+
* 一条对宿主 /api/ref-lib 路由族的 JSON 调用;失败时优先抛出带 wire code 的
|
|
1316
|
+
* RefLibApiError(用于本地化),无 code 时回退普通 Error。
|
|
1317
|
+
*/
|
|
1318
|
+
async function api(path, payload, method = "POST") {
|
|
1319
|
+
const res = await fetch("/api/ref-lib" + path, payload === void 0 ? void 0 : {
|
|
1320
|
+
method,
|
|
1321
|
+
headers: { "content-type": "application/json" },
|
|
1322
|
+
body: JSON.stringify(payload)
|
|
1323
|
+
});
|
|
1324
|
+
let body = null;
|
|
1325
|
+
try {
|
|
1326
|
+
body = await res.json();
|
|
1327
|
+
} catch {}
|
|
1328
|
+
if (!res.ok) {
|
|
1329
|
+
const parsed = parseApiErrorPayload(body);
|
|
1330
|
+
if (parsed !== null) throw new RefLibApiError(parsed.code, parsed.message, parsed.details);
|
|
1331
|
+
const message = body?.error ?? "HTTP " + res.status;
|
|
1332
|
+
throw new Error(typeof message === "string" ? message : String(message));
|
|
1333
|
+
}
|
|
1334
|
+
return body;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* 注册输入框上方的「参考库」入口与管理面板。
|
|
1338
|
+
* @param ctx - client 根上下文。
|
|
1339
|
+
*/
|
|
1340
|
+
function apply(ctx) {
|
|
1341
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1342
|
+
zh,
|
|
1343
|
+
en
|
|
1344
|
+
}), "ref-lib: dictionaries");
|
|
1345
|
+
const injected = () => ({
|
|
1346
|
+
load: async (sessionId) => parseLibsPayload(await api(`/list?session=${encodeURIComponent(sessionId)}`, void 0, "GET")),
|
|
1347
|
+
add: async (sessionId, path) => {
|
|
1348
|
+
await api("/add", {
|
|
1349
|
+
session: sessionId,
|
|
1350
|
+
path
|
|
1351
|
+
});
|
|
1352
|
+
},
|
|
1353
|
+
remove: async (sessionId, id) => {
|
|
1354
|
+
await api("/remove", {
|
|
1355
|
+
session: sessionId,
|
|
1356
|
+
id
|
|
1357
|
+
});
|
|
1358
|
+
},
|
|
1359
|
+
pickDirectory: () => ctx.workspaces.pickDirectory(),
|
|
1360
|
+
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal)
|
|
1361
|
+
});
|
|
1362
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
1363
|
+
name: "conversation.input.dock",
|
|
1364
|
+
id: "ref-lib",
|
|
1365
|
+
order: 200,
|
|
1366
|
+
locale: NS,
|
|
1367
|
+
inject: injected
|
|
1368
|
+
}, RefLibDock));
|
|
1369
|
+
}
|
|
1370
|
+
//#endregion
|
|
1371
|
+
exports.apply = apply;
|
|
1372
|
+
exports.inject = inject;
|
|
1373
|
+
return module.exports;
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
//# sourceMappingURL=client.js.map
|