@dungsil/dsh-plugin-ui-setting-skills 0.3.1 → 0.4.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/dist/client.js CHANGED
@@ -13,12 +13,13 @@ function installStyles(css) {
13
13
  return () => style.remove();
14
14
  }
15
15
  // ../../tools/ui/src/settings.tsx
16
- /** The rc.5 settings shell has no icon slot, so decorate only the matching navigation rows. */
16
+ /** Decorate matching navigation rows in both the rc.5 and 0.2.2 settings shells. */
17
17
  function installSettingsIcon(id, labels, icon) {
18
18
  const attribute = "data-dsh-settings-icon";
19
19
  const selector = `[${attribute}="${id}"]`;
20
20
  const removeStyles = installStyles(`
21
- ${selector} > svg { display: none; }
21
+ ${selector} > svg,
22
+ ${selector} > [aria-hidden="true"]:not([data-dsh-settings-icon-glyph]) { display: none; }
22
23
  ${typeof icon === "string" ? `${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(icon)}") center / contain no-repeat; }` : ""}
23
24
  ${selector} > [data-dsh-settings-icon-glyph] { display: inline-flex; flex: none; }
24
25
  `);
@@ -76,6 +77,9 @@ var en_default = {
76
77
  "dsh": "DSH skills",
77
78
  "loading": "Loading skills…",
78
79
  "empty": "No loaded skills from this source.",
80
+ "search": "Search skills",
81
+ "searchPlaceholder": "Search by name, description, or path",
82
+ "noResults": "No skills match your search.",
79
83
  "error": "Could not load skills. Close and reopen this settings section to try again.",
80
84
  "incomplete": "Some skill sources could not be loaded. This list may be incomplete.",
81
85
  "use": "Use",
@@ -89,7 +93,10 @@ var en_default = {
89
93
  "original": "View original",
90
94
  "retry": "Retry",
91
95
  "translating": "Translating…",
92
- "translationNotice": "Expanding a skill automatically sends its description to the service selected in reasoning translation settings. Translations are cached using its target language.",
96
+ "translateAll": "Translate all skills",
97
+ "translatingAll": "Translating all skills…",
98
+ "translationAllError": "Some skill descriptions could not be translated. Try again to translate only the failed items.",
99
+ "translationNotice": "Expanding a skill or translating all skills sends descriptions to the service selected in reasoning translation settings. Translate all includes both shared and DSH skills. Translations are cached using its target language.",
93
100
  "translationError": "Could not translate. The original is shown. Paid services require a Session and are unavailable here.",
94
101
  "saveError": "Could not save the setting. Close and reopen this section to check the saved state before trying again."
95
102
  }
@@ -107,6 +114,9 @@ var ko_default = {
107
114
  "dsh": "DSH 스킬",
108
115
  "loading": "스킬을 불러오는 중입니다…",
109
116
  "empty": "이 경로에서 로드된 스킬이 없습니다.",
117
+ "search": "스킬 검색",
118
+ "searchPlaceholder": "이름, 설명 또는 경로로 검색",
119
+ "noResults": "검색 조건에 맞는 스킬이 없습니다.",
110
120
  "error": "스킬을 불러오지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 주세요.",
111
121
  "incomplete": "일부 스킬 소스를 불러오지 못해 목록이 완전하지 않을 수 있습니다.",
112
122
  "use": "사용",
@@ -120,7 +130,10 @@ var ko_default = {
120
130
  "original": "원문 보기",
121
131
  "retry": "다시 시도",
122
132
  "translating": "번역 중…",
123
- "translationNotice": "스킬을 펼치면 사고 과정 번역 설정에서 선택한 서비스로 설명을 자동 전송합니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
133
+ "translateAll": "전체 스킬 번역",
134
+ "translatingAll": "전체 스킬 번역 중…",
135
+ "translationAllError": "일부 스킬 설명을 번역하지 못했습니다. 다시 시도하면 실패한 항목만 번역합니다.",
136
+ "translationNotice": "스킬을 펼치거나 전체 스킬 번역을 누르면 사고 과정 번역 설정에서 선택한 서비스로 설명을 전송합니다. 전체 번역에는 공용 스킬과 DSH 스킬이 모두 포함됩니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
124
137
  "translationError": "번역하지 못해 원문을 표시합니다. 유료 서비스는 세션이 필요하므로 이 화면에서 사용할 수 없습니다.",
125
138
  "saveError": "설정을 저장하지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 후 다시 시도해 주세요."
126
139
  }
@@ -138,6 +151,9 @@ var zh_default = {
138
151
  "dsh": "DSH 技能",
139
152
  "loading": "正在加载技能…",
140
153
  "empty": "未从此目录加载任何技能。",
154
+ "search": "搜索技能",
155
+ "searchPlaceholder": "按名称、描述或路径搜索",
156
+ "noResults": "没有符合搜索条件的技能。",
141
157
  "error": "无法加载技能。请关闭并重新打开此设置项后重试。",
142
158
  "incomplete": "部分技能源无法加载,列表可能不完整。",
143
159
  "use": "使用",
@@ -151,7 +167,10 @@ var zh_default = {
151
167
  "original": "查看原文",
152
168
  "retry": "重试",
153
169
  "translating": "正在翻译…",
154
- "translationNotice": "展开技能时,会自动将描述发送至思考过程翻译设置中选择的服务。译文按该设置的目标语言缓存。",
170
+ "translateAll": "翻译全部技能",
171
+ "translatingAll": "正在翻译全部技能…",
172
+ "translationAllError": "部分技能描述翻译失败。重试时仅翻译失败的项目。",
173
+ "translationNotice": "展开技能或点击翻译全部技能时,会将描述发送至思考过程翻译设置中选择的服务。全部翻译包括共享技能和 DSH 技能。译文按该设置的目标语言缓存。",
155
174
  "translationError": "翻译失败,已显示原文。付费服务需要会话,无法在此页面使用。",
156
175
  "saveError": "无法保存设置。请关闭并重新打开此设置项,确认已保存的状态后重试。"
157
176
  }
@@ -173,6 +192,7 @@ const styles = `
173
192
  .dsh-skills-settings { container-type: inline-size; color: var(--dsw-alias-label-primary); font: 14px/22px var(--dsw-font-family, system-ui); }
174
193
  .dsh-skills-settings p { margin: 4px 0; }
175
194
  .dsh-skills-settings-tabs { margin: 20px 0 12px; }
195
+ .dsh-skills-search { width: 100%; box-sizing: border-box; margin-bottom: 12px; }
176
196
  .dsh-skills-settings .dsh-skills-muted { color: var(--dsw-alias-label-secondary); }
177
197
  .dsh-skills-settings ul { list-style: none; margin: 0; padding: 0; }
178
198
  .dsh-skills-settings code { font-size: 12px; overflow-wrap: anywhere; }
@@ -196,52 +216,29 @@ const styles = `
196
216
  .dsh-skills-open-error { grid-column: 1 / -1; padding: 0 0 16px 28px; }
197
217
  .dsh-skills-list-header { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
198
218
  `;
199
- function SkillDescription({ skill, connection, language, available, expanded }) {
219
+ const translationKey = (skill, language) => JSON.stringify([
220
+ language,
221
+ skill.path,
222
+ skill.description
223
+ ]);
224
+ function SkillDescription({ skill, language, available, expanded, translation, translate }) {
200
225
  const text = localizedCopy(language);
201
- const [translated, setTranslated] = (0, react.useState)();
202
226
  const [original, setOriginal] = (0, react.useState)(false);
203
- const [pending, setPending] = (0, react.useState)(false);
204
- const [failed, setFailed] = (0, react.useState)(false);
205
- const [retry, setRetry] = (0, react.useState)(0);
227
+ const { value: translated, pending, failed } = translation ?? {};
206
228
  (0, react.useEffect)(() => {
207
- setTranslated(void 0);
208
- setOriginal(false);
209
- setPending(false);
210
- setFailed(false);
211
- if (!expanded || !available || !skill.path || !skill.description) return;
212
- const request = new AbortController();
213
- setPending(true);
214
- (async () => {
215
- try {
216
- const result = await connection.rpc.call(channel, translateMethod, {
217
- path: skill.path,
218
- language
219
- }, request.signal);
220
- if (request.signal.aborted) return;
221
- if (!result.ok || typeof result.value !== "string") throw new Error("Could not translate description");
222
- setTranslated(result.value);
223
- } catch {
224
- if (!request.signal.aborted) setFailed(true);
225
- } finally {
226
- if (!request.signal.aborted) setPending(false);
227
- }
228
- })();
229
- return () => request.abort();
229
+ if (expanded && available) translate(skill);
230
230
  }, [
231
231
  expanded,
232
- skill.description,
233
- skill.path,
234
- language,
235
232
  available,
236
- connection,
237
- retry
233
+ skill,
234
+ translate
238
235
  ]);
239
236
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: original || translated === void 0 ? skill.description : translated }), available && skill.path && skill.description && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
240
237
  pending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
241
238
  role: "status",
242
239
  children: text.translating
243
240
  }),
244
- translated !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
241
+ translated !== void 0 && translated !== skill.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
245
242
  variant: "ghost",
246
243
  size: "sm",
247
244
  onClick: () => setOriginal((value) => !value),
@@ -253,7 +250,7 @@ function SkillDescription({ skill, connection, language, available, expanded })
253
250
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
254
251
  variant: "ghost",
255
252
  size: "sm",
256
- onClick: () => setRetry((value) => value + 1),
253
+ onClick: () => void translate(skill),
257
254
  children: text.retry
258
255
  })] })
259
256
  ] })] });
@@ -273,11 +270,89 @@ function SkillsSettings({ ctx }) {
273
270
  const mounted = (0, react.useRef)(false);
274
271
  const [expanded, setExpanded] = (0, react.useState)({});
275
272
  const [source, setSource] = (0, react.useState)("user-agents");
276
- const groups = ["user-agents", "user-dsh"].map((value) => ({
277
- value,
278
- label: value === "user-agents" ? text.agents : text.dsh,
279
- skills: view?.skills.filter((skill) => skill.source === value) ?? []
280
- }));
273
+ const [query, setQuery] = (0, react.useState)("");
274
+ const [, refreshTranslations] = (0, react.useState)(0);
275
+ const translations = (0, react.useMemo)(() => ({
276
+ entries: /* @__PURE__ */ new Map(),
277
+ requests: /* @__PURE__ */ new Map(),
278
+ controllers: /* @__PURE__ */ new Set(),
279
+ bulk: false,
280
+ active: true
281
+ }), [connection, locale.active]);
282
+ (0, react.useEffect)(() => {
283
+ translations.active = true;
284
+ return () => {
285
+ translations.active = false;
286
+ for (const controller of translations.controllers) controller.abort();
287
+ };
288
+ }, [translations]);
289
+ const translate = (0, react.useCallback)((skill) => {
290
+ if (!skill.path || !skill.description) return Promise.resolve();
291
+ const key = translationKey(skill, locale.active);
292
+ const existing = translations.requests.get(key);
293
+ if (existing) return existing;
294
+ if (translations.entries.get(key)?.value !== void 0) return Promise.resolve();
295
+ const controller = new AbortController();
296
+ translations.controllers.add(controller);
297
+ translations.entries.set(key, { pending: true });
298
+ refreshTranslations((value) => value + 1);
299
+ const request = (async () => {
300
+ try {
301
+ const result = await connection.rpc.call(channel, translateMethod, {
302
+ path: skill.path,
303
+ language: locale.active
304
+ }, controller.signal);
305
+ if (!result.ok || typeof result.value !== "string") throw new Error("Could not translate description");
306
+ if (!controller.signal.aborted) translations.entries.set(key, { value: result.value });
307
+ } catch {
308
+ if (!controller.signal.aborted) translations.entries.set(key, { failed: true });
309
+ } finally {
310
+ translations.requests.delete(key);
311
+ translations.controllers.delete(controller);
312
+ if (translations.active && !controller.signal.aborted) refreshTranslations((value) => value + 1);
313
+ }
314
+ })();
315
+ translations.requests.set(key, request);
316
+ return request;
317
+ }, [
318
+ connection,
319
+ locale.active,
320
+ translations
321
+ ]);
322
+ async function translateAll() {
323
+ if (translations.bulk || !view?.translationAvailable) return;
324
+ translations.bulk = true;
325
+ refreshTranslations((value) => value + 1);
326
+ try {
327
+ for (const skill of view.skills) {
328
+ if (!translations.active) break;
329
+ await translate(skill);
330
+ }
331
+ } finally {
332
+ translations.bulk = false;
333
+ if (translations.active) refreshTranslations((value) => value + 1);
334
+ }
335
+ }
336
+ const normalizeSearch = (value) => value.normalize("NFKC").toLowerCase();
337
+ const searchTerms = normalizeSearch(query).trim().split(/\s+/u).filter(Boolean);
338
+ const groups = ["user-agents", "user-dsh"].map((value) => {
339
+ const skills = view?.skills.filter((skill) => skill.source === value) ?? [];
340
+ return {
341
+ value,
342
+ label: value === "user-agents" ? text.agents : text.dsh,
343
+ total: skills.length,
344
+ skills: skills.filter((skill) => {
345
+ const fields = [
346
+ skill.name,
347
+ skill.description,
348
+ skill.path ?? "",
349
+ translations.entries.get(translationKey(skill, locale.active))?.value ?? ""
350
+ ].map(normalizeSearch);
351
+ return searchTerms.every((term) => fields.some((field) => field.includes(term)));
352
+ })
353
+ };
354
+ });
355
+ const groupCount = (group) => searchTerms.length ? `${group.skills.length}/${group.total}` : `${group.total}`;
281
356
  (0, react.useEffect)(() => {
282
357
  const controller = new AbortController();
283
358
  mounted.current = true;
@@ -356,9 +431,30 @@ function SkillsSettings({ ctx }) {
356
431
  className: "dsh-skills-muted",
357
432
  children: text.openFolderUnavailable
358
433
  }),
359
- view?.translationAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
360
- className: "dsh-skills-muted",
361
- children: text.translationNotice
434
+ view?.translationAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
435
+ className: "dsh-skills-translation-toolbar",
436
+ children: [
437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
438
+ className: "dsh-skills-muted",
439
+ children: text.translationNotice
440
+ }),
441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
442
+ variant: "ghost",
443
+ size: "sm",
444
+ disabled: translations.bulk || !view.skills.some((skill) => skill.path && skill.description),
445
+ "aria-busy": translations.bulk,
446
+ onClick: () => void translateAll(),
447
+ children: translations.bulk ? text.translatingAll : text.translateAll
448
+ }),
449
+ translations.bulk && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
450
+ role: "status",
451
+ children: text.translatingAll
452
+ }),
453
+ !translations.bulk && [...translations.entries.values()].some((entry) => entry.failed) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
454
+ role: "alert",
455
+ children: text.translationAllError
456
+ })
457
+ ]
362
458
  }),
363
459
  !view && !error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
364
460
  role: "status",
@@ -376,112 +472,126 @@ function SkillsSettings({ ctx }) {
376
472
  role: "alert",
377
473
  children: text.incomplete
378
474
  }),
379
- view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
380
- id,
381
- className: "dsh-skills-settings-tabs",
382
- value: source,
383
- options: groups.map((group) => ({
384
- value: group.value,
385
- label: `${group.label} ${group.skills.length}`
386
- })),
387
- onChange: setSource,
388
- label: text.nav
389
- }), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
390
- id: `${id}-${group.value}-panel`,
391
- role: "tabpanel",
392
- "aria-labelledby": `${id}-${group.value}`,
393
- hidden: source !== group.value,
394
- tabIndex: 0,
395
- children: group.skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
396
- className: "dsh-skills-muted",
397
- children: text.empty
398
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
399
- className: "dsh-skills-list-header dsh-skills-muted",
400
- "aria-hidden": "true",
401
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
402
- text.nav,
403
- " ",
404
- group.skills.length
405
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.use })]
406
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
407
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
408
- onToggle: (event) => {
409
- const open = event.currentTarget.open;
410
- setExpanded((current) => ({
411
- ...current,
412
- [skill.path ?? skill.name]: open
413
- }));
414
- },
415
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
416
- "aria-label": skill.name,
417
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
418
- size: 16,
419
- "aria-hidden": "true"
420
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
421
- className: "dsh-skills-summary-text",
422
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
423
- className: "dsh-skills-title",
424
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: skill.name }), view.disabled.includes(skill.name) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
425
- className: "dsh-skills-muted",
426
- children: text.disabled
475
+ view && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
476
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
477
+ id,
478
+ className: "dsh-skills-settings-tabs",
479
+ value: source,
480
+ options: groups.map((group) => ({
481
+ value: group.value,
482
+ label: `${group.label} ${groupCount(group)}`
483
+ })),
484
+ onChange: setSource,
485
+ label: text.nav
486
+ }),
487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
488
+ className: "dsh-skills-search",
489
+ type: "search",
490
+ value: query,
491
+ "aria-label": text.search,
492
+ placeholder: text.searchPlaceholder,
493
+ autoComplete: "off",
494
+ onChange: (event) => setQuery(event.target.value)
495
+ }),
496
+ groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
497
+ id: `${id}-${group.value}-panel`,
498
+ role: "tabpanel",
499
+ "aria-labelledby": `${id}-${group.value}`,
500
+ hidden: source !== group.value,
501
+ tabIndex: 0,
502
+ children: group.skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
503
+ className: "dsh-skills-muted",
504
+ children: group.total > 0 && searchTerms.length ? text.noResults : text.empty
505
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
506
+ className: "dsh-skills-list-header dsh-skills-muted",
507
+ "aria-hidden": "true",
508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
509
+ text.nav,
510
+ " ",
511
+ groupCount(group)
512
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.use })]
513
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
515
+ open: expanded[skill.path ?? skill.name] ?? false,
516
+ onToggle: (event) => {
517
+ const open = event.currentTarget.open;
518
+ setExpanded((current) => ({
519
+ ...current,
520
+ [skill.path ?? skill.name]: open
521
+ }));
522
+ },
523
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
524
+ "aria-label": skill.name,
525
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
526
+ size: 16,
527
+ "aria-hidden": "true"
528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
529
+ className: "dsh-skills-summary-text",
530
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
531
+ className: "dsh-skills-title",
532
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: skill.name }), view.disabled.includes(skill.name) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
533
+ className: "dsh-skills-muted",
534
+ children: text.disabled
535
+ })]
536
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
537
+ className: "dsh-skills-preview dsh-skills-muted",
538
+ "aria-hidden": "true",
539
+ children: translations.entries.get(translationKey(skill, locale.active))?.value ?? skill.description
427
540
  })]
428
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
429
- className: "dsh-skills-preview dsh-skills-muted",
430
- "aria-hidden": "true",
431
- children: skill.description
541
+ })]
542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
543
+ className: "dsh-skills-detail",
544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDescription, {
545
+ skill,
546
+ translation: translations.entries.get(translationKey(skill, locale.active)),
547
+ translate,
548
+ language: locale.active,
549
+ available: view.translationAvailable && source === group.value,
550
+ expanded: expanded[skill.path ?? skill.name] ?? false
551
+ }, translationKey(skill, locale.active)), skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
552
+ className: "dsh-skills-muted",
553
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: skill.path })
432
554
  })]
433
555
  })]
434
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
435
- className: "dsh-skills-detail",
436
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDescription, {
437
- skill,
438
- connection,
439
- language: locale.active,
440
- available: view.translationAvailable && source === group.value,
441
- expanded: expanded[skill.path ?? skill.name] ?? false
442
- }), skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
443
- className: "dsh-skills-muted",
444
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: skill.path })
445
- })]
446
- })]
447
- }),
448
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
449
- className: "dsh-skills-actions",
450
- children: [skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
451
- label: folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder,
452
- side: "top",
453
- portal: true,
454
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
455
- className: "dsh-skills-folder",
456
- variant: "ghost",
457
- size: "sm",
458
- "aria-label": `${folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder}: ${skill.name}`,
459
- "aria-busy": folderStatus[skill.path] === "opening",
460
- disabled: !view.folderOpenAvailable || folderStatus[skill.path] === "opening",
461
- "aria-description": view.folderOpenAvailable ? text.openFolderHint : text.openFolderUnavailable,
462
- onClick: () => void openFolder(skill.path),
463
- icon: folderStatus[skill.path] === "opening" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular, {
464
- size: 16,
465
- "aria-hidden": "true"
466
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, {
467
- size: 16,
468
- "aria-hidden": "true"
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
558
+ className: "dsh-skills-actions",
559
+ children: [skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
560
+ label: folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder,
561
+ side: "top",
562
+ portal: true,
563
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
564
+ className: "dsh-skills-folder",
565
+ variant: "ghost",
566
+ size: "sm",
567
+ "aria-label": `${folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder}: ${skill.name}`,
568
+ "aria-busy": folderStatus[skill.path] === "opening",
569
+ disabled: !view.folderOpenAvailable || folderStatus[skill.path] === "opening",
570
+ "aria-description": view.folderOpenAvailable ? text.openFolderHint : text.openFolderUnavailable,
571
+ onClick: () => void openFolder(skill.path),
572
+ icon: folderStatus[skill.path] === "opening" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular, {
573
+ size: 16,
574
+ "aria-hidden": "true"
575
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, {
576
+ size: 16,
577
+ "aria-hidden": "true"
578
+ })
469
579
  })
470
- })
471
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
472
- label: `${text.use}: ${skill.name}`,
473
- checked: !view.disabled.includes(skill.name),
474
- disabled: busy || saveError,
475
- onChange: (enabled) => void save(skill.name, !enabled)
476
- })]
477
- }),
478
- skill.path && folderStatus[skill.path] === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
479
- className: "dsh-skills-open-error",
480
- role: "alert",
481
- children: text.openFolderError
482
- })
483
- ] }, skill.path ?? skill.name)) })] })
484
- }, group.value))] })
580
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
581
+ label: `${text.use}: ${skill.name}`,
582
+ checked: !view.disabled.includes(skill.name),
583
+ disabled: busy || saveError,
584
+ onChange: (enabled) => void save(skill.name, !enabled)
585
+ })]
586
+ }),
587
+ skill.path && folderStatus[skill.path] === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
588
+ className: "dsh-skills-open-error",
589
+ role: "alert",
590
+ children: text.openFolderError
591
+ })
592
+ ] }, skill.path ?? skill.name)) })] })
593
+ }, group.value))
594
+ ] })
485
595
  ]
486
596
  });
487
597
  }
package/dist/index.js CHANGED
@@ -7,7 +7,9 @@ import { dshCachePath } from "@deepseek-ai/dsh-home-paths";
7
7
  import { canOpenNativePath, openNativePath } from "@deepseek-ai/dsh-native-command";
8
8
  import { SkillRegistry } from "@deepseek-ai/dsh-skill";
9
9
  import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
10
- // ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.6/node_modules/@deepseek-ai/cosmokit/lib/index.js
10
+ import { francAll } from "franc-min";
11
+ import { iso6393 } from "iso-639-3";
12
+ // ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.9.0/node_modules/@deepseek-ai/cosmokit/lib/index.js
11
13
  /** Return true when a value is `null` or `undefined`. */
12
14
  function isNullable(value) {
13
15
  return value === null || value === void 0;
@@ -260,7 +262,7 @@ var Time;
260
262
  }
261
263
  Time.template = template;
262
264
  })(Time || (Time = {}));
263
- // ../../node_modules/.pnpm/@deepseek-ai+schemastery@3.18.5/node_modules/@deepseek-ai/schemastery/lib/index.mjs
265
+ // ../../node_modules/.pnpm/@deepseek-ai+schemastery@3.19.0/node_modules/@deepseek-ai/schemastery/lib/index.mjs
264
266
  const kSchema = Symbol.for("schemastery");
265
267
  const kValidationError = Symbol.for("ValidationError");
266
268
  globalThis.__schemastery_index__ ??= 0;
@@ -898,6 +900,23 @@ const method = "dungsil-ui-setting-skills/list";
898
900
  const saveMethod = "dungsil-ui-setting-skills/save";
899
901
  const translateMethod = "dungsil-ui-setting-skills/translate";
900
902
  const openFolderMethod = "dungsil-ui-setting-skills/open-folder";
903
+ // src/language.ts
904
+ const languageCodes = new Map(iso6393.map((entry) => [entry.iso6393, entry.iso6391 ?? entry.iso6393]));
905
+ languageCodes.set("cmn", "zh");
906
+ /** Only skip a provider request when local detection has a clear winner. */
907
+ function matchesTargetLanguage(text, targetLanguage) {
908
+ let target;
909
+ try {
910
+ target = new Intl.Locale(targetLanguage.replaceAll("_", "-"));
911
+ } catch {
912
+ return false;
913
+ }
914
+ if (target.script || target.language === "zh" && target.region) return false;
915
+ if ((text.match(/\p{L}/gu)?.length ?? 0) < 10) return false;
916
+ const [first, second] = francAll(text);
917
+ if (!first || first[0] === "und" || second && first[1] - second[1] < .2) return false;
918
+ return languageCodes.get(first[0]) === target.language;
919
+ }
901
920
  // src/index.ts
902
921
  const name = "dungsil-ui-setting-skills";
903
922
  const inject = ["connection", "configEditor"];
@@ -1010,8 +1029,8 @@ function apply(ctx, config = {}) {
1010
1029
  skill.description
1011
1030
  ])).digest("hex");
1012
1031
  const cachePath = join(cacheDir, `${key}.json`);
1013
- let value;
1014
- try {
1032
+ let value = matchesTargetLanguage(skill.description, targetLanguage) ? skill.description : void 0;
1033
+ if (value === void 0) try {
1015
1034
  const cached = JSON.parse(await readFile(cachePath, "utf8"));
1016
1035
  if (typeof cached === "string") value = cached;
1017
1036
  } catch {}
package/locale/en.json CHANGED
@@ -10,6 +10,9 @@
10
10
  "dsh": "DSH skills",
11
11
  "loading": "Loading skills…",
12
12
  "empty": "No loaded skills from this source.",
13
+ "search": "Search skills",
14
+ "searchPlaceholder": "Search by name, description, or path",
15
+ "noResults": "No skills match your search.",
13
16
  "error": "Could not load skills. Close and reopen this settings section to try again.",
14
17
  "incomplete": "Some skill sources could not be loaded. This list may be incomplete.",
15
18
  "use": "Use",
@@ -23,7 +26,10 @@
23
26
  "original": "View original",
24
27
  "retry": "Retry",
25
28
  "translating": "Translating…",
26
- "translationNotice": "Expanding a skill automatically sends its description to the service selected in reasoning translation settings. Translations are cached using its target language.",
29
+ "translateAll": "Translate all skills",
30
+ "translatingAll": "Translating all skills…",
31
+ "translationAllError": "Some skill descriptions could not be translated. Try again to translate only the failed items.",
32
+ "translationNotice": "Expanding a skill or translating all skills sends descriptions to the service selected in reasoning translation settings. Translate all includes both shared and DSH skills. Translations are cached using its target language.",
27
33
  "translationError": "Could not translate. The original is shown. Paid services require a Session and are unavailable here.",
28
34
  "saveError": "Could not save the setting. Close and reopen this section to check the saved state before trying again."
29
35
  }
package/locale/ko.json CHANGED
@@ -10,6 +10,9 @@
10
10
  "dsh": "DSH 스킬",
11
11
  "loading": "스킬을 불러오는 중입니다…",
12
12
  "empty": "이 경로에서 로드된 스킬이 없습니다.",
13
+ "search": "스킬 검색",
14
+ "searchPlaceholder": "이름, 설명 또는 경로로 검색",
15
+ "noResults": "검색 조건에 맞는 스킬이 없습니다.",
13
16
  "error": "스킬을 불러오지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 주세요.",
14
17
  "incomplete": "일부 스킬 소스를 불러오지 못해 목록이 완전하지 않을 수 있습니다.",
15
18
  "use": "사용",
@@ -23,7 +26,10 @@
23
26
  "original": "원문 보기",
24
27
  "retry": "다시 시도",
25
28
  "translating": "번역 중…",
26
- "translationNotice": "스킬을 펼치면 사고 과정 번역 설정에서 선택한 서비스로 설명을 자동 전송합니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
29
+ "translateAll": "전체 스킬 번역",
30
+ "translatingAll": "전체 스킬 번역 중…",
31
+ "translationAllError": "일부 스킬 설명을 번역하지 못했습니다. 다시 시도하면 실패한 항목만 번역합니다.",
32
+ "translationNotice": "스킬을 펼치거나 전체 스킬 번역을 누르면 사고 과정 번역 설정에서 선택한 서비스로 설명을 전송합니다. 전체 번역에는 공용 스킬과 DSH 스킬이 모두 포함됩니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
27
33
  "translationError": "번역하지 못해 원문을 표시합니다. 유료 서비스는 세션이 필요하므로 이 화면에서 사용할 수 없습니다.",
28
34
  "saveError": "설정을 저장하지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 후 다시 시도해 주세요."
29
35
  }
package/locale/zh.json CHANGED
@@ -10,6 +10,9 @@
10
10
  "dsh": "DSH 技能",
11
11
  "loading": "正在加载技能…",
12
12
  "empty": "未从此目录加载任何技能。",
13
+ "search": "搜索技能",
14
+ "searchPlaceholder": "按名称、描述或路径搜索",
15
+ "noResults": "没有符合搜索条件的技能。",
13
16
  "error": "无法加载技能。请关闭并重新打开此设置项后重试。",
14
17
  "incomplete": "部分技能源无法加载,列表可能不完整。",
15
18
  "use": "使用",
@@ -23,7 +26,10 @@
23
26
  "original": "查看原文",
24
27
  "retry": "重试",
25
28
  "translating": "正在翻译…",
26
- "translationNotice": "展开技能时,会自动将描述发送至思考过程翻译设置中选择的服务。译文按该设置的目标语言缓存。",
29
+ "translateAll": "翻译全部技能",
30
+ "translatingAll": "正在翻译全部技能…",
31
+ "translationAllError": "部分技能描述翻译失败。重试时仅翻译失败的项目。",
32
+ "translationNotice": "展开技能或点击翻译全部技能时,会将描述发送至思考过程翻译设置中选择的服务。全部翻译包括共享技能和 DSH 技能。译文按该设置的目标语言缓存。",
27
33
  "translationError": "翻译失败,已显示原文。付费服务需要会话,无法在此页面使用。",
28
34
  "saveError": "无法保存设置。请关闭并重新打开此设置项,确认已保存的状态后重试。"
29
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungsil/dsh-plugin-ui-setting-skills",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "View loaded user skills in DeepSeek Harness settings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,23 +25,25 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@deepseek-ai/dsh-atomic-write": "0.2.1-rc.5",
29
- "@deepseek-ai/dsh-home-paths": "0.2.1-rc.5",
30
- "@deepseek-ai/dsh-native-command": "0.2.1-rc.5"
28
+ "@deepseek-ai/dsh-atomic-write": "0.2.2-rc.1",
29
+ "@deepseek-ai/dsh-home-paths": "0.2.2-rc.1",
30
+ "@deepseek-ai/dsh-native-command": "0.2.2-rc.1",
31
+ "franc-min": "6.2.0",
32
+ "iso-639-3": "3.0.1"
31
33
  },
32
34
  "devDependencies": {
33
- "@deepseek-ai/cordis": "4.0.5",
34
- "@deepseek-ai/dsh-app-boot": "0.2.1-rc.5",
35
- "@deepseek-ai/dsh-client-connection": "0.2.1-rc.5",
36
- "@deepseek-ai/dsh-client-locale": "0.2.1-rc.5",
37
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
38
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
39
- "@deepseek-ai/dsh-config-editor": "0.2.1-rc.5",
40
- "@deepseek-ai/dsh-credentials-local": "0.2.1-rc.5",
41
- "@deepseek-ai/dsh-experimental-client-ui-cot-translation": "0.2.1-rc.5",
42
- "@deepseek-ai/dsh-skill": "0.2.1-rc.5",
43
- "@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.5",
44
- "@deepseek-ai/schemastery": "3.18.5",
35
+ "@deepseek-ai/cordis": "4.1.0",
36
+ "@deepseek-ai/dsh-app-boot": "0.2.2-rc.1",
37
+ "@deepseek-ai/dsh-client-connection": "0.2.2-rc.1",
38
+ "@deepseek-ai/dsh-client-locale": "0.2.2-rc.1",
39
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.2-rc.1",
40
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.2-rc.1",
41
+ "@deepseek-ai/dsh-config-editor": "0.2.2-rc.1",
42
+ "@deepseek-ai/dsh-credentials-local": "0.2.2-rc.1",
43
+ "@deepseek-ai/dsh-experimental-client-ui-cot-translation": "0.2.2-rc.1",
44
+ "@deepseek-ai/dsh-skill": "0.2.2-rc.1",
45
+ "@deepseek-ai/dsh-skill-filesystem": "0.2.2-rc.1",
46
+ "@deepseek-ai/schemastery": "3.19.0",
45
47
  "@dungsil/dsh-tsconfig": "0.0.0",
46
48
  "@dungsil/dsh-tsdown": "0.0.0",
47
49
  "@dungsil/dsh-ui": "0.0.0",
@@ -54,14 +56,14 @@
54
56
  },
55
57
  "peerDependencies": {
56
58
  "@deepseek-ai/cordis": "^4.0.5",
57
- "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.5",
58
- "@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5",
59
- "@deepseek-ai/dsh-config-editor": "^0.2.1-rc.5",
60
- "@deepseek-ai/dsh-skill": "^0.2.1-rc.5",
61
- "@deepseek-ai/dsh-skill-filesystem": "^0.2.1-rc.5"
59
+ "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.5 || ^0.2.2-rc.1",
60
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.2.1-rc.5 || ^0.2.2-rc.1",
61
+ "@deepseek-ai/dsh-config-editor": "^0.2.1-rc.5 || ^0.2.2-rc.1",
62
+ "@deepseek-ai/dsh-skill": "^0.2.1-rc.5 || ^0.2.2-rc.1",
63
+ "@deepseek-ai/dsh-skill-filesystem": "^0.2.1-rc.5 || ^0.2.2-rc.1"
62
64
  },
63
65
  "engines": {
64
- "dsh": "^0.2.1-rc.5",
66
+ "dsh": "^0.2.1-rc.5 || ^0.2.2-rc.1",
65
67
  "node": ">=24.9.0"
66
68
  },
67
69
  "dsh": {