@michengai/dsh-skills-manager 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -165,3 +165,4 @@ npm run pack:check
165
165
  Project status, usage boundaries, architecture, and iteration records begin at the [documentation entry point](docs/00-交接入口/00-阅读导航.md). The detailed operational guide is `docs\02-产品与业务\01-使用说明.md`.
166
166
 
167
167
  Licensed under [Apache License 2.0](LICENSE).
168
+
package/README.zh-CN.md CHANGED
@@ -165,3 +165,4 @@ npm run pack:check
165
165
  项目状态、使用边界、技术架构和迭代记录从[文档交接入口](docs/00-交接入口/00-阅读导航.md)开始。详细操作说明见 `docs\02-产品与业务\01-使用说明.md`。
166
166
 
167
167
  本项目采用 [Apache License 2.0](LICENSE)。
168
+
package/lib/client.js CHANGED
@@ -40,6 +40,16 @@ window.__ModuleLoader__.load({
40
40
  "summary.enabled.one": "个已启用",
41
41
  "summary.enabled.other": "个已启用",
42
42
  "summary.group": "{count} 个",
43
+ "filter.category": "分类",
44
+ "filter.all": "全部",
45
+ "filter.option": "{name}({count})",
46
+ "filter.showing.one": "显示 {count} 个",
47
+ "filter.showing.other": "显示 {count} 个",
48
+ "search": "搜索",
49
+ "search.placeholder": "名称、形态或简介",
50
+ "search.clear": "清除搜索",
51
+ "empty.search": "没有匹配的插件。试试其他关键词,或切换到「{all}」。",
52
+ "empty.search.reset": "清除筛选",
43
53
  "note.missing": "未提供简介",
44
54
  "empty.dir.uncreated": "目录尚未创建,上传第一个插件后会自动创建。",
45
55
  "empty.dir.missing": "公共目录不存在。",
@@ -128,6 +138,16 @@ window.__ModuleLoader__.load({
128
138
  "summary.enabled.one": "enabled",
129
139
  "summary.enabled.other": "enabled",
130
140
  "summary.group": "{count} items",
141
+ "filter.category": "Category",
142
+ "filter.all": "All",
143
+ "filter.option": "{name} ({count})",
144
+ "filter.showing.one": "Showing {count} skill",
145
+ "filter.showing.other": "Showing {count} skills",
146
+ "search": "Search",
147
+ "search.placeholder": "Name, kind, or description",
148
+ "search.clear": "Clear search",
149
+ "empty.search": "No matching plugins. Try another keyword, or switch to {all}.",
150
+ "empty.search.reset": "Clear filters",
131
151
  "note.missing": "No description provided",
132
152
  "empty.dir.uncreated": "The directory has not been created yet; it will be created automatically after the first upload.",
133
153
  "empty.dir.missing": "The public directory does not exist.",
@@ -202,7 +222,8 @@ window.__ModuleLoader__.load({
202
222
  .dssm-row{display:flex;align-items:center;gap:12px;padding:13px 16px;border-bottom:1px solid var(--dsw-alias-border-l1)}.dssm-row:last-child{border-bottom:0}.dssm-row-main{display:flex;min-width:0;flex:1;flex-direction:column;gap:3px}.dssm-row-id{display:flex;align-items:center;gap:7px;min-width:0}.dssm-row-name{overflow:hidden;font-size:13px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.dssm-tag{flex:none;padding:1px 6px;border:1px solid var(--dsw-alias-border-l3);border-radius:4px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}.dssm-tag-on{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.dssm-tag-off{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.dssm-note{overflow:hidden;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.dssm-empty{padding:28px 16px;color:var(--dsw-alias-label-tertiary);font-size:13px;text-align:center}.dssm-error{color:var(--dsw-alias-state-error-primary);font-size:13px;line-height:20px}
203
223
  .dssm-mask{position:fixed;z-index:1100;inset:0;display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;height:100%;padding:24px;background:rgba(0,0,0,.58)}.dssm-modal{box-sizing:border-box;display:flex;width:min(480px,100%)!important;height:auto!important;max-width:100%;max-height:100%;min-width:0;min-height:0;flex:0 0 auto!important;align-self:center!important;flex-direction:column;gap:18px;padding:24px;overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-2);box-shadow:var(--dsw-shadow-lv3)}.dssm-modal-upload{width:min(480px,100%)!important;height:auto!important;max-width:100%;max-height:100%;align-self:center!important;flex:0 0 auto!important;overflow:auto}.dssm-modal-head{display:flex;align-items:center;gap:12px}.dssm-modal-title{margin:0;flex:1;font-size:17px;line-height:24px;font-weight:650}.dssm-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:20px;line-height:1;cursor:pointer}.dssm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}
204
224
  .dssm-dropzone{box-sizing:border-box;display:flex;width:100%;min-height:144px;flex:none;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color 180ms ease,background 180ms ease}.dssm-dropzone:hover,.dssm-dropzone-active{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-interactive-bg-hover)}.dssm-dropzone-title{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:560}.dssm-dropzone-copy{font-size:12px;line-height:18px;text-align:center}.dssm-file{display:flex;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-primary);font-size:13px}.dssm-file-name{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}.dssm-modal-head,.dssm-file,.dssm-modal-actions{flex:none}.dssm-modal-actions{display:flex;justify-content:flex-end;flex-wrap:wrap;gap:8px}.dssm-hidden-input{display:none}
205
- @media (max-width:560px){.dssm-mask{padding:12px;align-items:center}.dssm-modal{width:100%!important;height:auto!important;padding:16px;gap:12px;align-self:center!important}.dssm-modal-upload{width:100%!important;align-self:center!important}.dssm-toolbar{flex-wrap:wrap}.dssm-actions{margin-left:0}.dssm-path{display:none}.dssm-row{align-items:flex-start;flex-wrap:wrap}.dssm-row>.dssm-btn{margin-left:auto}.dssm-modal-actions{justify-content:stretch}.dssm-modal-actions .dssm-btn{flex:1 1 auto}}@media (prefers-reduced-motion:reduce){.dssm-btn,.dssm-dropzone{transition:none}}
225
+ .dssm-filters{display:flex;align-items:flex-end;gap:10px}.dssm-field{display:flex;min-width:0;flex:1;flex-direction:column;gap:6px}.dssm-field-category{flex:0 1 220px}.dssm-field-search{flex:1 1 240px}.dssm-label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px}.dssm-control{box-sizing:border-box;width:100%;min-height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}.dssm-control:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-select{position:relative}.dssm-select-trigger{box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;min-height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dssm-select-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}.dssm-select-trigger:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-select-trigger[aria-expanded="true"]{border-color:var(--dsw-alias-state-success-primary)}.dssm-select-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dssm-select-caret{flex:none;width:12px;height:12px;color:var(--dsw-alias-label-tertiary)}.dssm-select-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:30;box-sizing:border-box;max-height:280px;overflow:auto;padding:4px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2));box-shadow:var(--dsw-shadow-lv3)}.dssm-select-option{box-sizing:border-box;display:flex;align-items:center;width:100%;min-height:32px;padding:0 10px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer}.dssm-select-option:hover,.dssm-select-option[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover)}.dssm-select-option[aria-selected="true"]{color:var(--dsw-alias-state-success-primary)}.dssm-search-wrap{position:relative;display:flex;align-items:center}.dssm-search{padding-right:32px}.dssm-search::-webkit-search-cancel-button,.dssm-search::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.dssm-search-clear{position:absolute;right:4px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:16px;line-height:1;cursor:pointer}.dssm-search-clear:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.dssm-search-clear:focus-visible{outline:2px solid var(--dsw-alias-state-success-primary);outline-offset:2px}.dssm-empty-filter{display:flex;flex-direction:column;align-items:center;gap:12px;padding:28px 16px;border:1px dashed var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px;text-align:center}.dssm-filter-meta{margin-left:auto;color:var(--dsw-alias-label-tertiary);font-size:12px}
226
+ @media (max-width:560px){.dssm-mask{padding:12px;align-items:center}.dssm-modal{width:100%!important;height:auto!important;padding:16px;gap:12px;align-self:center!important}.dssm-modal-upload{width:100%!important;align-self:center!important}.dssm-toolbar{flex-wrap:wrap}.dssm-actions{margin-left:0}.dssm-path{display:none}.dssm-row{align-items:flex-start;flex-wrap:wrap}.dssm-row>.dssm-btn{margin-left:auto}.dssm-filters{flex-direction:column;align-items:stretch}.dssm-field-category,.dssm-field-search{flex:none}.dssm-filter-meta{margin-left:0}.dssm-modal-actions{justify-content:stretch}.dssm-modal-actions .dssm-btn{flex:1 1 auto}}@media (prefers-reduced-motion:reduce){.dssm-btn,.dssm-dropzone{transition:none}}
206
227
  `;
207
228
 
208
229
  function callApi(path, options) {
@@ -258,6 +279,50 @@ window.__ModuleLoader__.load({
258
279
  return skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable;
259
280
  }
260
281
 
282
+ /** 规范化检索词:去首尾空白并转小写,便于名称、形态和简介统一匹配。 */
283
+ function normalizeSkillQuery(query) {
284
+ return String(query == null ? "" : query).trim().toLowerCase();
285
+ }
286
+
287
+ /** 按名称、简介、形态或分类标签做包含匹配;空检索视为全部命中。 */
288
+ function matchSkillQuery(skill, query) {
289
+ var q = normalizeSkillQuery(query);
290
+ if (q === "") return true;
291
+ return [
292
+ skill.name,
293
+ skill.description,
294
+ skill.kind,
295
+ skill.kindLabel,
296
+ skill.statusLabel,
297
+ skill.rootKey,
298
+ skill.rootLabel,
299
+ ].some(function (field) {
300
+ return String(field == null ? "" : field).toLowerCase().includes(q);
301
+ });
302
+ }
303
+
304
+ /** 先按目录分类收窄,再按检索词过滤。rootKey 为空表示全部分类。 */
305
+ function filterSkills(list, options) {
306
+ var query = options && options.query;
307
+ var rootKey = options && options.rootKey != null ? options.rootKey : "";
308
+ return list.filter(function (skill) {
309
+ return (rootKey === "" || skill.rootKey === rootKey) && matchSkillQuery(skill, query);
310
+ });
311
+ }
312
+
313
+ function skillSearchMeta(skill, root, t) {
314
+ var enabled = isSkillEnabled(skill);
315
+ return {
316
+ name: skill.name,
317
+ description: skill.description,
318
+ kind: skill.kind,
319
+ kindLabel: t(skill.kind === "bundle" ? "status.bundle" : "status.single"),
320
+ statusLabel: skill.invocationPolicyValid ? (enabled ? t("status.enabled") : t("status.disabled")) : t("status.invalid"),
321
+ rootKey: root.key,
322
+ rootLabel: translateOrFallback(t, "root." + root.key, root.label),
323
+ };
324
+ }
325
+
261
326
  function modalFocusable(modal) {
262
327
  return Array.prototype.slice.call(modal.querySelectorAll("button:not(:disabled), [href], input:not([type=hidden]):not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"])"));
263
328
  }
@@ -273,6 +338,122 @@ window.__ModuleLoader__.load({
273
338
  }
274
339
  }
275
340
 
341
+ function CategorySelect(props) {
342
+ var openState = react.useState(false);
343
+ var open = openState[0];
344
+ var setOpen = openState[1];
345
+ var selectedIndex = 0;
346
+ for (var i = 0; i < props.options.length; i++) {
347
+ if (props.options[i].value === props.value) { selectedIndex = i; break; }
348
+ }
349
+ var activeState = react.useState(selectedIndex);
350
+ var active = activeState[0];
351
+ var setActive = activeState[1];
352
+ var rootRef = react.useRef(null);
353
+ var triggerRef = react.useRef(null);
354
+ var listRef = react.useRef(null);
355
+ var wasOpen = react.useRef(false);
356
+ var selected = props.options[selectedIndex];
357
+
358
+ react.useEffect(function () {
359
+ if (!open) return undefined;
360
+ setActive(selectedIndex);
361
+ function onPointerDown(event) {
362
+ var node = rootRef.current;
363
+ if (node && event.target && node.contains(event.target)) return;
364
+ setOpen(false);
365
+ }
366
+ document.addEventListener("pointerdown", onPointerDown);
367
+ return function () { document.removeEventListener("pointerdown", onPointerDown); };
368
+ }, [open, selectedIndex]);
369
+
370
+ react.useEffect(function () {
371
+ if (open) {
372
+ if (listRef.current) listRef.current.focus();
373
+ wasOpen.current = true;
374
+ return;
375
+ }
376
+ if (wasOpen.current) {
377
+ if (triggerRef.current) triggerRef.current.focus();
378
+ wasOpen.current = false;
379
+ }
380
+ }, [open]);
381
+
382
+ react.useEffect(function () {
383
+ if (!open) return;
384
+ var option = document.getElementById(props.id + "-opt-" + String(active));
385
+ if (option) option.scrollIntoView({ block: "nearest" });
386
+ }, [active, open, props.id]);
387
+
388
+ function choose(value) {
389
+ props.onChange(value);
390
+ setOpen(false);
391
+ }
392
+
393
+ function move(next) {
394
+ if (!props.options.length) return;
395
+ setActive(Math.min(props.options.length - 1, Math.max(0, next)));
396
+ }
397
+
398
+ function onTriggerKeyDown(event) {
399
+ if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "Enter" || event.key === " ") {
400
+ event.preventDefault();
401
+ setOpen(true);
402
+ }
403
+ }
404
+
405
+ function onListKeyDown(event) {
406
+ if (event.key === "ArrowDown") { event.preventDefault(); move(active + 1); return; }
407
+ if (event.key === "ArrowUp") { event.preventDefault(); move(active - 1); return; }
408
+ if (event.key === "Home") { event.preventDefault(); move(0); return; }
409
+ if (event.key === "End") { event.preventDefault(); move(props.options.length - 1); return; }
410
+ if (event.key === "Enter" || event.key === " ") {
411
+ event.preventDefault();
412
+ var option = props.options[active];
413
+ if (option) choose(option.value);
414
+ return;
415
+ }
416
+ if (event.key === "Escape" || event.key === "Tab") setOpen(false);
417
+ }
418
+
419
+ return h("div", { className: "dssm-select", ref: rootRef },
420
+ h("button", {
421
+ id: props.id,
422
+ ref: triggerRef,
423
+ type: "button",
424
+ className: "dssm-select-trigger",
425
+ "aria-haspopup": "listbox",
426
+ "aria-expanded": open,
427
+ "aria-controls": props.id + "-list",
428
+ onClick: function () { setOpen(function (current) { return !current; }); },
429
+ onKeyDown: onTriggerKeyDown,
430
+ },
431
+ h("span", { className: "dssm-select-value" }, selected ? selected.label : ""),
432
+ h("svg", { className: "dssm-select-caret", viewBox: "0 0 12 12", "aria-hidden": true, focusable: false },
433
+ h("path", { d: "M2.5 4.5L6 8l3.5-3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }))),
434
+ open ? h("div", {
435
+ id: props.id + "-list",
436
+ ref: listRef,
437
+ className: "dssm-select-menu",
438
+ role: "listbox",
439
+ tabIndex: 0,
440
+ "aria-activedescendant": props.id + "-opt-" + String(active),
441
+ onKeyDown: onListKeyDown,
442
+ }, props.options.map(function (option, index) {
443
+ return h("button", {
444
+ key: option.value === "" ? "all" : option.value,
445
+ id: props.id + "-opt-" + String(index),
446
+ type: "button",
447
+ role: "option",
448
+ className: "dssm-select-option",
449
+ "aria-selected": option.value === props.value,
450
+ "data-active": index === active,
451
+ onMouseEnter: function () { setActive(index); },
452
+ onClick: function () { choose(option.value); },
453
+ }, option.label);
454
+ })) : null);
455
+ }
456
+
276
457
  // 渲染机制按 register 的 locale 字段把命名空间绑定的 t 注入组件 props。
277
458
  function SkillManagerSection(props) {
278
459
  var t = props.t;
@@ -297,6 +478,12 @@ window.__ModuleLoader__.load({
297
478
  var deleteState = react.useState(null);
298
479
  var confirmDelete = deleteState[0];
299
480
  var setConfirmDelete = deleteState[1];
481
+ var queryState = react.useState("");
482
+ var query = queryState[0];
483
+ var setQuery = queryState[1];
484
+ var categoryState = react.useState("");
485
+ var category = categoryState[0];
486
+ var setCategory = categoryState[1];
300
487
  var inputRef = react.useRef(null);
301
488
  var modalRef = react.useRef(null);
302
489
 
@@ -443,15 +630,65 @@ window.__ModuleLoader__.load({
443
630
  h("div", { className: "dssm-actions" },
444
631
  h("button", { className: "dssm-btn dssm-btn-secondary", disabled: !!busy, onClick: refresh }, t("btn.refresh")),
445
632
  h("button", { className: "dssm-btn", disabled: !!busy, onClick: function () { setImportResult(null); setUploadOpen(true); } }, t("btn.upload")))));
633
+ var searchable = roots.reduce(function (all, root) {
634
+ return all.concat((root.skills || []).map(function (skill) { return skillSearchMeta(skill, root, t); }));
635
+ }, []);
636
+ var filtered = filterSkills(searchable, { query: query, rootKey: category });
637
+ var filteredCount = filtered.length;
638
+ var hasQuery = normalizeSkillQuery(query) !== "";
639
+ var hasFilter = hasQuery || category !== "";
640
+ var resetFilters = function () { setQuery(""); setCategory(""); };
446
641
  // 复数按 en 的 one/other 二元规则选择;zh/en 均适用,扩展多复数语言时需改 CLDR 规则。
447
642
  nodes.push(h("div", { key: "summary", className: "dssm-summary" },
448
- h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, t(skills.length === 1 ? "summary.total.one" : "summary.total.other", { count: skills.length })), h("span", { className: "dssm-summary-separator" }), h("span", { className: "dssm-summary-count" }, enabled), h("span", { className: "dssm-summary-label" }, t(enabled === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabled }))));
643
+ h("span", { className: "dssm-summary-count" }, skills.length), h("span", { className: "dssm-summary-label" }, t(skills.length === 1 ? "summary.total.one" : "summary.total.other", { count: skills.length })), h("span", { className: "dssm-summary-separator" }), h("span", { className: "dssm-summary-count" }, enabled), h("span", { className: "dssm-summary-label" }, t(enabled === 1 ? "summary.enabled.one" : "summary.enabled.other", { count: enabled })),
644
+ hasFilter ? h("span", { className: "dssm-filter-meta", "aria-live": "polite" }, t(filteredCount === 1 ? "filter.showing.one" : "filter.showing.other", { count: filteredCount })) : null));
645
+ nodes.push(h("div", { key: "filters", className: "dssm-filters" },
646
+ h("div", { className: "dssm-field dssm-field-category" },
647
+ h("label", { className: "dssm-label", htmlFor: "dssm-filter-category" }, t("filter.category")),
648
+ h(CategorySelect, {
649
+ id: "dssm-filter-category",
650
+ value: category,
651
+ onChange: setCategory,
652
+ options: [{ value: "", label: t("filter.option", { name: t("filter.all"), count: skills.length }) }].concat(roots.map(function (root) {
653
+ return { value: root.key, label: t("filter.option", { name: translateOrFallback(t, "root." + root.key, root.label), count: (root.skills || []).length }) };
654
+ })),
655
+ })),
656
+ h("div", { className: "dssm-field dssm-field-search" },
657
+ h("label", { className: "dssm-label", htmlFor: "dssm-filter-search" }, t("search")),
658
+ h("div", { className: "dssm-search-wrap" },
659
+ h("input", {
660
+ id: "dssm-filter-search",
661
+ className: "dssm-control dssm-search",
662
+ type: "search",
663
+ value: query,
664
+ autoComplete: "off",
665
+ spellCheck: false,
666
+ placeholder: t("search.placeholder"),
667
+ onChange: function (event) { setQuery(event.currentTarget.value); },
668
+ }),
669
+ query !== "" ? h("button", { type: "button", className: "dssm-search-clear", "aria-label": t("search.clear"), onClick: function () { setQuery(""); } }, "×") : null))));
670
+ var visibleRoots = [];
449
671
  roots.forEach(function (root) {
450
- var groupSkills = root.skills || [];
451
- nodes.push(h("div", { key: root.key, className: "dssm-group" },
452
- h("div", { className: "dssm-group-head" }, h("h3", { className: "dssm-group-title" }, translateOrFallback(t, "root." + root.key, root.label)), h("span", { className: "dssm-count" }, t("summary.group", { count: groupSkills.length })), h("span", { className: "dssm-path", title: root.path }, root.path)),
453
- root.exists === false ? h("div", { className: "dssm-empty" }, root.mutable ? t("empty.dir.uncreated") : t("empty.dir.missing")) : (groupSkills.length ? groupSkills.map(function (skill) { return skillRow(skill, root); }) : h("div", { className: "dssm-empty" }, t("empty.skills.none")))));
672
+ if (category !== "" && root.key !== category) return;
673
+ var groupSkills = (root.skills || []).filter(function (skill) {
674
+ return matchSkillQuery(skillSearchMeta(skill, root, t), query);
675
+ });
676
+ if (hasQuery && groupSkills.length === 0) return;
677
+ visibleRoots.push({ root: root, groupSkills: groupSkills });
454
678
  });
679
+ if (hasQuery && visibleRoots.length === 0) {
680
+ nodes.push(h("div", { key: "empty-search", className: "dssm-empty-filter" },
681
+ h("div", null, t("empty.search", { all: t("filter.all") })),
682
+ h("button", { type: "button", className: "dssm-btn dssm-btn-secondary", onClick: resetFilters }, t("empty.search.reset"))));
683
+ } else {
684
+ visibleRoots.forEach(function (item) {
685
+ var root = item.root;
686
+ var groupSkills = item.groupSkills;
687
+ nodes.push(h("div", { key: root.key, className: "dssm-group" },
688
+ h("div", { className: "dssm-group-head" }, h("h3", { className: "dssm-group-title" }, translateOrFallback(t, "root." + root.key, root.label)), h("span", { className: "dssm-count" }, t("summary.group", { count: groupSkills.length })), h("span", { className: "dssm-path", title: root.path }, root.path)),
689
+ root.exists === false ? h("div", { className: "dssm-empty" }, root.mutable ? t("empty.dir.uncreated") : t("empty.dir.missing")) : (groupSkills.length ? groupSkills.map(function (skill) { return skillRow(skill, root); }) : h("div", { className: "dssm-empty" }, t("empty.skills.none")))));
690
+ });
691
+ }
455
692
  } else if (snapshot.loading) {
456
693
  nodes.push(h("div", { key: "loading", className: "dssm-note" }, t("loading")));
457
694
  }
@@ -522,8 +759,16 @@ window.__ModuleLoader__.load({
522
759
  module.exports.DICT = DICT;
523
760
  module.exports.translateError = translateError;
524
761
  module.exports.isSkillEnabled = isSkillEnabled;
762
+ module.exports.normalizeSkillQuery = normalizeSkillQuery;
763
+ module.exports.matchSkillQuery = matchSkillQuery;
764
+ module.exports.filterSkills = filterSkills;
525
765
  module.exports.parseApiResponse = parseApiResponse;
526
766
  module.exports.trapModalFocus = trapModalFocus;
527
767
  return module.exports;
528
768
  }
529
769
  });
770
+
771
+
772
+
773
+
774
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-skills-manager",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "NPM-installable DSH Web plugin for managing local skills and viewing shared Agent skills safely.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {