@sparkelf/dsh-client-ui-skill-center 0.2.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +71 -0
- package/README.zh.md +78 -0
- package/lib/client.js +613 -0
- package/lib/index.js +396 -0
- package/lib/types/client/SkillCenterPage.d.ts +23 -0
- package/lib/types/client/SkillCenterPanelIcon.d.ts +17 -0
- package/lib/types/client/api.d.ts +75 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +43 -0
- package/lib/types/index.d.ts +155 -0
- package/package.json +65 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@sparkelf/dsh-client-ui-skill-center",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region \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:32px;padding:28px clamp(24px,4vw,48px) 48px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:900px}.DGiSta_pageHead{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_pageIntro{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:8px;display:flex}.DGiSta_button{border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:13px}.DGiSta_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_primaryButton{background:var(--dsw-alias-brand-primary);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:8px;padding:0 14px;font-size:13px}.DGiSta_search{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;height:36px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:10px;padding:0 12px;font-size:13px}.DGiSta_search::placeholder{color:var(--dsw-alias-label-tertiary)}.DGiSta_group{margin-top:32px}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 4px;font-size:15px;font-weight:500;line-height:22px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:13px;font-weight:400}.DGiSta_groupHint{color:var(--dsw-alias-label-caption);font-size:12px;font-weight:400}.DGiSta_cards{margin:0;padding:0;list-style:none}.DGiSta_card{border-radius:12px;min-width:0;margin:0 -8px}.DGiSta_card:hover{background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_cardHead{align-items:center;gap:14px;padding:8px;display:flex}.DGiSta_cardIcon{border:.5px solid var(--dsw-alias-border-l3);width:48px;height:48px;color:var(--dsw-alias-label-secondary);border-radius:10px;flex:none;justify-content:center;align-items:center;display:inline-flex}.DGiSta_cardMain{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.DGiSta_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_cardWhenToUse{color:var(--dsw-alias-label-caption);font-size:12px;line-height:18px}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-tertiary);border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px}.DGiSta_markOn{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.DGiSta_markOff{border-color:var(--dsw-alias-border-l4);color:var(--dsw-alias-label-caption)}.DGiSta_markDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.DGiSta_cardEnd{flex:none;align-items:center;gap:8px;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:\"\";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_form{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;flex-direction:column;gap:12px;padding:16px;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_formActions{justify-content:flex-end;gap:8px;display:flex}.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)}";
|
|
12
|
+
const tagId = "@sparkelf/dsh-client-ui-skill-center/SkillCenterPage.module.css";
|
|
13
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
14
|
+
const tag = document.createElement("style");
|
|
15
|
+
tag.dataset.plugin = "@sparkelf/dsh-client-ui-skill-center";
|
|
16
|
+
tag.dataset.pluginCss = tagId;
|
|
17
|
+
tag.textContent = css;
|
|
18
|
+
document.head.appendChild(tag);
|
|
19
|
+
}
|
|
20
|
+
var SkillCenterPage_module_css_default = {
|
|
21
|
+
"button": "DGiSta_button",
|
|
22
|
+
"card": "DGiSta_card",
|
|
23
|
+
"cardDesc": "DGiSta_cardDesc",
|
|
24
|
+
"cardEnd": "DGiSta_cardEnd",
|
|
25
|
+
"cardHead": "DGiSta_cardHead",
|
|
26
|
+
"cardIcon": "DGiSta_cardIcon",
|
|
27
|
+
"cardMain": "DGiSta_cardMain",
|
|
28
|
+
"cardMarks": "DGiSta_cardMarks",
|
|
29
|
+
"cardTitle": "DGiSta_cardTitle",
|
|
30
|
+
"cardWhenToUse": "DGiSta_cardWhenToUse",
|
|
31
|
+
"cards": "DGiSta_cards",
|
|
32
|
+
"field": "DGiSta_field",
|
|
33
|
+
"fieldLabel": "DGiSta_fieldLabel",
|
|
34
|
+
"form": "DGiSta_form",
|
|
35
|
+
"formActions": "DGiSta_formActions",
|
|
36
|
+
"group": "DGiSta_group",
|
|
37
|
+
"groupCount": "DGiSta_groupCount",
|
|
38
|
+
"groupHint": "DGiSta_groupHint",
|
|
39
|
+
"groupTitle": "DGiSta_groupTitle",
|
|
40
|
+
"input": "DGiSta_input",
|
|
41
|
+
"mark": "DGiSta_mark",
|
|
42
|
+
"markDanger": "DGiSta_markDanger",
|
|
43
|
+
"markOff": "DGiSta_markOff",
|
|
44
|
+
"markOn": "DGiSta_markOn",
|
|
45
|
+
"page": "DGiSta_page",
|
|
46
|
+
"pageHead": "DGiSta_pageHead",
|
|
47
|
+
"pageIntro": "DGiSta_pageIntro",
|
|
48
|
+
"pageTitle": "DGiSta_pageTitle",
|
|
49
|
+
"primaryButton": "DGiSta_primaryButton",
|
|
50
|
+
"search": "DGiSta_search",
|
|
51
|
+
"status": "DGiSta_status",
|
|
52
|
+
"statusError": "DGiSta_statusError",
|
|
53
|
+
"switch": "DGiSta_switch",
|
|
54
|
+
"switchOn": "DGiSta_switchOn",
|
|
55
|
+
"textarea": "DGiSta_textarea",
|
|
56
|
+
"toolbar": "DGiSta_toolbar"
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/client/api.ts
|
|
60
|
+
/**
|
|
61
|
+
* Skill center API client. Talks to the host route family over same-origin
|
|
62
|
+
* fetch; the host owns the trust fence.
|
|
63
|
+
*
|
|
64
|
+
* @module @sparkelf/dsh-client-ui-skill-center/client/api
|
|
65
|
+
*/
|
|
66
|
+
/** Route paths, mirrored from the host (`src/index.ts` ROUTES). */
|
|
67
|
+
const API = {
|
|
68
|
+
list: "/api/dsh-skill-center/list",
|
|
69
|
+
setEnabled: "/api/dsh-skill-center/set-enabled",
|
|
70
|
+
create: "/api/dsh-skill-center/create",
|
|
71
|
+
remove: "/api/dsh-skill-center/delete"
|
|
72
|
+
};
|
|
73
|
+
/** One thrown API error carrying the host's message. */
|
|
74
|
+
var ApiError = class extends Error {};
|
|
75
|
+
/** Skill center API client. */
|
|
76
|
+
var SkillApi = class {
|
|
77
|
+
/** Fetch the grouped skill list. */
|
|
78
|
+
async list(cwd) {
|
|
79
|
+
const url = typeof cwd === "string" && cwd.trim() !== "" ? `${API.list}?cwd=${encodeURIComponent(cwd)}` : API.list;
|
|
80
|
+
return this.request(url);
|
|
81
|
+
}
|
|
82
|
+
/** Enable or disable a skill by rewriting its frontmatter flag. */
|
|
83
|
+
async setEnabled(name, path, enabled) {
|
|
84
|
+
return this.request(API.setEnabled, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: {
|
|
87
|
+
name,
|
|
88
|
+
path,
|
|
89
|
+
enabled
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** Create a skill file under the user or project root. */
|
|
94
|
+
async create(payload) {
|
|
95
|
+
return this.request(API.create, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: payload
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Delete a skill by moving it into the recoverable trash. */
|
|
101
|
+
async remove(name, path) {
|
|
102
|
+
return this.request(API.remove, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: {
|
|
105
|
+
name,
|
|
106
|
+
path
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async request(path, options = {}) {
|
|
111
|
+
const init = { method: options.method ?? "GET" };
|
|
112
|
+
if (options.body !== void 0) {
|
|
113
|
+
init.headers = { "content-type": "application/json" };
|
|
114
|
+
init.body = JSON.stringify(options.body);
|
|
115
|
+
}
|
|
116
|
+
const response = await fetch(path, init);
|
|
117
|
+
let body;
|
|
118
|
+
try {
|
|
119
|
+
body = await response.json();
|
|
120
|
+
} catch {
|
|
121
|
+
body = void 0;
|
|
122
|
+
}
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const reported = typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "";
|
|
125
|
+
throw new ApiError(reported === "" ? "HTTP " + String(response.status) : reported);
|
|
126
|
+
}
|
|
127
|
+
return body;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/client/SkillCenterPage.tsx
|
|
132
|
+
/**
|
|
133
|
+
* Skill center page. Browses the loaded skills grouped by source, toggles model
|
|
134
|
+
* invocation, creates a skill, and deletes one into a recoverable trash.
|
|
135
|
+
*
|
|
136
|
+
* The header, rows, and glyph frame follow the official plugin panel's layout so
|
|
137
|
+
* the page reads as one of the shell's own.
|
|
138
|
+
*
|
|
139
|
+
* @module @sparkelf/dsh-client-ui-skill-center/client/SkillCenterPage
|
|
140
|
+
*/
|
|
141
|
+
/**
|
|
142
|
+
* One labelled create-form field.
|
|
143
|
+
* @param props - the field's label and the control it wraps.
|
|
144
|
+
* @returns the labelled field.
|
|
145
|
+
*/
|
|
146
|
+
function DraftField({ label, children }) {
|
|
147
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
148
|
+
className: SkillCenterPage_module_css_default.field,
|
|
149
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
150
|
+
className: SkillCenterPage_module_css_default.fieldLabel,
|
|
151
|
+
children: label
|
|
152
|
+
}), children]
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* One skill row.
|
|
157
|
+
* @param props - the entry, its toggle, and its delete action.
|
|
158
|
+
* @returns the row.
|
|
159
|
+
*/
|
|
160
|
+
function SkillCard({ skill, t, onToggle, onDelete }) {
|
|
161
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
162
|
+
className: SkillCenterPage_module_css_default.card,
|
|
163
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
164
|
+
className: SkillCenterPage_module_css_default.cardHead,
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
167
|
+
className: SkillCenterPage_module_css_default.cardIcon,
|
|
168
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size: 22 })
|
|
169
|
+
}),
|
|
170
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
171
|
+
className: SkillCenterPage_module_css_default.cardMain,
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
174
|
+
className: SkillCenterPage_module_css_default.cardTitle,
|
|
175
|
+
children: skill.name
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
178
|
+
className: SkillCenterPage_module_css_default.cardDesc,
|
|
179
|
+
children: skill.description
|
|
180
|
+
}),
|
|
181
|
+
skill.whenToUse === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
182
|
+
className: SkillCenterPage_module_css_default.cardWhenToUse,
|
|
183
|
+
children: skill.whenToUse
|
|
184
|
+
}),
|
|
185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
186
|
+
className: SkillCenterPage_module_css_default.cardMarks,
|
|
187
|
+
children: [
|
|
188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
189
|
+
className: skill.modelInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
|
|
190
|
+
children: t("skill.modelInvocable")
|
|
191
|
+
}),
|
|
192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
193
|
+
className: skill.userInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
|
|
194
|
+
children: t("skill.userInvocable")
|
|
195
|
+
}),
|
|
196
|
+
skill.linked === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
197
|
+
className: SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markDanger,
|
|
198
|
+
children: t("skill.linked")
|
|
199
|
+
}) : null
|
|
200
|
+
]
|
|
201
|
+
})
|
|
202
|
+
]
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
205
|
+
className: SkillCenterPage_module_css_default.cardEnd,
|
|
206
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
207
|
+
type: "button",
|
|
208
|
+
className: skill.modelInvocable ? SkillCenterPage_module_css_default.switch + " " + SkillCenterPage_module_css_default.switchOn : SkillCenterPage_module_css_default.switch,
|
|
209
|
+
"aria-label": skill.modelInvocable ? t("skill.disabled") : t("skill.enabled"),
|
|
210
|
+
"aria-pressed": skill.modelInvocable,
|
|
211
|
+
onClick: () => {
|
|
212
|
+
onToggle(skill, !skill.modelInvocable);
|
|
213
|
+
}
|
|
214
|
+
}), skill.path === void 0 || skill.linked === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
215
|
+
type: "button",
|
|
216
|
+
className: SkillCenterPage_module_css_default.button,
|
|
217
|
+
onClick: () => {
|
|
218
|
+
onDelete(skill);
|
|
219
|
+
},
|
|
220
|
+
children: t("action.delete")
|
|
221
|
+
})]
|
|
222
|
+
})
|
|
223
|
+
]
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The skill center panel.
|
|
229
|
+
* @param props - the slot's runtime share, providing the dictionary resolver.
|
|
230
|
+
* @returns the rendered panel.
|
|
231
|
+
*/
|
|
232
|
+
function SkillCenterPage({ t }) {
|
|
233
|
+
const api = (0, react.useMemo)(() => new SkillApi(), []);
|
|
234
|
+
const [state, setState] = (0, react.useState)({ kind: "loading" });
|
|
235
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
236
|
+
const [creating, setCreating] = (0, react.useState)(false);
|
|
237
|
+
const [draft, setDraft] = (0, react.useState)({
|
|
238
|
+
name: "",
|
|
239
|
+
description: "",
|
|
240
|
+
whenToUse: "",
|
|
241
|
+
content: "",
|
|
242
|
+
root: "user"
|
|
243
|
+
});
|
|
244
|
+
const load = (0, react.useCallback)(async () => {
|
|
245
|
+
try {
|
|
246
|
+
setState({
|
|
247
|
+
kind: "ready",
|
|
248
|
+
payload: await api.list()
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
setState({
|
|
252
|
+
kind: "failed",
|
|
253
|
+
message: error instanceof Error ? error.message : String(error)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}, [api]);
|
|
257
|
+
(0, react.useEffect)(() => {
|
|
258
|
+
load();
|
|
259
|
+
}, [load]);
|
|
260
|
+
const groups = (0, react.useMemo)(() => {
|
|
261
|
+
if (state.kind !== "ready") return [];
|
|
262
|
+
const q = query.trim().toLowerCase();
|
|
263
|
+
if (q === "") return state.payload.groups;
|
|
264
|
+
return state.payload.groups.map((g) => ({
|
|
265
|
+
...g,
|
|
266
|
+
skills: g.skills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || (s.whenToUse ?? "").toLowerCase().includes(q))
|
|
267
|
+
})).filter((g) => g.skills.length > 0);
|
|
268
|
+
}, [state, query]);
|
|
269
|
+
const toggle = (0, react.useCallback)(async (skill, enabled) => {
|
|
270
|
+
if (skill.path === void 0) return;
|
|
271
|
+
try {
|
|
272
|
+
await api.setEnabled(skill.name, skill.path, enabled);
|
|
273
|
+
await load();
|
|
274
|
+
} catch (error) {
|
|
275
|
+
setState({
|
|
276
|
+
kind: "failed",
|
|
277
|
+
message: error instanceof Error ? error.message : String(error)
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}, [api, load]);
|
|
281
|
+
const remove = (0, react.useCallback)(async (skill) => {
|
|
282
|
+
if (skill.path === void 0) return;
|
|
283
|
+
if (!window.confirm(t("delete.confirm"))) return;
|
|
284
|
+
try {
|
|
285
|
+
await api.remove(skill.name, skill.path);
|
|
286
|
+
await load();
|
|
287
|
+
} catch (error) {
|
|
288
|
+
setState({
|
|
289
|
+
kind: "failed",
|
|
290
|
+
message: error instanceof Error ? error.message : String(error)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}, [
|
|
294
|
+
api,
|
|
295
|
+
load,
|
|
296
|
+
t
|
|
297
|
+
]);
|
|
298
|
+
/** Bind one draft key to a change handler. */
|
|
299
|
+
const set = (0, react.useCallback)((key) => (event) => {
|
|
300
|
+
const value = event.target.value;
|
|
301
|
+
setDraft((d) => ({
|
|
302
|
+
...d,
|
|
303
|
+
[key]: value
|
|
304
|
+
}));
|
|
305
|
+
}, []);
|
|
306
|
+
const submit = (0, react.useCallback)(async () => {
|
|
307
|
+
try {
|
|
308
|
+
await api.create({
|
|
309
|
+
root: draft.root,
|
|
310
|
+
name: draft.name,
|
|
311
|
+
description: draft.description,
|
|
312
|
+
...draft.whenToUse.trim() === "" ? {} : { whenToUse: draft.whenToUse },
|
|
313
|
+
...draft.content.trim() === "" ? {} : { content: draft.content }
|
|
314
|
+
});
|
|
315
|
+
setDraft({
|
|
316
|
+
name: "",
|
|
317
|
+
description: "",
|
|
318
|
+
whenToUse: "",
|
|
319
|
+
content: "",
|
|
320
|
+
root: "user"
|
|
321
|
+
});
|
|
322
|
+
setCreating(false);
|
|
323
|
+
await load();
|
|
324
|
+
} catch (error) {
|
|
325
|
+
setState({
|
|
326
|
+
kind: "failed",
|
|
327
|
+
message: error instanceof Error ? error.message : String(error)
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}, [
|
|
331
|
+
api,
|
|
332
|
+
draft,
|
|
333
|
+
load
|
|
334
|
+
]);
|
|
335
|
+
const total = state.kind === "ready" ? state.payload.groups.reduce((n, g) => n + g.skills.length, 0) : 0;
|
|
336
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
337
|
+
className: SkillCenterPage_module_css_default.page,
|
|
338
|
+
children: [
|
|
339
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
340
|
+
className: SkillCenterPage_module_css_default.pageHead,
|
|
341
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
342
|
+
className: SkillCenterPage_module_css_default.pageTitle,
|
|
343
|
+
children: t("panel")
|
|
344
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
345
|
+
className: SkillCenterPage_module_css_default.pageIntro,
|
|
346
|
+
children: t("panel.intro")
|
|
347
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
348
|
+
className: SkillCenterPage_module_css_default.toolbar,
|
|
349
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
350
|
+
type: "button",
|
|
351
|
+
className: SkillCenterPage_module_css_default.button,
|
|
352
|
+
onClick: () => {
|
|
353
|
+
load();
|
|
354
|
+
},
|
|
355
|
+
children: t("action.refresh")
|
|
356
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
357
|
+
type: "button",
|
|
358
|
+
className: SkillCenterPage_module_css_default.primaryButton,
|
|
359
|
+
onClick: () => {
|
|
360
|
+
setCreating((v) => !v);
|
|
361
|
+
},
|
|
362
|
+
children: t("action.create")
|
|
363
|
+
})]
|
|
364
|
+
})]
|
|
365
|
+
}),
|
|
366
|
+
creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
367
|
+
className: SkillCenterPage_module_css_default.form,
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
|
|
370
|
+
label: t("create.name"),
|
|
371
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
372
|
+
className: SkillCenterPage_module_css_default.input,
|
|
373
|
+
value: draft.name,
|
|
374
|
+
onChange: set("name")
|
|
375
|
+
})
|
|
376
|
+
}),
|
|
377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
|
|
378
|
+
label: t("create.description"),
|
|
379
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
380
|
+
className: SkillCenterPage_module_css_default.input,
|
|
381
|
+
value: draft.description,
|
|
382
|
+
onChange: set("description")
|
|
383
|
+
})
|
|
384
|
+
}),
|
|
385
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
|
|
386
|
+
label: t("create.whenToUse"),
|
|
387
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
388
|
+
className: SkillCenterPage_module_css_default.input,
|
|
389
|
+
value: draft.whenToUse,
|
|
390
|
+
onChange: set("whenToUse")
|
|
391
|
+
})
|
|
392
|
+
}),
|
|
393
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
|
|
394
|
+
label: t("create.content"),
|
|
395
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
396
|
+
className: SkillCenterPage_module_css_default.textarea,
|
|
397
|
+
value: draft.content,
|
|
398
|
+
onChange: set("content")
|
|
399
|
+
})
|
|
400
|
+
}),
|
|
401
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DraftField, {
|
|
402
|
+
label: t("create.root"),
|
|
403
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
404
|
+
className: SkillCenterPage_module_css_default.input,
|
|
405
|
+
value: draft.root,
|
|
406
|
+
onChange: (e) => {
|
|
407
|
+
setDraft((d) => ({
|
|
408
|
+
...d,
|
|
409
|
+
root: e.target.value === "project" ? "project" : "user"
|
|
410
|
+
}));
|
|
411
|
+
},
|
|
412
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
413
|
+
value: "user",
|
|
414
|
+
children: t("create.rootUser")
|
|
415
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
416
|
+
value: "project",
|
|
417
|
+
children: t("create.rootProject")
|
|
418
|
+
})]
|
|
419
|
+
})
|
|
420
|
+
}),
|
|
421
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
422
|
+
className: SkillCenterPage_module_css_default.formActions,
|
|
423
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
424
|
+
type: "button",
|
|
425
|
+
className: SkillCenterPage_module_css_default.button,
|
|
426
|
+
onClick: () => {
|
|
427
|
+
setCreating(false);
|
|
428
|
+
},
|
|
429
|
+
children: t("action.cancel")
|
|
430
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
431
|
+
type: "button",
|
|
432
|
+
className: SkillCenterPage_module_css_default.primaryButton,
|
|
433
|
+
onClick: () => {
|
|
434
|
+
submit();
|
|
435
|
+
},
|
|
436
|
+
children: t("action.confirm")
|
|
437
|
+
})]
|
|
438
|
+
})
|
|
439
|
+
]
|
|
440
|
+
}) : null,
|
|
441
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
442
|
+
className: SkillCenterPage_module_css_default.search,
|
|
443
|
+
placeholder: t("search.placeholder"),
|
|
444
|
+
value: query,
|
|
445
|
+
onChange: (e) => {
|
|
446
|
+
setQuery(e.target.value);
|
|
447
|
+
}
|
|
448
|
+
}),
|
|
449
|
+
state.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
450
|
+
className: SkillCenterPage_module_css_default.status,
|
|
451
|
+
children: t("state.loading")
|
|
452
|
+
}) : null,
|
|
453
|
+
state.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
454
|
+
className: SkillCenterPage_module_css_default.status + " " + SkillCenterPage_module_css_default.statusError,
|
|
455
|
+
children: [
|
|
456
|
+
t("state.failed"),
|
|
457
|
+
": ",
|
|
458
|
+
state.message
|
|
459
|
+
]
|
|
460
|
+
}) : null,
|
|
461
|
+
state.kind === "ready" && groups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
462
|
+
className: SkillCenterPage_module_css_default.status,
|
|
463
|
+
children: query.trim() === "" ? t("state.empty") : t("search.noMatches")
|
|
464
|
+
}) : null,
|
|
465
|
+
state.kind === "ready" && groups.length > 0 && total === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
466
|
+
className: SkillCenterPage_module_css_default.status,
|
|
467
|
+
children: t("state.empty")
|
|
468
|
+
}) : null,
|
|
469
|
+
groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
470
|
+
className: SkillCenterPage_module_css_default.group,
|
|
471
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
472
|
+
className: SkillCenterPage_module_css_default.groupTitle,
|
|
473
|
+
children: [
|
|
474
|
+
group.title,
|
|
475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
476
|
+
className: SkillCenterPage_module_css_default.groupCount,
|
|
477
|
+
children: group.skills.length
|
|
478
|
+
}),
|
|
479
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
480
|
+
className: SkillCenterPage_module_css_default.groupHint,
|
|
481
|
+
children: group.hint
|
|
482
|
+
})
|
|
483
|
+
]
|
|
484
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
485
|
+
className: SkillCenterPage_module_css_default.cards,
|
|
486
|
+
children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillCard, {
|
|
487
|
+
skill,
|
|
488
|
+
t,
|
|
489
|
+
onToggle: (s, enabled) => {
|
|
490
|
+
toggle(s, enabled);
|
|
491
|
+
},
|
|
492
|
+
onDelete: (s) => {
|
|
493
|
+
remove(s);
|
|
494
|
+
}
|
|
495
|
+
}, group.key + ":" + skill.name))
|
|
496
|
+
})]
|
|
497
|
+
}, group.key))
|
|
498
|
+
]
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/client/SkillCenterPanelIcon.tsx
|
|
503
|
+
/**
|
|
504
|
+
* Render the glyph at the size the sidebar asks for.
|
|
505
|
+
* @param props - the sidebar's icon share: requested edge and selected state.
|
|
506
|
+
* @returns the icon element.
|
|
507
|
+
*/
|
|
508
|
+
function SkillCenterPanelIcon({ size }) {
|
|
509
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, { size });
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/client/locales.ts
|
|
513
|
+
/** Simplified Chinese dictionary. */
|
|
514
|
+
const zh = {
|
|
515
|
+
"panel": "技能中心",
|
|
516
|
+
"panel.intro": "浏览已加载的技能,启用或停用模型调用",
|
|
517
|
+
"search.placeholder": "搜索技能…",
|
|
518
|
+
"search.clear": "清除搜索",
|
|
519
|
+
"search.noMatches": "无匹配技能",
|
|
520
|
+
"action.refresh": "刷新",
|
|
521
|
+
"action.create": "新建技能",
|
|
522
|
+
"action.delete": "删除",
|
|
523
|
+
"action.cancel": "取消",
|
|
524
|
+
"action.confirm": "确定",
|
|
525
|
+
"state.loading": "正在加载技能…",
|
|
526
|
+
"state.empty": "该分组暂无技能",
|
|
527
|
+
"state.failed": "技能列表加载失败",
|
|
528
|
+
"skill.enabled": "已启用",
|
|
529
|
+
"skill.disabled": "已停用",
|
|
530
|
+
"skill.modelInvocable": "模型可调用",
|
|
531
|
+
"skill.userInvocable": "用户可调用",
|
|
532
|
+
"skill.linked": "符号链接(不可删除)",
|
|
533
|
+
"create.title": "新建技能",
|
|
534
|
+
"create.name": "名称(小写连字符)",
|
|
535
|
+
"create.description": "描述",
|
|
536
|
+
"create.whenToUse": "何时使用(可选)",
|
|
537
|
+
"create.content": "正文(可选)",
|
|
538
|
+
"create.root": "位置",
|
|
539
|
+
"create.rootUser": "用户级",
|
|
540
|
+
"create.rootProject": "项目级",
|
|
541
|
+
"delete.confirm": "删除后可从 .trash 恢复。确定删除?"
|
|
542
|
+
};
|
|
543
|
+
/** English dictionary. */
|
|
544
|
+
const en = {
|
|
545
|
+
"panel": "Skill Center",
|
|
546
|
+
"panel.intro": "Browse loaded skills and control model invocation",
|
|
547
|
+
"search.placeholder": "Search skills…",
|
|
548
|
+
"search.clear": "Clear search",
|
|
549
|
+
"search.noMatches": "No matching skills",
|
|
550
|
+
"action.refresh": "Refresh",
|
|
551
|
+
"action.create": "New skill",
|
|
552
|
+
"action.delete": "Delete",
|
|
553
|
+
"action.cancel": "Cancel",
|
|
554
|
+
"action.confirm": "Confirm",
|
|
555
|
+
"state.loading": "Loading skills…",
|
|
556
|
+
"state.empty": "No skills in this group",
|
|
557
|
+
"state.failed": "Could not load the skill list",
|
|
558
|
+
"skill.enabled": "Enabled",
|
|
559
|
+
"skill.disabled": "Disabled",
|
|
560
|
+
"skill.modelInvocable": "Model invocable",
|
|
561
|
+
"skill.userInvocable": "User invocable",
|
|
562
|
+
"skill.linked": "Symlink (cannot delete)",
|
|
563
|
+
"create.title": "New skill",
|
|
564
|
+
"create.name": "Name (lowercase kebab-case)",
|
|
565
|
+
"create.description": "Description",
|
|
566
|
+
"create.whenToUse": "When to use (optional)",
|
|
567
|
+
"create.content": "Body (optional)",
|
|
568
|
+
"create.root": "Location",
|
|
569
|
+
"create.rootUser": "User",
|
|
570
|
+
"create.rootProject": "Project",
|
|
571
|
+
"delete.confirm": "Deleting moves it into .trash, where it can be restored. Delete?"
|
|
572
|
+
};
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/client/index.ts
|
|
575
|
+
/** Dictionary namespace owned by this plugin. */
|
|
576
|
+
const NS = "skillCenter";
|
|
577
|
+
/** The id shared by the sidebar entry and the main panel it opens. */
|
|
578
|
+
const PANEL_ID = "skill-center";
|
|
579
|
+
/** Services required by the sidebar and page registrations. */
|
|
580
|
+
const inject = ["slots", "locale"];
|
|
581
|
+
/**
|
|
582
|
+
* Contribute the Skill Center entry and the page it opens.
|
|
583
|
+
* @param ctx - the browser plugin context.
|
|
584
|
+
*/
|
|
585
|
+
function apply(ctx) {
|
|
586
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
587
|
+
zh,
|
|
588
|
+
en
|
|
589
|
+
}), "ui-skill-center: dictionaries");
|
|
590
|
+
const t = ctx.locale.bind(NS);
|
|
591
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
592
|
+
name: "main",
|
|
593
|
+
key: PANEL_ID,
|
|
594
|
+
locale: NS
|
|
595
|
+
}, SkillCenterPage));
|
|
596
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
597
|
+
name: "sidebar.panellist",
|
|
598
|
+
id: PANEL_ID,
|
|
599
|
+
order: 10,
|
|
600
|
+
label: () => t("panel"),
|
|
601
|
+
locale: NS
|
|
602
|
+
}, SkillCenterPanelIcon));
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
exports.NS = NS;
|
|
606
|
+
exports.PANEL_ID = PANEL_ID;
|
|
607
|
+
exports.apply = apply;
|
|
608
|
+
exports.inject = inject;
|
|
609
|
+
return module.exports;
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
//# sourceMappingURL=client.js.map
|