@sparkelf/dsh-client-ui-skill-center 0.2.1-alpha.29 → 0.2.1-alpha.30
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.i18n.yaml +2 -2
- package/README.md +4 -3
- package/README.zh.md +4 -3
- package/lib/client.js +247 -67
- package/lib/index.js +179 -22
- package/lib/types/client/api.d.ts +34 -1
- package/lib/types/client/locales.d.ts +10 -2
- package/lib/types/client/slot-contract.d.ts +7 -4
- package/lib/types/index.d.ts +32 -1
- package/lib/types/translations.d.ts +50 -0
- package/package.json +42 -42
- package/LICENSE +0 -21
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/plus/ui-skill-center/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: ffac1f7d3aeb0e73c23f99b728b65f5a3e776884
|
|
6
|
+
README.zh.md: 70e530cb1f95d6b365358f8fb96a514ab5379770
|
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A skill center for the dsh Web sidebar: browses loaded skills grouped by source,
|
|
|
14
14
|
## What it does
|
|
15
15
|
|
|
16
16
|
- **Sidebar entry** "Skill Center" opens a panel, registered through the official `sidebar.panellist` slot and the keyed `main` page slot — the same slots the Plugins panel uses, so the shell owns the row, label, and selected state.
|
|
17
|
-
- **Skills list**: skills grouped by discovery source (bundled / runtime / `~/.agents/skills` / `~/.dsh/skills` / project `.agents/skills` / project `.dsh/skills` / custom roots),
|
|
17
|
+
- **Skills list**: skills grouped by discovery source (bundled / runtime / `~/.agents/skills` / `~/.dsh/skills` / project `.agents/skills` / project `.dsh/skills` / custom roots). A card carries the skill's summary — its translated one when a translation sits beside the skill, otherwise the registry description — and a search box filters by name, description, summary, or when-to-use.
|
|
18
|
+
- **Details**: a card opens the skill's own `SKILL.md` body in a scrolling block, under the metadata row. The body is read when the dialog opens, so a long skill costs nothing until it is asked for.
|
|
18
19
|
- **Enable / disable**: rewrites `disable-model-invocation` in the skill's YAML frontmatter. The field is edited in place rather than re-serialized, so comments, key order, and the body survive.
|
|
19
20
|
- **Create**: writes a standard `SKILL.md` under the user or project root.
|
|
20
21
|
- **Delete**: moves the skill into a `.trash` sibling directory, where it can be restored.
|
|
@@ -33,7 +34,7 @@ A skill center for the dsh Web sidebar: browses loaded skills grouped by source,
|
|
|
33
34
|
|
|
34
35
|
Reading goes through the official `ctx.skills` registry, so the catalog and its hot reload are the harness's rather than a second scanner's. The registry's summaries deliberately omit file paths — they are invocation-neutral metadata — so a write resolves the path through `ctx.skills.get()` when an action actually needs to touch a file. `collect()` caches the catalog those calls share, so the resolve is not a second scan.
|
|
35
36
|
|
|
36
|
-
The three writes have no official equivalent; the registry is read-only.
|
|
37
|
+
The three writes have no official equivalent; the registry is read-only. The content route resolves the file the panel listed first, then through `ctx.skills.get()`, then through the configured `translationRoot`: a bundled skill serves its body from inside an installed package, where the registry reports a path or a provider raises, so one source is never the only answer.
|
|
37
38
|
|
|
38
39
|
Every colour, border, and label weight comes from the shared alias tokens (`--dsw-alias-*`), and the sidebar glyph is `IconSkillOutline16` from `@deepseek-ai/dsh-client-ui-primitives`, so the panel and its row match the official panels beside them.
|
|
39
40
|
|
|
@@ -54,7 +55,7 @@ None; no string this package owns reaches a model request, so a request's cachea
|
|
|
54
55
|
No runtime invariant companion is published. The package owns no durable package-local state: the route handlers read the skill registry and write skill files, and every observable outcome is covered by the route tests.
|
|
55
56
|
|
|
56
57
|
- **Deletion refuses a symlinked skill**: a skill discovered through a symlink has no single owning directory to move, so the delete action is withheld for it.
|
|
57
|
-
- **The list omits file paths**:
|
|
58
|
+
- **The list omits file paths**: a registry-served row has no path, so the panel can neither offer its deletion nor read its body by path; whether either applies is known only when the action is attempted.
|
|
58
59
|
- **Grouping follows the discovery source label, not the file path**: a provider that reports an unrecognized source lands in the custom group.
|
|
59
60
|
|
|
60
61
|
<a id="dev-note"></a>
|
package/README.zh.md
CHANGED
|
@@ -16,7 +16,8 @@ dsh Web 侧边栏的技能中心:按来源分组浏览已加载的技能、启
|
|
|
16
16
|
## 功能
|
|
17
17
|
|
|
18
18
|
- **侧边栏入口**「技能中心」打开一个面板,它通过官方 `sidebar.panellist` 槽位与键控的 `main` 页面槽位注册 —— 与「插件」面板用的是同一批槽位,因此行、标签与选中态由外壳负责。
|
|
19
|
-
- **技能列表**:按发现来源分组(系统内置 / 运行时 / `~/.agents/skills` / `~/.dsh/skills` / 项目 `.agents/skills` / 项目 `.dsh/skills` /
|
|
19
|
+
- **技能列表**:按发现来源分组(系统内置 / 运行时 / `~/.agents/skills` / `~/.dsh/skills` / 项目 `.agents/skills` / 项目 `.dsh/skills` / 自定义目录)。卡片只带一句简介 —— 技能旁有翻译时用译文,否则用注册表的描述 —— 搜索框按名称、描述、简介或何时使用过滤。
|
|
20
|
+
- **详情**:卡片打开后,在元数据行下方以滚动区块展示该技能自己的 `SKILL.md` 正文。正文在弹窗打开时才读取,因此长技能在没人点开前不产生任何开销。
|
|
20
21
|
- **启用 / 停用**:改写技能 YAML 前置块里的 `disable-model-invocation`。该字段是就地编辑而非重新序列化,因此注释、键顺序与正文都原样保留。
|
|
21
22
|
- **新建**:在用户级或项目级根目录下写入一个标准的 `SKILL.md`。
|
|
22
23
|
- **删除**:把技能移入同级的 `.trash` 目录,可从中恢复。
|
|
@@ -37,7 +38,7 @@ dsh Web 侧边栏的技能中心:按来源分组浏览已加载的技能、启
|
|
|
37
38
|
|
|
38
39
|
读取走官方的 `ctx.skills` 注册表,因此目录与其热更新属于 harness 本身,而不是第二套扫描器。注册表的摘要有意省略文件路径 —— 它们是调用无关元数据 —— 所以写操作在真正需要触碰文件时通过 `ctx.skills.get()` 解析路径。这些调用共享的目录由 `collect()` 缓存,因此该解析不是第二次全量扫描。
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
三个写操作在官方没有等价物;注册表是只读的。正文路由先解析面板列出的文件,再走 `ctx.skills.get()`,最后走配置的 `translationRoot`:内置技能的正文安装在已安装的包内,注册表要么报告路径、要么提供者抛错,因此任何单一来源都不能是唯一答案。
|
|
41
42
|
|
|
42
43
|
每一个颜色、边框与标签字重都来自共享别名 token(`--dsw-alias-*`),侧边栏图标是 `@deepseek-ai/dsh-client-ui-primitives` 的 `IconSkillOutline16`,因此面板与其行与旁边的官方面板一致。
|
|
43
44
|
|
|
@@ -61,7 +62,7 @@ host 半边挂载 `/api/dsh-skill-center` 路由族;client 半边注册侧边
|
|
|
61
62
|
No runtime invariant companion is published. 本包不拥有任何持久化的包内状态:路由处理器读取技能注册表并写入技能文件,每一个可观察结果都由路由测试覆盖。
|
|
62
63
|
|
|
63
64
|
- **删除会拒绝符号链接的技能**:通过符号链接发现的技能没有唯一归属目录可移动,因此对它不提供删除操作。
|
|
64
|
-
-
|
|
65
|
+
- **列表省略文件路径**:注册表提供的行没有路径,因此面板既无法为它提供删除,也无法按路径读取它的正文;这两者是否适用,只有真正执行该操作时才知道。
|
|
65
66
|
- **分组跟随发现来源标签,而不是文件路径**:报告了无法识别的来源的提供者会落到自定义分组。
|
|
66
67
|
|
|
67
68
|
<a id="dev-note"></a>
|
package/lib/client.js
CHANGED
|
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region \0dsh-css:/root/projects/deepseek-harness-plus/packages/plus/ui-skill-center/src/client/SkillCenterPage.module.css.mjs
|
|
11
|
-
const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;align-items:center;gap:
|
|
11
|
+
const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);flex-direction:column;align-items:center;gap:16px;padding:24px clamp(24px,4vw,40px) 40px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:960px}.DGiSta_pageHead{justify-content:space-between;align-items:center;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:16px;display:flex}.DGiSta_iconButton{width:28px;height:28px;color:var(--dsw-alias-label-caption);cursor:pointer;background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;display:inline-flex}.DGiSta_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.DGiSta_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_iconWrap{display:inline-flex}.DGiSta_search{width:100%}.DGiSta_group{min-width:0}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 8px;font-size:14px;font-weight:500;line-height:20px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400}.DGiSta_cards{grid-template-columns:repeat(auto-fill,minmax(272px,1fr));align-items:start;gap:12px;margin:0;padding:0;list-style:none;display:grid}.DGiSta_card{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;align-self:start;gap:10px;min-width:0;padding:12px;display:flex}.DGiSta_cardHead{align-items:center;gap:10px;min-width:0;display:flex}.DGiSta_cardIcon{corner-shape:round;background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}.DGiSta_cardForeign{opacity:.72}.DGiSta_workspaceBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);vertical-align:middle;border-radius:4px;margin-left:8px;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.DGiSta_cardTitleRow{flex:1;align-items:center;gap:8px;min-width:0;display:flex}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{color:var(--dsw-alias-state-business-primary);white-space:nowrap;background:#ecf1fc;border:0;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:500;line-height:16px}.DGiSta_markOff,.DGiSta_markDanger{background:#ecf1fc}.DGiSta_detail{flex-direction:column;gap:12px;margin:0;display:flex}.DGiSta_cardActions{flex:none;align-items:center;gap:4px;display:inline-flex}.DGiSta_detailRow{flex-direction:column;gap:2px;display:flex}.DGiSta_detailLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}.DGiSta_detailValue{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:18px}.DGiSta_cardEnd{flex:none;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;padding-top:2px;display:flex}.DGiSta_cardBody{flex-direction:column;gap:6px;min-width:0;display:flex}.DGiSta_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:10px;width:36px;height:20px;transition:background .12s;position:relative}.DGiSta_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_switchOn{background:var(--dsw-alias-state-success-primary)}.DGiSta_switch:after{content:\"\";corner-shape:round;background:var(--dsw-alias-bg-layer-1);border-radius:50%;width:16px;height:16px;transition:transform .12s;position:absolute;top:2px;left:2px}.DGiSta_switchOn:after{transform:translate(16px)}.DGiSta_dialog{width:min(560px,100%)}.DGiSta_form{flex-direction:column;gap:12px;display:flex}.DGiSta_field{flex-direction:column;gap:4px;display:flex}.DGiSta_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.DGiSta_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;padding:0 10px;font-size:13px}.DGiSta_textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);min-height:80px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small);resize:vertical;background:0 0;border-radius:8px;padding:8px 10px;font-size:12px}.DGiSta_status{text-align:center;color:var(--dsw-alias-label-tertiary);padding:32px 0;font-size:13px}.DGiSta_statusError{color:var(--dsw-alias-state-error-primary)}.DGiSta_cardSummary{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_body{flex-direction:column;gap:4px;min-width:0;margin-top:16px;display:flex}.DGiSta_bodyLabel{color:var(--dsw-alias-label-caption);margin:0;font-size:11px;font-weight:500;line-height:16px}.DGiSta_bodyPre{background:var(--dsw-alias-markdown-code-block);max-height:46vh;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small-font-family);font-size:var(--dsw-font-markdown-code-block-small-font-size);line-height:var(--dsw-font-markdown-code-block-small-line-height);white-space:pre-wrap;overflow-wrap:anywhere;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:8px;margin:0;padding:12px;overflow:auto}.DGiSta_bodyStatus{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.DGiSta_bodyError{color:var(--dsw-alias-state-error-primary);}.DGiSta_progressRow{align-items:center;gap:8px;margin-bottom:12px;display:flex}.DGiSta_progressLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px;line-height:18px}.DGiSta_progressTrack{background:var(--dsw-alias-border-l2);border-radius:2px;flex:1;height:4px;overflow:hidden}.DGiSta_progressFill{background:var(--dsw-alias-label-primary);border-radius:2px;height:100%;transition:width .2s;display:block}.DGiSta_progressCount{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:18px}";
|
|
12
12
|
const tagId = "@sparkelf/dsh-client-ui-skill-center/SkillCenterPage.module.css";
|
|
13
13
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
14
14
|
const tag = document.createElement("style");
|
|
@@ -18,26 +18,33 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
document.head.appendChild(tag);
|
|
19
19
|
}
|
|
20
20
|
var SkillCenterPage_module_css_default = {
|
|
21
|
-
"
|
|
21
|
+
"body": "DGiSta_body",
|
|
22
|
+
"bodyError": "DGiSta_bodyError",
|
|
23
|
+
"bodyLabel": "DGiSta_bodyLabel",
|
|
24
|
+
"bodyPre": "DGiSta_bodyPre",
|
|
25
|
+
"bodyStatus": "DGiSta_bodyStatus",
|
|
22
26
|
"card": "DGiSta_card",
|
|
27
|
+
"cardActions": "DGiSta_cardActions",
|
|
23
28
|
"cardBody": "DGiSta_cardBody",
|
|
24
|
-
"cardDesc": "DGiSta_cardDesc",
|
|
25
29
|
"cardEnd": "DGiSta_cardEnd",
|
|
26
30
|
"cardForeign": "DGiSta_cardForeign",
|
|
27
31
|
"cardHead": "DGiSta_cardHead",
|
|
28
32
|
"cardIcon": "DGiSta_cardIcon",
|
|
29
33
|
"cardMarks": "DGiSta_cardMarks",
|
|
34
|
+
"cardSummary": "DGiSta_cardSummary",
|
|
30
35
|
"cardTitle": "DGiSta_cardTitle",
|
|
31
36
|
"cardTitleRow": "DGiSta_cardTitleRow",
|
|
32
|
-
"cardWhenToUse": "DGiSta_cardWhenToUse",
|
|
33
37
|
"cards": "DGiSta_cards",
|
|
38
|
+
"detail": "DGiSta_detail",
|
|
39
|
+
"detailLabel": "DGiSta_detailLabel",
|
|
40
|
+
"detailRow": "DGiSta_detailRow",
|
|
41
|
+
"detailValue": "DGiSta_detailValue",
|
|
34
42
|
"dialog": "DGiSta_dialog",
|
|
35
43
|
"field": "DGiSta_field",
|
|
36
44
|
"fieldLabel": "DGiSta_fieldLabel",
|
|
37
45
|
"form": "DGiSta_form",
|
|
38
46
|
"group": "DGiSta_group",
|
|
39
47
|
"groupCount": "DGiSta_groupCount",
|
|
40
|
-
"groupHint": "DGiSta_groupHint",
|
|
41
48
|
"groupTitle": "DGiSta_groupTitle",
|
|
42
49
|
"iconButton": "DGiSta_iconButton",
|
|
43
50
|
"iconWrap": "DGiSta_iconWrap",
|
|
@@ -45,11 +52,14 @@ window.__ModuleLoader__.load({
|
|
|
45
52
|
"mark": "DGiSta_mark",
|
|
46
53
|
"markDanger": "DGiSta_markDanger",
|
|
47
54
|
"markOff": "DGiSta_markOff",
|
|
48
|
-
"markOn": "DGiSta_markOn",
|
|
49
55
|
"page": "DGiSta_page",
|
|
50
56
|
"pageHead": "DGiSta_pageHead",
|
|
51
|
-
"pageIntro": "DGiSta_pageIntro",
|
|
52
57
|
"pageTitle": "DGiSta_pageTitle",
|
|
58
|
+
"progressCount": "DGiSta_progressCount",
|
|
59
|
+
"progressFill": "DGiSta_progressFill",
|
|
60
|
+
"progressLabel": "DGiSta_progressLabel",
|
|
61
|
+
"progressRow": "DGiSta_progressRow",
|
|
62
|
+
"progressTrack": "DGiSta_progressTrack",
|
|
53
63
|
"search": "DGiSta_search",
|
|
54
64
|
"status": "DGiSta_status",
|
|
55
65
|
"statusError": "DGiSta_statusError",
|
|
@@ -72,7 +82,9 @@ window.__ModuleLoader__.load({
|
|
|
72
82
|
list: "/api/dsh-skill-center/list",
|
|
73
83
|
setEnabled: "/api/dsh-skill-center/set-enabled",
|
|
74
84
|
create: "/api/dsh-skill-center/create",
|
|
75
|
-
remove: "/api/dsh-skill-center/delete"
|
|
85
|
+
remove: "/api/dsh-skill-center/delete",
|
|
86
|
+
content: "/api/dsh-skill-center/content",
|
|
87
|
+
translationProgress: "/api/dsh-skill-center/translation-progress"
|
|
76
88
|
};
|
|
77
89
|
/** One thrown API error carrying the host's message. */
|
|
78
90
|
var ApiError = class extends Error {};
|
|
@@ -111,6 +123,27 @@ window.__ModuleLoader__.load({
|
|
|
111
123
|
}
|
|
112
124
|
});
|
|
113
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* One skill's instruction body, without the YAML frontmatter the card already
|
|
128
|
+
* shows as metadata.
|
|
129
|
+
*
|
|
130
|
+
* The path the list served travels back with the request: a row from another
|
|
131
|
+
* workspace is read from the file that was listed rather than from whatever the
|
|
132
|
+
* viewing workspace's registry resolves by name.
|
|
133
|
+
*/
|
|
134
|
+
async content(name, path, cwd) {
|
|
135
|
+
const params = new URLSearchParams({ name });
|
|
136
|
+
if (path !== void 0 && path !== "") params.set("path", path);
|
|
137
|
+
if (cwd !== void 0 && cwd !== "") params.set("cwd", cwd);
|
|
138
|
+
return this.request(API.content + "?" + params.toString());
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The translated share of the skill set, or undefined when the host has not
|
|
142
|
+
* been told where the translations live (the route then answers `{}`).
|
|
143
|
+
*/
|
|
144
|
+
async translationProgress() {
|
|
145
|
+
return (await this.request(API.translationProgress)).progress;
|
|
146
|
+
}
|
|
114
147
|
async request(path, options = {}) {
|
|
115
148
|
const init = { method: options.method ?? "GET" };
|
|
116
149
|
if (options.body !== void 0) {
|
|
@@ -157,13 +190,59 @@ window.__ModuleLoader__.load({
|
|
|
157
190
|
});
|
|
158
191
|
}
|
|
159
192
|
/**
|
|
160
|
-
* One skill row.
|
|
161
|
-
* @param props - the entry,
|
|
193
|
+
* One skill row, with the dialog that shows the skill's own Markdown.
|
|
194
|
+
* @param props - the entry, the API client and viewing context its body is read
|
|
195
|
+
* with, its toggle, and its delete action.
|
|
162
196
|
* @returns the row.
|
|
163
197
|
*/
|
|
164
|
-
function SkillCard({ skill, t, onToggle, onDelete }) {
|
|
198
|
+
function SkillCard({ skill, api, cwd, t, onToggle, onDelete }) {
|
|
199
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
200
|
+
const [body, setBody] = (0, react.useState)({ kind: "loading" });
|
|
201
|
+
(0, react.useEffect)(() => {
|
|
202
|
+
if (!open) return;
|
|
203
|
+
let cancelled = false;
|
|
204
|
+
setBody({ kind: "loading" });
|
|
205
|
+
api.content(skill.name, skill.path, cwd).then((payload) => {
|
|
206
|
+
if (!cancelled) setBody({
|
|
207
|
+
kind: "ready",
|
|
208
|
+
content: payload.content
|
|
209
|
+
});
|
|
210
|
+
}).catch((error) => {
|
|
211
|
+
if (!cancelled) setBody({
|
|
212
|
+
kind: "failed",
|
|
213
|
+
message: error instanceof Error ? error.message : String(error)
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
return () => {
|
|
217
|
+
cancelled = true;
|
|
218
|
+
};
|
|
219
|
+
}, [
|
|
220
|
+
open,
|
|
221
|
+
api,
|
|
222
|
+
skill.name,
|
|
223
|
+
skill.path,
|
|
224
|
+
cwd
|
|
225
|
+
]);
|
|
226
|
+
const foreign = skill.isActiveWorkspace === false && skill.workspaceName !== void 0;
|
|
227
|
+
const details = [];
|
|
228
|
+
if (skill.whenToUse !== void 0) details.push({
|
|
229
|
+
key: "detail.whenToUse",
|
|
230
|
+
value: skill.whenToUse
|
|
231
|
+
});
|
|
232
|
+
details.push({
|
|
233
|
+
key: "detail.source",
|
|
234
|
+
value: skill.level
|
|
235
|
+
});
|
|
236
|
+
if (skill.provider !== void 0) details.push({
|
|
237
|
+
key: "detail.provider",
|
|
238
|
+
value: skill.provider
|
|
239
|
+
});
|
|
240
|
+
if (skill.path !== void 0) details.push({
|
|
241
|
+
key: "detail.path",
|
|
242
|
+
value: skill.path
|
|
243
|
+
});
|
|
165
244
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
166
|
-
className:
|
|
245
|
+
className: foreign ? SkillCenterPage_module_css_default.card + " " + SkillCenterPage_module_css_default.cardForeign : SkillCenterPage_module_css_default.card,
|
|
167
246
|
children: [
|
|
168
247
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
169
248
|
className: SkillCenterPage_module_css_default.cardHead,
|
|
@@ -174,7 +253,7 @@ window.__ModuleLoader__.load({
|
|
|
174
253
|
className: SkillCenterPage_module_css_default.cardTitleRow,
|
|
175
254
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
176
255
|
className: SkillCenterPage_module_css_default.cardTitle,
|
|
177
|
-
children: skill.name
|
|
256
|
+
children: skill.i18n?.title ?? skill.name
|
|
178
257
|
}), skill.workspaceName === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
179
258
|
className: SkillCenterPage_module_css_default.workspaceBadge,
|
|
180
259
|
children: skill.workspaceName
|
|
@@ -183,33 +262,26 @@ window.__ModuleLoader__.load({
|
|
|
183
262
|
}),
|
|
184
263
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
185
264
|
className: SkillCenterPage_module_css_default.cardBody,
|
|
186
|
-
children: [
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
skill.linked === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
207
|
-
className: SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markDanger,
|
|
208
|
-
children: t("skill.linked")
|
|
209
|
-
}) : null
|
|
210
|
-
]
|
|
211
|
-
})
|
|
212
|
-
]
|
|
265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
266
|
+
className: SkillCenterPage_module_css_default.cardSummary,
|
|
267
|
+
children: skill.i18n?.summary ?? skill.description
|
|
268
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
269
|
+
className: SkillCenterPage_module_css_default.cardMarks,
|
|
270
|
+
children: [
|
|
271
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
272
|
+
className: skill.modelInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
|
|
273
|
+
children: t("skill.modelInvocable")
|
|
274
|
+
}),
|
|
275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
276
|
+
className: skill.userInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
|
|
277
|
+
children: t("skill.userInvocable")
|
|
278
|
+
}),
|
|
279
|
+
skill.linked === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
280
|
+
className: SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markDanger,
|
|
281
|
+
children: t("skill.linked")
|
|
282
|
+
}) : null
|
|
283
|
+
]
|
|
284
|
+
})]
|
|
213
285
|
}),
|
|
214
286
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
215
287
|
className: SkillCenterPage_module_css_default.cardEnd,
|
|
@@ -221,13 +293,77 @@ window.__ModuleLoader__.load({
|
|
|
221
293
|
onClick: () => {
|
|
222
294
|
onToggle(skill, !skill.modelInvocable);
|
|
223
295
|
}
|
|
224
|
-
}),
|
|
225
|
-
|
|
226
|
-
|
|
296
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
297
|
+
className: SkillCenterPage_module_css_default.cardActions,
|
|
298
|
+
children: [skill.path === void 0 || skill.linked === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
299
|
+
variant: "outline",
|
|
300
|
+
size: "sm",
|
|
301
|
+
onClick: () => {
|
|
302
|
+
onDelete(skill);
|
|
303
|
+
},
|
|
304
|
+
children: t("action.delete")
|
|
305
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
306
|
+
variant: "outline",
|
|
307
|
+
size: "sm",
|
|
308
|
+
onClick: () => {
|
|
309
|
+
setOpen(true);
|
|
310
|
+
},
|
|
311
|
+
children: t("action.showDetail")
|
|
312
|
+
})]
|
|
313
|
+
})]
|
|
314
|
+
}),
|
|
315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
316
|
+
open,
|
|
317
|
+
onClose: () => {
|
|
318
|
+
setOpen(false);
|
|
319
|
+
},
|
|
320
|
+
title: skill.name,
|
|
321
|
+
closeLabel: t("action.close"),
|
|
322
|
+
className: SkillCenterPage_module_css_default.dialog,
|
|
323
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
324
|
+
variant: "outline",
|
|
227
325
|
onClick: () => {
|
|
228
|
-
|
|
326
|
+
setOpen(false);
|
|
229
327
|
},
|
|
230
|
-
children: t("action.
|
|
328
|
+
children: t("action.close")
|
|
329
|
+
}),
|
|
330
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
|
|
331
|
+
className: SkillCenterPage_module_css_default.detail,
|
|
332
|
+
children: details.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
333
|
+
className: SkillCenterPage_module_css_default.detailRow,
|
|
334
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
335
|
+
className: SkillCenterPage_module_css_default.detailLabel,
|
|
336
|
+
children: t(item.key)
|
|
337
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
338
|
+
className: SkillCenterPage_module_css_default.detailValue,
|
|
339
|
+
children: item.value
|
|
340
|
+
})]
|
|
341
|
+
}, item.key))
|
|
342
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
343
|
+
className: SkillCenterPage_module_css_default.body,
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
346
|
+
className: SkillCenterPage_module_css_default.bodyLabel,
|
|
347
|
+
children: t("detail.content")
|
|
348
|
+
}),
|
|
349
|
+
body.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
350
|
+
className: SkillCenterPage_module_css_default.bodyStatus,
|
|
351
|
+
children: t("content.loading")
|
|
352
|
+
}) : null,
|
|
353
|
+
body.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
354
|
+
className: SkillCenterPage_module_css_default.bodyError,
|
|
355
|
+
children: [
|
|
356
|
+
t("content.failed"),
|
|
357
|
+
": ",
|
|
358
|
+
body.message
|
|
359
|
+
]
|
|
360
|
+
}) : null,
|
|
361
|
+
body.kind === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
362
|
+
className: SkillCenterPage_module_css_default.bodyPre,
|
|
363
|
+
"data-skill-body": skill.name,
|
|
364
|
+
children: body.content
|
|
365
|
+
}) : null
|
|
366
|
+
]
|
|
231
367
|
})]
|
|
232
368
|
})
|
|
233
369
|
]
|
|
@@ -242,6 +378,7 @@ window.__ModuleLoader__.load({
|
|
|
242
378
|
const api = (0, react.useMemo)(() => new SkillApi(), []);
|
|
243
379
|
const [state, setState] = (0, react.useState)({ kind: "loading" });
|
|
244
380
|
const [query, setQuery] = (0, react.useState)("");
|
|
381
|
+
const [progress, setProgress] = (0, react.useState)(void 0);
|
|
245
382
|
const [creating, setCreating] = (0, react.useState)(false);
|
|
246
383
|
const [draft, setDraft] = (0, react.useState)({
|
|
247
384
|
name: "",
|
|
@@ -271,13 +408,22 @@ window.__ModuleLoader__.load({
|
|
|
271
408
|
(0, react.useEffect)(() => {
|
|
272
409
|
load();
|
|
273
410
|
}, [load]);
|
|
411
|
+
(0, react.useEffect)(() => {
|
|
412
|
+
let cancelled = false;
|
|
413
|
+
api.translationProgress().then((p) => {
|
|
414
|
+
if (!cancelled) setProgress(p);
|
|
415
|
+
}).catch(() => {});
|
|
416
|
+
return () => {
|
|
417
|
+
cancelled = true;
|
|
418
|
+
};
|
|
419
|
+
}, [api]);
|
|
274
420
|
const groups = (0, react.useMemo)(() => {
|
|
275
421
|
if (state.kind !== "ready") return [];
|
|
276
422
|
const q = query.trim().toLowerCase();
|
|
277
423
|
if (q === "") return state.payload.groups;
|
|
278
424
|
return state.payload.groups.map((g) => ({
|
|
279
425
|
...g,
|
|
280
|
-
skills: g.skills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || (s.whenToUse ?? "").toLowerCase().includes(q))
|
|
426
|
+
skills: g.skills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || (s.i18n?.title ?? "").toLowerCase().includes(q) || (s.i18n?.description ?? "").toLowerCase().includes(q) || (s.i18n?.summary ?? "").toLowerCase().includes(q) || (s.whenToUse ?? "").toLowerCase().includes(q))
|
|
281
427
|
})).filter((g) => g.skills.length > 0);
|
|
282
428
|
}, [state, query]);
|
|
283
429
|
const toggle = (0, react.useCallback)(async (skill, enabled) => {
|
|
@@ -352,13 +498,10 @@ window.__ModuleLoader__.load({
|
|
|
352
498
|
children: [
|
|
353
499
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
354
500
|
className: SkillCenterPage_module_css_default.pageHead,
|
|
355
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
501
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
356
502
|
className: SkillCenterPage_module_css_default.pageTitle,
|
|
357
503
|
children: t("panel")
|
|
358
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
359
|
-
className: SkillCenterPage_module_css_default.pageIntro,
|
|
360
|
-
children: t("panel.intro")
|
|
361
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
504
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
362
505
|
className: SkillCenterPage_module_css_default.toolbar,
|
|
363
506
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
364
507
|
type: "button",
|
|
@@ -464,14 +607,40 @@ window.__ModuleLoader__.load({
|
|
|
464
607
|
]
|
|
465
608
|
})
|
|
466
609
|
}),
|
|
467
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
610
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
468
611
|
className: SkillCenterPage_module_css_default.search,
|
|
612
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, {}),
|
|
469
613
|
placeholder: t("search.placeholder"),
|
|
614
|
+
"aria-label": t("search.placeholder"),
|
|
470
615
|
value: query,
|
|
471
616
|
onChange: (e) => {
|
|
472
617
|
setQuery(e.target.value);
|
|
473
618
|
}
|
|
474
619
|
}),
|
|
620
|
+
progress === void 0 || progress.done >= progress.total ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
621
|
+
className: SkillCenterPage_module_css_default.progressRow,
|
|
622
|
+
children: [
|
|
623
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
624
|
+
className: SkillCenterPage_module_css_default.progressLabel,
|
|
625
|
+
children: t("translate.label")
|
|
626
|
+
}),
|
|
627
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
628
|
+
className: SkillCenterPage_module_css_default.progressTrack,
|
|
629
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
630
|
+
className: SkillCenterPage_module_css_default.progressFill,
|
|
631
|
+
style: { width: String(progress.total === 0 ? 0 : Math.round(progress.done / progress.total * 100)) + "%" }
|
|
632
|
+
})
|
|
633
|
+
}),
|
|
634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
635
|
+
className: SkillCenterPage_module_css_default.progressCount,
|
|
636
|
+
children: [
|
|
637
|
+
progress.done,
|
|
638
|
+
" / ",
|
|
639
|
+
progress.total
|
|
640
|
+
]
|
|
641
|
+
})
|
|
642
|
+
]
|
|
643
|
+
}),
|
|
475
644
|
state.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
476
645
|
className: SkillCenterPage_module_css_default.status,
|
|
477
646
|
children: t("state.loading")
|
|
@@ -496,21 +665,16 @@ window.__ModuleLoader__.load({
|
|
|
496
665
|
className: SkillCenterPage_module_css_default.group,
|
|
497
666
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
498
667
|
className: SkillCenterPage_module_css_default.groupTitle,
|
|
499
|
-
children: [
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
children: group.skills.length
|
|
504
|
-
}),
|
|
505
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
506
|
-
className: SkillCenterPage_module_css_default.groupHint,
|
|
507
|
-
children: group.hint
|
|
508
|
-
})
|
|
509
|
-
]
|
|
668
|
+
children: [group.title, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
669
|
+
className: SkillCenterPage_module_css_default.groupCount,
|
|
670
|
+
children: group.skills.length
|
|
671
|
+
})]
|
|
510
672
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
511
673
|
className: SkillCenterPage_module_css_default.cards,
|
|
512
674
|
children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillCard, {
|
|
513
675
|
skill,
|
|
676
|
+
api,
|
|
677
|
+
cwd: state.kind === "ready" ? state.payload.cwd : scope,
|
|
514
678
|
t,
|
|
515
679
|
onToggle: (s, enabled) => {
|
|
516
680
|
toggle(s, enabled);
|
|
@@ -540,18 +704,26 @@ window.__ModuleLoader__.load({
|
|
|
540
704
|
/** Simplified Chinese dictionary. */
|
|
541
705
|
const zh = {
|
|
542
706
|
"panel": "技能中心",
|
|
543
|
-
"panel.intro": "浏览已加载的技能,启用或停用模型调用",
|
|
544
707
|
"search.placeholder": "搜索技能…",
|
|
545
|
-
"search.clear": "清除搜索",
|
|
546
708
|
"search.noMatches": "无匹配技能",
|
|
547
709
|
"action.refresh": "刷新",
|
|
548
710
|
"action.create": "新建技能",
|
|
549
711
|
"action.delete": "删除",
|
|
712
|
+
"action.showDetail": "详情",
|
|
713
|
+
"action.close": "关闭",
|
|
714
|
+
"translate.label": "中文翻译",
|
|
715
|
+
"detail.whenToUse": "何时使用",
|
|
716
|
+
"detail.source": "来源",
|
|
717
|
+
"detail.provider": "提供方",
|
|
718
|
+
"detail.path": "位置",
|
|
719
|
+
"detail.content": "技能内容",
|
|
550
720
|
"action.cancel": "取消",
|
|
551
721
|
"action.confirm": "确定",
|
|
552
722
|
"state.loading": "正在加载技能…",
|
|
553
723
|
"state.empty": "该分组暂无技能",
|
|
554
724
|
"state.failed": "技能列表加载失败",
|
|
725
|
+
"content.loading": "正在读取技能正文…",
|
|
726
|
+
"content.failed": "技能正文加载失败",
|
|
555
727
|
"skill.enabled": "已启用",
|
|
556
728
|
"skill.disabled": "已停用",
|
|
557
729
|
"skill.modelInvocable": "模型可调用",
|
|
@@ -570,18 +742,26 @@ window.__ModuleLoader__.load({
|
|
|
570
742
|
/** English dictionary. */
|
|
571
743
|
const en = {
|
|
572
744
|
"panel": "Skill Center",
|
|
573
|
-
"panel.intro": "Browse loaded skills and control model invocation",
|
|
574
745
|
"search.placeholder": "Search skills…",
|
|
575
|
-
"search.clear": "Clear search",
|
|
576
746
|
"search.noMatches": "No matching skills",
|
|
577
747
|
"action.refresh": "Refresh",
|
|
578
748
|
"action.create": "New skill",
|
|
579
749
|
"action.delete": "Delete",
|
|
750
|
+
"action.showDetail": "Details",
|
|
751
|
+
"action.close": "Close",
|
|
752
|
+
"translate.label": "Chinese",
|
|
753
|
+
"detail.whenToUse": "When to use",
|
|
754
|
+
"detail.source": "Source",
|
|
755
|
+
"detail.provider": "Provider",
|
|
756
|
+
"detail.path": "Location",
|
|
757
|
+
"detail.content": "Skill content",
|
|
580
758
|
"action.cancel": "Cancel",
|
|
581
759
|
"action.confirm": "Confirm",
|
|
582
760
|
"state.loading": "Loading skills…",
|
|
583
761
|
"state.empty": "No skills in this group",
|
|
584
762
|
"state.failed": "Could not load the skill list",
|
|
763
|
+
"content.loading": "Loading the skill content…",
|
|
764
|
+
"content.failed": "Could not load the skill content",
|
|
585
765
|
"skill.enabled": "Enabled",
|
|
586
766
|
"skill.disabled": "Disabled",
|
|
587
767
|
"skill.modelInvocable": "Model invocable",
|
package/lib/index.js
CHANGED
|
@@ -2,11 +2,75 @@ import { existsSync, lstatSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
//#region lib/types/translations.js
|
|
6
|
+
/**
|
|
7
|
+
* Chinese metadata for the skill center, read from each skill's own directory.
|
|
8
|
+
*
|
|
9
|
+
* The panel renders what the official registry routes on, so the registry's English
|
|
10
|
+
* description is what decides whether the model invokes a skill. Replacing it would
|
|
11
|
+
* change that decision, so a translation is carried alongside and applied only at
|
|
12
|
+
* render time: the card shows Chinese, the model keeps reading English.
|
|
13
|
+
*
|
|
14
|
+
* A skill keeps its translation in its own directory (`<skill>/i18n/zh.json`), which
|
|
15
|
+
* lets a skill and its Chinese travel together and ship from one clone. The skill
|
|
16
|
+
* file itself is located through the registry, so the translation is found even when
|
|
17
|
+
* the skill resolves through a symlink into another repository.
|
|
18
|
+
*
|
|
19
|
+
* @module @sparkelf/dsh-client-ui-skill-center/translations
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Read one skill's translation from beside its resolved skill file.
|
|
23
|
+
*
|
|
24
|
+
* A skill without a translation, or with an unreadable or malformed one, is returned
|
|
25
|
+
* as undefined: the panel then shows the registry values, so a partial translation
|
|
26
|
+
* set degrades to English rather than to a broken card.
|
|
27
|
+
*
|
|
28
|
+
* @param skillFilePath - absolute path of the skill's SKILL.md, when the registry reports one.
|
|
29
|
+
* @returns the translation, or undefined when none is usable.
|
|
30
|
+
*/
|
|
31
|
+
async function readSkillTranslation(skillFilePath) {
|
|
32
|
+
if (skillFilePath === void 0) return void 0;
|
|
33
|
+
let record;
|
|
34
|
+
try {
|
|
35
|
+
record = JSON.parse(await readFile(join(dirname(skillFilePath), "i18n", "zh.json"), "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const { title, description, summary } = record;
|
|
40
|
+
if (typeof title !== "string" || typeof description !== "string" || typeof summary !== "string") return void 0;
|
|
41
|
+
return {
|
|
42
|
+
title,
|
|
43
|
+
description,
|
|
44
|
+
summary
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Read translation progress for the panel's progress bar.
|
|
49
|
+
*
|
|
50
|
+
* @param translationRoot - repository holding the skill set, whose `i18n/.progress.json` the script writes.
|
|
51
|
+
* @returns the progress, or undefined when no translation run has recorded one.
|
|
52
|
+
*/
|
|
53
|
+
async function readTranslationProgress(translationRoot) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = JSON.parse(await readFile(join(translationRoot, "i18n", ".progress.json"), "utf8"));
|
|
56
|
+
if (typeof raw.total !== "number" || typeof raw.done !== "number") return void 0;
|
|
57
|
+
return {
|
|
58
|
+
total: raw.total,
|
|
59
|
+
done: raw.done,
|
|
60
|
+
current: typeof raw.current === "string" ? raw.current : null,
|
|
61
|
+
finishedAt: typeof raw.finishedAt === "string" ? raw.finishedAt : null
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
5
68
|
//#region lib/types/index.js
|
|
6
69
|
/**
|
|
7
70
|
* Skill center — host half. Serves the skill-center data source over the
|
|
8
|
-
* `/api/dsh-skill-center` route family: list grouped by source,
|
|
9
|
-
* model invocation, create, delete into a recoverable
|
|
71
|
+
* `/api/dsh-skill-center` route family: list grouped by source, read one skill's
|
|
72
|
+
* body, enable or disable model invocation, create, delete into a recoverable
|
|
73
|
+
* trash, and health.
|
|
10
74
|
*
|
|
11
75
|
* Reading goes through the official `ctx.skills` registry, so the catalog, its
|
|
12
76
|
* provider precedence, and its hot reload are the harness's rather than a second
|
|
@@ -30,7 +94,9 @@ const ROUTES = {
|
|
|
30
94
|
setEnabled: "/api/dsh-skill-center/set-enabled",
|
|
31
95
|
create: "/api/dsh-skill-center/create",
|
|
32
96
|
delete: "/api/dsh-skill-center/delete",
|
|
33
|
-
|
|
97
|
+
content: "/api/dsh-skill-center/content",
|
|
98
|
+
health: "/api/dsh-skill-center/health",
|
|
99
|
+
translationProgress: "/api/dsh-skill-center/translation-progress"
|
|
34
100
|
};
|
|
35
101
|
/**
|
|
36
102
|
* Groups the panel renders, in order. The level a skill resolves to selects its
|
|
@@ -39,38 +105,31 @@ const ROUTES = {
|
|
|
39
105
|
const SOURCE_GROUPS = [
|
|
40
106
|
{
|
|
41
107
|
key: "bundled",
|
|
42
|
-
title: "系统内置"
|
|
43
|
-
hint: "随 Harness 一同安装,对所有项目生效"
|
|
108
|
+
title: "系统内置"
|
|
44
109
|
},
|
|
45
110
|
{
|
|
46
111
|
key: "project-dsh",
|
|
47
|
-
title: "项目 .dsh/skills"
|
|
48
|
-
hint: "位于项目目录,仅对该工作区生效"
|
|
112
|
+
title: "项目 .dsh/skills"
|
|
49
113
|
},
|
|
50
114
|
{
|
|
51
115
|
key: "project-agents",
|
|
52
|
-
title: "项目 .agents/skills"
|
|
53
|
-
hint: "位于项目目录,随项目提交,对协作者共享"
|
|
116
|
+
title: "项目 .agents/skills"
|
|
54
117
|
},
|
|
55
118
|
{
|
|
56
119
|
key: "custom",
|
|
57
|
-
title: "自定义目录"
|
|
58
|
-
hint: "由本插件的 customSkillDirs 配置"
|
|
120
|
+
title: "自定义目录"
|
|
59
121
|
},
|
|
60
122
|
{
|
|
61
123
|
key: "user-dsh",
|
|
62
|
-
title: "用户 ~/.dsh/skills"
|
|
63
|
-
hint: "在用户 dsh 目录下,对本机所有项目生效"
|
|
124
|
+
title: "用户 ~/.dsh/skills"
|
|
64
125
|
},
|
|
65
126
|
{
|
|
66
127
|
key: "user-agents",
|
|
67
|
-
title: "用户 ~/.agents/skills"
|
|
68
|
-
hint: "在用户 agents 目录下,对本机所有项目生效"
|
|
128
|
+
title: "用户 ~/.agents/skills"
|
|
69
129
|
},
|
|
70
130
|
{
|
|
71
131
|
key: "runtime",
|
|
72
|
-
title: "运行时注册"
|
|
73
|
-
hint: "由已安装的插件在运行时注册"
|
|
132
|
+
title: "运行时注册"
|
|
74
133
|
}
|
|
75
134
|
];
|
|
76
135
|
/**
|
|
@@ -240,6 +299,16 @@ function localSkillRoots(deps, projectRoots, activeRoot) {
|
|
|
240
299
|
return roots;
|
|
241
300
|
}
|
|
242
301
|
/**
|
|
302
|
+
* Strip the leading YAML frontmatter from a skill file.
|
|
303
|
+
*
|
|
304
|
+
* @param source - the skill file's complete content.
|
|
305
|
+
* @returns the body below the closing delimiter, or the whole file when it declares none.
|
|
306
|
+
*/
|
|
307
|
+
function stripFrontmatter(source) {
|
|
308
|
+
const match = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/.exec(source);
|
|
309
|
+
return match === null ? source : source.slice(match[0].length);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
243
312
|
* Read `disable-model-invocation` from a skill file.
|
|
244
313
|
* @param source - the skill file's content.
|
|
245
314
|
* @returns the flag when the frontmatter declares it, otherwise undefined.
|
|
@@ -311,14 +380,43 @@ async function readJson(req, maxBytes) {
|
|
|
311
380
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("request body must be a JSON object");
|
|
312
381
|
return parsed;
|
|
313
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Chinese copy for every skill the list will render, keyed by skill name.
|
|
385
|
+
*
|
|
386
|
+
* The registry omits the skill file path from its summaries, so each one is resolved
|
|
387
|
+
* through `pathOf`, which consults the catalog `collect()` already cached. A skill
|
|
388
|
+
* whose path or translation is missing simply has no entry here and renders in English.
|
|
389
|
+
*
|
|
390
|
+
* @param deps - the routes' dependencies.
|
|
391
|
+
* @param skills - skill names to look up.
|
|
392
|
+
* @param cwd - the viewing context the registry resolves against.
|
|
393
|
+
* @returns the translations found, keyed by name.
|
|
394
|
+
*/
|
|
395
|
+
async function translationsFor(deps, skills, cwd) {
|
|
396
|
+
const found = /* @__PURE__ */ new Map();
|
|
397
|
+
const root = deps.translationRoot;
|
|
398
|
+
await Promise.all(skills.map(async ({ name, path: known }) => {
|
|
399
|
+
let path = known;
|
|
400
|
+
if (path === void 0) try {
|
|
401
|
+
path = await deps.pathOf(name, cwd);
|
|
402
|
+
} catch {
|
|
403
|
+
path = void 0;
|
|
404
|
+
}
|
|
405
|
+
const translation = await readSkillTranslation(path) ?? (root === void 0 ? void 0 : await readSkillTranslation(join(root, name, "SKILL.md")));
|
|
406
|
+
if (translation !== void 0) found.set(name, translation);
|
|
407
|
+
}));
|
|
408
|
+
return found;
|
|
409
|
+
}
|
|
314
410
|
/** The list handler: group the registry snapshot and the local roots together. */
|
|
315
411
|
async function handleList(deps, cwd) {
|
|
316
412
|
const projectRoots = [...new Set(deps.activeSessionCwds().map(findProjectRoot).concat(findProjectRoot(cwd)))];
|
|
317
413
|
const snapshot = await deps.snapshotProject(cwd);
|
|
318
414
|
const byGroup = new Map(SOURCE_GROUPS.map((g) => [g.key, []]));
|
|
319
415
|
const seen = /* @__PURE__ */ new Set();
|
|
416
|
+
const translations = await translationsFor(deps, snapshot.skills.map((s) => ({ name: s.name })), cwd);
|
|
320
417
|
for (const skill of snapshot.skills) {
|
|
321
418
|
const level = levelOfSource(skill.source, skill.provider);
|
|
419
|
+
const i18n = translations.get(skill.name);
|
|
322
420
|
const entry = {
|
|
323
421
|
name: skill.name,
|
|
324
422
|
description: skill.description,
|
|
@@ -326,7 +424,8 @@ async function handleList(deps, cwd) {
|
|
|
326
424
|
provider: skill.provider,
|
|
327
425
|
modelInvocable: skill.invocation.modelInvocable,
|
|
328
426
|
userInvocable: skill.invocation.userInvocable,
|
|
329
|
-
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse }
|
|
427
|
+
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
428
|
+
...i18n === void 0 ? {} : { i18n }
|
|
330
429
|
};
|
|
331
430
|
seen.add(level + ":" + skill.name);
|
|
332
431
|
(byGroup.get(level) ?? byGroup.get("custom"))?.push(entry);
|
|
@@ -334,6 +433,7 @@ async function handleList(deps, cwd) {
|
|
|
334
433
|
for (const root of localSkillRoots(deps, projectRoots, findProjectRoot(cwd))) for (const skill of await scanSkillRoot(root.path, root.level, root.workspace)) {
|
|
335
434
|
if (seen.has(root.level + ":" + skill.name)) continue;
|
|
336
435
|
seen.add(root.level + ":" + skill.name);
|
|
436
|
+
const i18n = await readSkillTranslation(skill.path) ?? (deps.translationRoot === void 0 ? void 0 : await readSkillTranslation(join(deps.translationRoot, skill.name, "SKILL.md")));
|
|
337
437
|
byGroup.get(root.level)?.push({
|
|
338
438
|
name: skill.name,
|
|
339
439
|
description: skill.description,
|
|
@@ -345,7 +445,8 @@ async function handleList(deps, cwd) {
|
|
|
345
445
|
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
346
446
|
...skill.workspaceRoot === void 0 ? {} : { workspaceRoot: skill.workspaceRoot },
|
|
347
447
|
...skill.workspaceName === void 0 ? {} : { workspaceName: skill.workspaceName },
|
|
348
|
-
...skill.isActiveWorkspace === void 0 ? {} : { isActiveWorkspace: skill.isActiveWorkspace }
|
|
448
|
+
...skill.isActiveWorkspace === void 0 ? {} : { isActiveWorkspace: skill.isActiveWorkspace },
|
|
449
|
+
...i18n === void 0 ? {} : { i18n }
|
|
349
450
|
});
|
|
350
451
|
}
|
|
351
452
|
const groups = [];
|
|
@@ -354,7 +455,6 @@ async function handleList(deps, cwd) {
|
|
|
354
455
|
if (skills.length > 0) groups.push({
|
|
355
456
|
key: g.key,
|
|
356
457
|
title: g.title,
|
|
357
|
-
hint: g.hint,
|
|
358
458
|
skills
|
|
359
459
|
});
|
|
360
460
|
}
|
|
@@ -365,6 +465,39 @@ async function handleList(deps, cwd) {
|
|
|
365
465
|
groups
|
|
366
466
|
};
|
|
367
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* The content handler: one skill's instruction body, without its frontmatter.
|
|
470
|
+
*
|
|
471
|
+
* The registry's summaries carry metadata only, so the body is read from the file
|
|
472
|
+
* the name resolves to. The path the panel listed is preferred — the row already
|
|
473
|
+
* names its own file, and the panel lists skills from workspaces other than the one
|
|
474
|
+
* being viewed — then the registry's resolution, then the configured translation
|
|
475
|
+
* set's own copy, which is what answers for a bundled skill.
|
|
476
|
+
*
|
|
477
|
+
* @param listedPath - the path the listing served with the row, when the caller sent one.
|
|
478
|
+
*/
|
|
479
|
+
async function handleContent(deps, name, listedPath, cwd) {
|
|
480
|
+
const candidates = [];
|
|
481
|
+
if (listedPath !== void 0 && listedPath !== "") candidates.push(listedPath);
|
|
482
|
+
try {
|
|
483
|
+
const resolved = await deps.pathOf(name, cwd);
|
|
484
|
+
if (resolved !== void 0 && !candidates.includes(resolved)) candidates.push(resolved);
|
|
485
|
+
} catch {}
|
|
486
|
+
if (deps.translationRoot !== void 0) candidates.push(join(deps.translationRoot, name, SKILL_FILE));
|
|
487
|
+
for (const candidate of candidates) {
|
|
488
|
+
let source;
|
|
489
|
+
try {
|
|
490
|
+
source = await readFile(candidate, "utf8");
|
|
491
|
+
} catch {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
name,
|
|
496
|
+
content: stripFrontmatter(source)
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
throw new Error("skill \"" + name + "\" has no readable SKILL.md");
|
|
500
|
+
}
|
|
368
501
|
/** The set-enabled handler: rewrite the skill's frontmatter flag. */
|
|
369
502
|
async function handleSetEnabled(deps, body, cwd) {
|
|
370
503
|
const path = await resolveSkillPath(deps, requireString(body.name, "name"), body, cwd);
|
|
@@ -452,6 +585,14 @@ function makeRoutes(deps) {
|
|
|
452
585
|
path: ROUTES.health,
|
|
453
586
|
handler: (_req, res) => writeJson(res, 200, { ok: true })
|
|
454
587
|
},
|
|
588
|
+
{
|
|
589
|
+
kind: "exact",
|
|
590
|
+
path: ROUTES.translationProgress,
|
|
591
|
+
handler: async (_req, res) => {
|
|
592
|
+
const progress = deps.translationRoot === void 0 ? void 0 : await readTranslationProgress(deps.translationRoot);
|
|
593
|
+
writeJson(res, 200, progress === void 0 ? {} : { progress });
|
|
594
|
+
}
|
|
595
|
+
},
|
|
455
596
|
{
|
|
456
597
|
kind: "exact",
|
|
457
598
|
path: ROUTES.list,
|
|
@@ -464,6 +605,21 @@ function makeRoutes(deps) {
|
|
|
464
605
|
}
|
|
465
606
|
}
|
|
466
607
|
},
|
|
608
|
+
{
|
|
609
|
+
kind: "exact",
|
|
610
|
+
path: ROUTES.content,
|
|
611
|
+
handler: async (req, res) => {
|
|
612
|
+
try {
|
|
613
|
+
const params = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
614
|
+
const name = params.get("name") ?? "";
|
|
615
|
+
if (name === "") throw new Error("missing name");
|
|
616
|
+
writeJson(res, 200, await handleContent(deps, name, params.get("path") ?? void 0, currentCwd(deps, req)));
|
|
617
|
+
} catch (error) {
|
|
618
|
+
deps.logger.warn(error);
|
|
619
|
+
writeJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
467
623
|
{
|
|
468
624
|
kind: "exact",
|
|
469
625
|
path: ROUTES.setEnabled,
|
|
@@ -522,9 +678,10 @@ function apply(ctx, config = {}) {
|
|
|
522
678
|
activeSessionCwds: () => ctx.sessions.list().map((s) => s.header.cwd).filter((c) => typeof c === "string" && c !== ""),
|
|
523
679
|
logger: { warn: (error) => {
|
|
524
680
|
ctx.logger.warn(error);
|
|
525
|
-
} }
|
|
681
|
+
} },
|
|
682
|
+
...config.translationRoot === void 0 ? {} : { translationRoot: config.translationRoot }
|
|
526
683
|
};
|
|
527
684
|
for (const route of makeRoutes(deps)) ctx.effect(() => ctx.webServer.register(route), "ui-skill-center: route " + route.path);
|
|
528
685
|
}
|
|
529
686
|
//#endregion
|
|
530
|
-
export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, localSkillRoots, makeRoutes, name, projectSkillRoot, readDisabledFlag, scanSkillRoot, userSkillRoot, withDisabledFlag };
|
|
687
|
+
export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, localSkillRoots, makeRoutes, name, projectSkillRoot, readDisabledFlag, scanSkillRoot, stripFrontmatter, userSkillRoot, withDisabledFlag };
|
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module @sparkelf/dsh-client-ui-skill-center/client/api
|
|
6
6
|
*/
|
|
7
|
+
/** Chinese copy for one skill, when a translation file exists beside it. */
|
|
8
|
+
export interface SkillEntryTranslation {
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
}
|
|
13
|
+
/** One skill's instruction body, as the content route serves it. */
|
|
14
|
+
export interface SkillContent {
|
|
15
|
+
name: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
/** Translated share of the skill set, as the translation script recorded it. */
|
|
19
|
+
export interface TranslationProgress {
|
|
20
|
+
total: number;
|
|
21
|
+
done: number;
|
|
22
|
+
current: string | null;
|
|
23
|
+
finishedAt: string | null;
|
|
24
|
+
}
|
|
7
25
|
/** One skill entry as the host serves it. */
|
|
8
26
|
export interface SkillEntry {
|
|
9
27
|
name: string;
|
|
@@ -19,12 +37,13 @@ export interface SkillEntry {
|
|
|
19
37
|
workspaceRoot?: string;
|
|
20
38
|
workspaceName?: string;
|
|
21
39
|
isActiveWorkspace?: boolean;
|
|
40
|
+
/** Chinese copy rendered in place of the English values; the registry keeps its own. */
|
|
41
|
+
i18n?: SkillEntryTranslation;
|
|
22
42
|
}
|
|
23
43
|
/** One group as the host serves it. */
|
|
24
44
|
export interface GroupPayload {
|
|
25
45
|
key: string;
|
|
26
46
|
title: string;
|
|
27
|
-
hint: string;
|
|
28
47
|
skills: SkillEntry[];
|
|
29
48
|
}
|
|
30
49
|
/** One selectable workspace. */
|
|
@@ -70,6 +89,20 @@ export declare class SkillApi {
|
|
|
70
89
|
ok: true;
|
|
71
90
|
moved: string;
|
|
72
91
|
}>;
|
|
92
|
+
/**
|
|
93
|
+
* One skill's instruction body, without the YAML frontmatter the card already
|
|
94
|
+
* shows as metadata.
|
|
95
|
+
*
|
|
96
|
+
* The path the list served travels back with the request: a row from another
|
|
97
|
+
* workspace is read from the file that was listed rather than from whatever the
|
|
98
|
+
* viewing workspace's registry resolves by name.
|
|
99
|
+
*/
|
|
100
|
+
content(name: string, path?: string, cwd?: string): Promise<SkillContent>;
|
|
101
|
+
/**
|
|
102
|
+
* The translated share of the skill set, or undefined when the host has not
|
|
103
|
+
* been told where the translations live (the route then answers `{}`).
|
|
104
|
+
*/
|
|
105
|
+
translationProgress(): Promise<TranslationProgress | undefined>;
|
|
73
106
|
private request;
|
|
74
107
|
}
|
|
75
108
|
//# sourceMappingURL=api.d.ts.map
|
|
@@ -7,18 +7,26 @@
|
|
|
7
7
|
/** Every string this plugin renders. */
|
|
8
8
|
export interface SkillCenterLocale {
|
|
9
9
|
'panel': string;
|
|
10
|
-
'panel.intro': string;
|
|
11
10
|
'search.placeholder': string;
|
|
12
|
-
'search.clear': string;
|
|
13
11
|
'search.noMatches': string;
|
|
14
12
|
'action.refresh': string;
|
|
15
13
|
'action.create': string;
|
|
16
14
|
'action.delete': string;
|
|
15
|
+
'action.showDetail': string;
|
|
16
|
+
'action.close': string;
|
|
17
|
+
'translate.label': string;
|
|
18
|
+
'detail.whenToUse': string;
|
|
19
|
+
'detail.source': string;
|
|
20
|
+
'detail.provider': string;
|
|
21
|
+
'detail.path': string;
|
|
22
|
+
'detail.content': string;
|
|
17
23
|
'action.cancel': string;
|
|
18
24
|
'action.confirm': string;
|
|
19
25
|
'state.loading': string;
|
|
20
26
|
'state.empty': string;
|
|
21
27
|
'state.failed': string;
|
|
28
|
+
'content.loading': string;
|
|
29
|
+
'content.failed': string;
|
|
22
30
|
'skill.enabled': string;
|
|
23
31
|
'skill.disabled': string;
|
|
24
32
|
'skill.modelInvocable': string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Owner props a contributed section receives.
|
|
3
|
-
*
|
|
2
|
+
* Owner props a contributed section receives.
|
|
3
|
+
*
|
|
4
|
+
* Declared here rather than inferred at the call site: the page and the section live in
|
|
5
|
+
* different packages, so the type is the only thing keeping their props in step.
|
|
4
6
|
*/
|
|
5
7
|
export interface SkillCenterSectionOwnerProps {
|
|
6
|
-
/**
|
|
7
|
-
readonly
|
|
8
|
+
/** The page's current search text, unfiltered and untrimmed. */
|
|
9
|
+
readonly query: string;
|
|
8
10
|
}
|
|
9
11
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
10
12
|
interface SlotMap {
|
|
@@ -12,6 +14,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
12
14
|
'skill-center.section': {
|
|
13
15
|
kind: 'list';
|
|
14
16
|
scope: 'root';
|
|
17
|
+
owner: SkillCenterSectionOwnerProps;
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -11,7 +11,9 @@ export declare const ROUTES: {
|
|
|
11
11
|
readonly setEnabled: "/api/dsh-skill-center/set-enabled";
|
|
12
12
|
readonly create: "/api/dsh-skill-center/create";
|
|
13
13
|
readonly delete: "/api/dsh-skill-center/delete";
|
|
14
|
+
readonly content: "/api/dsh-skill-center/content";
|
|
14
15
|
readonly health: "/api/dsh-skill-center/health";
|
|
16
|
+
readonly translationProgress: "/api/dsh-skill-center/translation-progress";
|
|
15
17
|
};
|
|
16
18
|
/** Plugin configuration. */
|
|
17
19
|
export interface Config {
|
|
@@ -21,6 +23,20 @@ export interface Config {
|
|
|
21
23
|
dshHome?: string;
|
|
22
24
|
/** User agents config root; defaults to `$DSH_AGENTS_HOME` or `~/.agents`. */
|
|
23
25
|
agentsHome?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Skill set whose `<skill>/i18n/zh.json` files supply the panel's Chinese copy.
|
|
28
|
+
*
|
|
29
|
+
* A skill that resolves through a symlink into another repository keeps its
|
|
30
|
+
* translation in that repository, not in the deployment, so this names the
|
|
31
|
+
* checkout that holds the translated set. Unset means English only.
|
|
32
|
+
*/
|
|
33
|
+
translationRoot?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Chinese copy for one skill, rendered in place of the English values. */
|
|
36
|
+
export interface SkillEntryTranslation {
|
|
37
|
+
title: string;
|
|
38
|
+
description: string;
|
|
39
|
+
summary: string;
|
|
24
40
|
}
|
|
25
41
|
/** A skill row as the panel receives it. */
|
|
26
42
|
export interface SkillEntry {
|
|
@@ -37,12 +53,18 @@ export interface SkillEntry {
|
|
|
37
53
|
workspaceRoot?: string;
|
|
38
54
|
workspaceName?: string;
|
|
39
55
|
isActiveWorkspace?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Chinese copy, when a translation file exists beside the skill.
|
|
58
|
+
*
|
|
59
|
+
* Kept apart from `description` rather than replacing it: the registry value is
|
|
60
|
+
* what the model routes on, and the panel must stay able to show it.
|
|
61
|
+
*/
|
|
62
|
+
i18n?: SkillEntryTranslation;
|
|
40
63
|
}
|
|
41
64
|
/** One rendered group. */
|
|
42
65
|
export interface GroupPayload {
|
|
43
66
|
key: string;
|
|
44
67
|
title: string;
|
|
45
|
-
hint: string;
|
|
46
68
|
skills: SkillEntry[];
|
|
47
69
|
}
|
|
48
70
|
/** One selectable workspace. */
|
|
@@ -148,6 +170,13 @@ export declare function localSkillRoots(deps: Pick<SkillRoutesDeps, 'dshHome' |
|
|
|
148
170
|
active: boolean;
|
|
149
171
|
};
|
|
150
172
|
}[];
|
|
173
|
+
/**
|
|
174
|
+
* Strip the leading YAML frontmatter from a skill file.
|
|
175
|
+
*
|
|
176
|
+
* @param source - the skill file's complete content.
|
|
177
|
+
* @returns the body below the closing delimiter, or the whole file when it declares none.
|
|
178
|
+
*/
|
|
179
|
+
export declare function stripFrontmatter(source: string): string;
|
|
151
180
|
/**
|
|
152
181
|
* Read `disable-model-invocation` from a skill file.
|
|
153
182
|
* @param source - the skill file's content.
|
|
@@ -197,6 +226,8 @@ export interface SkillRoutesDeps {
|
|
|
197
226
|
logger: {
|
|
198
227
|
warn(error: unknown): void;
|
|
199
228
|
};
|
|
229
|
+
/** Skill set holding the `<skill>/i18n/zh.json` files; unset disables translation. */
|
|
230
|
+
translationRoot?: string;
|
|
200
231
|
}
|
|
201
232
|
/** The registry summary this route family reads, re-exported for its callers. */
|
|
202
233
|
export type { SkillSummary };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chinese metadata for the skill center, read from each skill's own directory.
|
|
3
|
+
*
|
|
4
|
+
* The panel renders what the official registry routes on, so the registry's English
|
|
5
|
+
* description is what decides whether the model invokes a skill. Replacing it would
|
|
6
|
+
* change that decision, so a translation is carried alongside and applied only at
|
|
7
|
+
* render time: the card shows Chinese, the model keeps reading English.
|
|
8
|
+
*
|
|
9
|
+
* A skill keeps its translation in its own directory (`<skill>/i18n/zh.json`), which
|
|
10
|
+
* lets a skill and its Chinese travel together and ship from one clone. The skill
|
|
11
|
+
* file itself is located through the registry, so the translation is found even when
|
|
12
|
+
* the skill resolves through a symlink into another repository.
|
|
13
|
+
*
|
|
14
|
+
* @module @sparkelf/dsh-client-ui-skill-center/translations
|
|
15
|
+
*/
|
|
16
|
+
/** One skill's translated metadata. */
|
|
17
|
+
export interface SkillTranslation {
|
|
18
|
+
/** Short Chinese title shown in place of the kebab-case identifier. */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Chinese rendering of the registry description. */
|
|
21
|
+
description: string;
|
|
22
|
+
/** Chinese summary of the skill body. */
|
|
23
|
+
summary: string;
|
|
24
|
+
}
|
|
25
|
+
/** Translation progress, written by the translation script. */
|
|
26
|
+
export interface TranslationProgress {
|
|
27
|
+
total: number;
|
|
28
|
+
done: number;
|
|
29
|
+
current: string | null;
|
|
30
|
+
finishedAt: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read one skill's translation from beside its resolved skill file.
|
|
34
|
+
*
|
|
35
|
+
* A skill without a translation, or with an unreadable or malformed one, is returned
|
|
36
|
+
* as undefined: the panel then shows the registry values, so a partial translation
|
|
37
|
+
* set degrades to English rather than to a broken card.
|
|
38
|
+
*
|
|
39
|
+
* @param skillFilePath - absolute path of the skill's SKILL.md, when the registry reports one.
|
|
40
|
+
* @returns the translation, or undefined when none is usable.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readSkillTranslation(skillFilePath: string | undefined): Promise<SkillTranslation | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Read translation progress for the panel's progress bar.
|
|
45
|
+
*
|
|
46
|
+
* @param translationRoot - repository holding the skill set, whose `i18n/.progress.json` the script writes.
|
|
47
|
+
* @returns the progress, or undefined when no translation run has recorded one.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readTranslationProgress(translationRoot: string): Promise<TranslationProgress | undefined>;
|
|
50
|
+
//# sourceMappingURL=translations.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "@sparkelf/dsh-client-ui-skill-center",
|
|
3
|
+
"version": "0.2.1-alpha.30",
|
|
2
4
|
"description": "Skill center for the dsh web client: browses loaded skills grouped by source, enables or disables model invocation, creates skills, and deletes them into a recoverable trash",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/plus/ui-skill-center"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./lib/types/client/index.d.ts",
|
|
23
|
+
"default": "./lib/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
18
26
|
},
|
|
19
27
|
"dsh": {
|
|
20
28
|
"client": {
|
|
@@ -25,16 +33,9 @@
|
|
|
25
33
|
"platform": "web"
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"types": "./lib/types/index.d.ts"
|
|
32
|
-
},
|
|
33
|
-
"./client": {
|
|
34
|
-
"default": "./lib/client.js",
|
|
35
|
-
"types": "./lib/types/client/index.d.ts"
|
|
36
|
-
},
|
|
37
|
-
"./package.json": "./package.json"
|
|
36
|
+
"scripts": {
|
|
37
|
+
"bundle": "tsdown",
|
|
38
|
+
"watch": "tsdown --watch"
|
|
38
39
|
},
|
|
39
40
|
"files": [
|
|
40
41
|
"lib/index.js",
|
|
@@ -42,24 +43,23 @@
|
|
|
42
43
|
"lib/types/**/*.d.ts"
|
|
43
44
|
],
|
|
44
45
|
"license": "MIT",
|
|
45
|
-
"main": "lib/index.js",
|
|
46
|
-
"name": "@sparkelf/dsh-client-ui-skill-center",
|
|
47
46
|
"peerDependencies": {
|
|
48
47
|
"@deepseek-ai/cordis": ">=4.0.1"
|
|
49
48
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@deepseek-ai/cordis": "workspace:^",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-layout": "workspace:^",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-renderer": "workspace:^",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
|
57
|
+
"@deepseek-ai/dsh-host-webserver": "workspace:^",
|
|
58
|
+
"@deepseek-ai/dsh-session": "workspace:^",
|
|
59
|
+
"@deepseek-ai/dsh-skill": "workspace:^",
|
|
60
|
+
"@types/react": "~18.3.1",
|
|
61
|
+
"@types/react-dom": "~18.3.0",
|
|
62
|
+
"react": "^18.2.0",
|
|
63
|
+
"react-dom": "^18.2.0"
|
|
64
|
+
}
|
|
65
65
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 DeepSeek
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|