@dungsil/dsh-plugin-ui-setting-skills 0.3.2 → 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
@@ -77,6 +77,9 @@ var en_default = {
77
77
  "dsh": "DSH skills",
78
78
  "loading": "Loading skills…",
79
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.",
80
83
  "error": "Could not load skills. Close and reopen this settings section to try again.",
81
84
  "incomplete": "Some skill sources could not be loaded. This list may be incomplete.",
82
85
  "use": "Use",
@@ -90,7 +93,10 @@ var en_default = {
90
93
  "original": "View original",
91
94
  "retry": "Retry",
92
95
  "translating": "Translating…",
93
- "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.",
94
100
  "translationError": "Could not translate. The original is shown. Paid services require a Session and are unavailable here.",
95
101
  "saveError": "Could not save the setting. Close and reopen this section to check the saved state before trying again."
96
102
  }
@@ -108,6 +114,9 @@ var ko_default = {
108
114
  "dsh": "DSH 스킬",
109
115
  "loading": "스킬을 불러오는 중입니다…",
110
116
  "empty": "이 경로에서 로드된 스킬이 없습니다.",
117
+ "search": "스킬 검색",
118
+ "searchPlaceholder": "이름, 설명 또는 경로로 검색",
119
+ "noResults": "검색 조건에 맞는 스킬이 없습니다.",
111
120
  "error": "스킬을 불러오지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 주세요.",
112
121
  "incomplete": "일부 스킬 소스를 불러오지 못해 목록이 완전하지 않을 수 있습니다.",
113
122
  "use": "사용",
@@ -121,7 +130,10 @@ var ko_default = {
121
130
  "original": "원문 보기",
122
131
  "retry": "다시 시도",
123
132
  "translating": "번역 중…",
124
- "translationNotice": "스킬을 펼치면 사고 과정 번역 설정에서 선택한 서비스로 설명을 자동 전송합니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
133
+ "translateAll": "전체 스킬 번역",
134
+ "translatingAll": "전체 스킬 번역 중…",
135
+ "translationAllError": "일부 스킬 설명을 번역하지 못했습니다. 다시 시도하면 실패한 항목만 번역합니다.",
136
+ "translationNotice": "스킬을 펼치거나 전체 스킬 번역을 누르면 사고 과정 번역 설정에서 선택한 서비스로 설명을 전송합니다. 전체 번역에는 공용 스킬과 DSH 스킬이 모두 포함됩니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
125
137
  "translationError": "번역하지 못해 원문을 표시합니다. 유료 서비스는 세션이 필요하므로 이 화면에서 사용할 수 없습니다.",
126
138
  "saveError": "설정을 저장하지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 후 다시 시도해 주세요."
127
139
  }
@@ -139,6 +151,9 @@ var zh_default = {
139
151
  "dsh": "DSH 技能",
140
152
  "loading": "正在加载技能…",
141
153
  "empty": "未从此目录加载任何技能。",
154
+ "search": "搜索技能",
155
+ "searchPlaceholder": "按名称、描述或路径搜索",
156
+ "noResults": "没有符合搜索条件的技能。",
142
157
  "error": "无法加载技能。请关闭并重新打开此设置项后重试。",
143
158
  "incomplete": "部分技能源无法加载,列表可能不完整。",
144
159
  "use": "使用",
@@ -152,7 +167,10 @@ var zh_default = {
152
167
  "original": "查看原文",
153
168
  "retry": "重试",
154
169
  "translating": "正在翻译…",
155
- "translationNotice": "展开技能时,会自动将描述发送至思考过程翻译设置中选择的服务。译文按该设置的目标语言缓存。",
170
+ "translateAll": "翻译全部技能",
171
+ "translatingAll": "正在翻译全部技能…",
172
+ "translationAllError": "部分技能描述翻译失败。重试时仅翻译失败的项目。",
173
+ "translationNotice": "展开技能或点击翻译全部技能时,会将描述发送至思考过程翻译设置中选择的服务。全部翻译包括共享技能和 DSH 技能。译文按该设置的目标语言缓存。",
156
174
  "translationError": "翻译失败,已显示原文。付费服务需要会话,无法在此页面使用。",
157
175
  "saveError": "无法保存设置。请关闭并重新打开此设置项,确认已保存的状态后重试。"
158
176
  }
@@ -174,6 +192,7 @@ const styles = `
174
192
  .dsh-skills-settings { container-type: inline-size; color: var(--dsw-alias-label-primary); font: 14px/22px var(--dsw-font-family, system-ui); }
175
193
  .dsh-skills-settings p { margin: 4px 0; }
176
194
  .dsh-skills-settings-tabs { margin: 20px 0 12px; }
195
+ .dsh-skills-search { width: 100%; box-sizing: border-box; margin-bottom: 12px; }
177
196
  .dsh-skills-settings .dsh-skills-muted { color: var(--dsw-alias-label-secondary); }
178
197
  .dsh-skills-settings ul { list-style: none; margin: 0; padding: 0; }
179
198
  .dsh-skills-settings code { font-size: 12px; overflow-wrap: anywhere; }
@@ -197,52 +216,29 @@ const styles = `
197
216
  .dsh-skills-open-error { grid-column: 1 / -1; padding: 0 0 16px 28px; }
198
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); }
199
218
  `;
200
- 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 }) {
201
225
  const text = localizedCopy(language);
202
- const [translated, setTranslated] = (0, react.useState)();
203
226
  const [original, setOriginal] = (0, react.useState)(false);
204
- const [pending, setPending] = (0, react.useState)(false);
205
- const [failed, setFailed] = (0, react.useState)(false);
206
- const [retry, setRetry] = (0, react.useState)(0);
227
+ const { value: translated, pending, failed } = translation ?? {};
207
228
  (0, react.useEffect)(() => {
208
- setTranslated(void 0);
209
- setOriginal(false);
210
- setPending(false);
211
- setFailed(false);
212
- if (!expanded || !available || !skill.path || !skill.description) return;
213
- const request = new AbortController();
214
- setPending(true);
215
- (async () => {
216
- try {
217
- const result = await connection.rpc.call(channel, translateMethod, {
218
- path: skill.path,
219
- language
220
- }, request.signal);
221
- if (request.signal.aborted) return;
222
- if (!result.ok || typeof result.value !== "string") throw new Error("Could not translate description");
223
- setTranslated(result.value);
224
- } catch {
225
- if (!request.signal.aborted) setFailed(true);
226
- } finally {
227
- if (!request.signal.aborted) setPending(false);
228
- }
229
- })();
230
- return () => request.abort();
229
+ if (expanded && available) translate(skill);
231
230
  }, [
232
231
  expanded,
233
- skill.description,
234
- skill.path,
235
- language,
236
232
  available,
237
- connection,
238
- retry
233
+ skill,
234
+ translate
239
235
  ]);
240
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: [
241
237
  pending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
242
238
  role: "status",
243
239
  children: text.translating
244
240
  }),
245
- 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, {
246
242
  variant: "ghost",
247
243
  size: "sm",
248
244
  onClick: () => setOriginal((value) => !value),
@@ -254,7 +250,7 @@ function SkillDescription({ skill, connection, language, available, expanded })
254
250
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
255
251
  variant: "ghost",
256
252
  size: "sm",
257
- onClick: () => setRetry((value) => value + 1),
253
+ onClick: () => void translate(skill),
258
254
  children: text.retry
259
255
  })] })
260
256
  ] })] });
@@ -274,11 +270,89 @@ function SkillsSettings({ ctx }) {
274
270
  const mounted = (0, react.useRef)(false);
275
271
  const [expanded, setExpanded] = (0, react.useState)({});
276
272
  const [source, setSource] = (0, react.useState)("user-agents");
277
- const groups = ["user-agents", "user-dsh"].map((value) => ({
278
- value,
279
- label: value === "user-agents" ? text.agents : text.dsh,
280
- skills: view?.skills.filter((skill) => skill.source === value) ?? []
281
- }));
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}`;
282
356
  (0, react.useEffect)(() => {
283
357
  const controller = new AbortController();
284
358
  mounted.current = true;
@@ -357,9 +431,30 @@ function SkillsSettings({ ctx }) {
357
431
  className: "dsh-skills-muted",
358
432
  children: text.openFolderUnavailable
359
433
  }),
360
- view?.translationAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
361
- className: "dsh-skills-muted",
362
- 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
+ ]
363
458
  }),
364
459
  !view && !error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
365
460
  role: "status",
@@ -377,112 +472,126 @@ function SkillsSettings({ ctx }) {
377
472
  role: "alert",
378
473
  children: text.incomplete
379
474
  }),
380
- 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, {
381
- id,
382
- className: "dsh-skills-settings-tabs",
383
- value: source,
384
- options: groups.map((group) => ({
385
- value: group.value,
386
- label: `${group.label} ${group.skills.length}`
387
- })),
388
- onChange: setSource,
389
- label: text.nav
390
- }), groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
391
- id: `${id}-${group.value}-panel`,
392
- role: "tabpanel",
393
- "aria-labelledby": `${id}-${group.value}`,
394
- hidden: source !== group.value,
395
- tabIndex: 0,
396
- children: group.skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
397
- className: "dsh-skills-muted",
398
- children: text.empty
399
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
400
- className: "dsh-skills-list-header dsh-skills-muted",
401
- "aria-hidden": "true",
402
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
403
- text.nav,
404
- " ",
405
- group.skills.length
406
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.use })]
407
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
408
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
409
- onToggle: (event) => {
410
- const open = event.currentTarget.open;
411
- setExpanded((current) => ({
412
- ...current,
413
- [skill.path ?? skill.name]: open
414
- }));
415
- },
416
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
417
- "aria-label": skill.name,
418
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
419
- size: 16,
420
- "aria-hidden": "true"
421
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
422
- className: "dsh-skills-summary-text",
423
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
424
- className: "dsh-skills-title",
425
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: skill.name }), view.disabled.includes(skill.name) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
426
- className: "dsh-skills-muted",
427
- 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
428
540
  })]
429
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
430
- className: "dsh-skills-preview dsh-skills-muted",
431
- "aria-hidden": "true",
432
- 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 })
433
554
  })]
434
555
  })]
435
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
436
- className: "dsh-skills-detail",
437
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDescription, {
438
- skill,
439
- connection,
440
- language: locale.active,
441
- available: view.translationAvailable && source === group.value,
442
- expanded: expanded[skill.path ?? skill.name] ?? false
443
- }), skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
444
- className: "dsh-skills-muted",
445
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: skill.path })
446
- })]
447
- })]
448
- }),
449
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
450
- className: "dsh-skills-actions",
451
- children: [skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
452
- label: folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder,
453
- side: "top",
454
- portal: true,
455
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
456
- className: "dsh-skills-folder",
457
- variant: "ghost",
458
- size: "sm",
459
- "aria-label": `${folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder}: ${skill.name}`,
460
- "aria-busy": folderStatus[skill.path] === "opening",
461
- disabled: !view.folderOpenAvailable || folderStatus[skill.path] === "opening",
462
- "aria-description": view.folderOpenAvailable ? text.openFolderHint : text.openFolderUnavailable,
463
- onClick: () => void openFolder(skill.path),
464
- icon: folderStatus[skill.path] === "opening" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular, {
465
- size: 16,
466
- "aria-hidden": "true"
467
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, {
468
- size: 16,
469
- "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
+ })
470
579
  })
471
- })
472
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
473
- label: `${text.use}: ${skill.name}`,
474
- checked: !view.disabled.includes(skill.name),
475
- disabled: busy || saveError,
476
- onChange: (enabled) => void save(skill.name, !enabled)
477
- })]
478
- }),
479
- skill.path && folderStatus[skill.path] === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
480
- className: "dsh-skills-open-error",
481
- role: "alert",
482
- children: text.openFolderError
483
- })
484
- ] }, skill.path ?? skill.name)) })] })
485
- }, 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
+ ] })
486
595
  ]
487
596
  });
488
597
  }
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@ 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
+ import { francAll } from "franc-min";
11
+ import { iso6393 } from "iso-639-3";
10
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) {
@@ -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.2",
3
+ "version": "0.4.0",
4
4
  "description": "View loaded user skills in DeepSeek Harness settings.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,7 +27,9 @@
27
27
  "dependencies": {
28
28
  "@deepseek-ai/dsh-atomic-write": "0.2.2-rc.1",
29
29
  "@deepseek-ai/dsh-home-paths": "0.2.2-rc.1",
30
- "@deepseek-ai/dsh-native-command": "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
35
  "@deepseek-ai/cordis": "4.1.0",