@dungsil/dsh-plugin-ui-setting-skills 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/assets/light-dark-preview.webp +0 -0
- package/dist/client.js +277 -33
- package/dist/index.js +114 -1
- package/locale/en.json +11 -0
- package/locale/ko.json +11 -0
- package/locale/zh.json +11 -0
- package/package.json +11 -1
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Skills Settings · 스킬 설정
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
DSH Web 설정에서 로드된 사용자 스킬을 확인하고 DSH에서의 사용 여부를 설정합니다.
|
|
6
|
+
|
|
7
|
+
위 이미지는 최신 빌드의 실제 플러그인 컴포넌트에 예시 스킬 목록·설정 응답과 공식 DSH 테마·UI primitives를 제공한 독립 브라우저 화면을 각각 캡처해 좌측 라이트·우측 다크 모드의 전체 화면을 나란히 이어 붙였습니다. 실제 DSH 세션 화면은 아니며, 스킬 이름·설명·경로와 사용 상태는 예시입니다.
|
|
8
|
+
|
|
9
|
+
## 사용 방법
|
|
10
|
+
|
|
11
|
+
1. DSH 플러그인 설치 화면에서 `@dungsil/dsh-plugin-ui-setting-skills`를 설치하고 활성화합니다.
|
|
12
|
+
2. **설정 → 스킬**을 열고 **공용 스킬** 또는 **DSH 스킬** 탭을 선택합니다.
|
|
13
|
+
3. 스킬을 펼쳐 설명과 파일 경로를 확인합니다. 오른쪽 스위치로 사용 여부를 변경하면 자동 저장합니다. 이 설정은 DSH에만 적용되며 스킬 파일은 수정하지 않습니다.
|
|
14
|
+
|
|
15
|
+
## 동작과 호환성
|
|
16
|
+
|
|
17
|
+
- DSH `0.2.1-rc.5` 이상 `0.3.0` 미만의 Web UI를 지원합니다. 한국어·영어·중국어(간체)와 DSH 테마를 따릅니다.
|
|
18
|
+
- 공용 에이전트 및 DSH 사용자 스킬 경로에서 발견한 목록을 표시합니다. 프로젝트 스킬을 편집하거나 새 스킬을 만드는 화면은 아닙니다.
|
|
19
|
+
- 사고 과정 번역 서비스가 연결되어 있으면 스킬을 펼칠 때 설명을 해당 서비스로 자동 전송해 번역하고 캐시에 저장합니다. 원문으로 전환할 수 있습니다. 번역 서비스가 없으면 원문만 표시합니다.
|
|
20
|
+
- 저장 오류가 나타나면 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 뒤 다시 시도하세요.
|
|
Binary file
|
package/dist/client.js
CHANGED
|
@@ -1,8 +1,68 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({ id: "@dungsil/dsh-plugin-ui-setting-skills", factory(require) { const module = { exports: {} }; const exports = module.exports;
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
let react = require("react");
|
|
4
|
+
let react_dom_client = require("react-dom/client");
|
|
4
5
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
5
6
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
// ../../tools/ui/src/client.ts
|
|
8
|
+
/** Each effect owns its sheet, even when another plugin installs the same defaults. */
|
|
9
|
+
function installStyles(css) {
|
|
10
|
+
const style = document.createElement("style");
|
|
11
|
+
style.textContent = css;
|
|
12
|
+
document.head.append(style);
|
|
13
|
+
return () => style.remove();
|
|
14
|
+
}
|
|
15
|
+
// ../../tools/ui/src/settings.tsx
|
|
16
|
+
/** The rc.5 settings shell has no icon slot, so decorate only the matching navigation rows. */
|
|
17
|
+
function installSettingsIcon(id, labels, icon) {
|
|
18
|
+
const attribute = "data-dsh-settings-icon";
|
|
19
|
+
const selector = `[${attribute}="${id}"]`;
|
|
20
|
+
const removeStyles = installStyles(`
|
|
21
|
+
${selector} > svg { display: none; }
|
|
22
|
+
${typeof icon === "string" ? `${selector}::before { content: ''; display: block; flex: none; width: 16px; height: 16px; background: currentColor; mask: url("data:image/svg+xml,${encodeURIComponent(icon)}") center / contain no-repeat; }` : ""}
|
|
23
|
+
${selector} > [data-dsh-settings-icon-glyph] { display: inline-flex; flex: none; }
|
|
24
|
+
`);
|
|
25
|
+
const marked = /* @__PURE__ */ new Map();
|
|
26
|
+
const refresh = () => {
|
|
27
|
+
const rows = new Set(document.querySelectorAll("[data-shortcut-modal=\"settings\"] nav button"));
|
|
28
|
+
for (const [row, dispose] of marked) if (!rows.has(row) || !labels.includes(row.textContent?.trim() ?? "")) {
|
|
29
|
+
row.removeAttribute(attribute);
|
|
30
|
+
dispose();
|
|
31
|
+
marked.delete(row);
|
|
32
|
+
}
|
|
33
|
+
for (const row of rows) if (!marked.has(row) && labels.includes(row.textContent?.trim() ?? "")) {
|
|
34
|
+
row.setAttribute(attribute, id);
|
|
35
|
+
let dispose = () => {};
|
|
36
|
+
if (typeof icon !== "string") {
|
|
37
|
+
const glyph = document.createElement("span");
|
|
38
|
+
glyph.setAttribute("data-dsh-settings-icon-glyph", "");
|
|
39
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
40
|
+
row.prepend(glyph);
|
|
41
|
+
const unmount = icon(glyph);
|
|
42
|
+
dispose = () => {
|
|
43
|
+
unmount();
|
|
44
|
+
glyph.remove();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
marked.set(row, dispose);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const observer = new MutationObserver(refresh);
|
|
51
|
+
observer.observe(document.body, {
|
|
52
|
+
childList: true,
|
|
53
|
+
subtree: true,
|
|
54
|
+
characterData: true
|
|
55
|
+
});
|
|
56
|
+
refresh();
|
|
57
|
+
return () => {
|
|
58
|
+
observer.disconnect();
|
|
59
|
+
for (const [row, dispose] of marked) {
|
|
60
|
+
row.removeAttribute(attribute);
|
|
61
|
+
dispose();
|
|
62
|
+
}
|
|
63
|
+
removeStyles();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
6
66
|
// locale/en.json
|
|
7
67
|
var en_default = {
|
|
8
68
|
meta: {
|
|
@@ -20,6 +80,17 @@ var en_default = {
|
|
|
20
80
|
"incomplete": "Some skill sources could not be loaded. This list may be incomplete.",
|
|
21
81
|
"use": "Use",
|
|
22
82
|
"disabled": "Off",
|
|
83
|
+
"openFolder": "Open folder",
|
|
84
|
+
"openingFolder": "Opening folder…",
|
|
85
|
+
"openFolderHint": "Open this skill’s folder in the file manager on the DSH host.",
|
|
86
|
+
"openFolderUnavailable": "Folder opening requires a desktop on the DSH host.",
|
|
87
|
+
"openFolderError": "Could not open the skill folder. Check the file manager on the DSH host and try again.",
|
|
88
|
+
"translate": "View translation",
|
|
89
|
+
"original": "View original",
|
|
90
|
+
"retry": "Retry",
|
|
91
|
+
"translating": "Translating…",
|
|
92
|
+
"translationNotice": "Expanding a skill automatically sends its description to the service selected in reasoning translation settings. Translations are cached using its target language.",
|
|
93
|
+
"translationError": "Could not translate. The original is shown. Paid services require a Session and are unavailable here.",
|
|
23
94
|
"saveError": "Could not save the setting. Close and reopen this section to check the saved state before trying again."
|
|
24
95
|
}
|
|
25
96
|
};
|
|
@@ -40,6 +111,17 @@ var ko_default = {
|
|
|
40
111
|
"incomplete": "일부 스킬 소스를 불러오지 못해 목록이 완전하지 않을 수 있습니다.",
|
|
41
112
|
"use": "사용",
|
|
42
113
|
"disabled": "꺼짐",
|
|
114
|
+
"openFolder": "폴더 열기",
|
|
115
|
+
"openingFolder": "폴더를 여는 중…",
|
|
116
|
+
"openFolderHint": "DSH 실행 호스트의 파일 탐색기에서 이 스킬의 폴더를 엽니다.",
|
|
117
|
+
"openFolderUnavailable": "폴더를 열려면 DSH 실행 호스트에 데스크톱 환경이 필요합니다.",
|
|
118
|
+
"openFolderError": "스킬 폴더를 열지 못했습니다. DSH 실행 호스트의 파일 탐색기를 확인한 후 다시 시도해 주세요.",
|
|
119
|
+
"translate": "번역 보기",
|
|
120
|
+
"original": "원문 보기",
|
|
121
|
+
"retry": "다시 시도",
|
|
122
|
+
"translating": "번역 중…",
|
|
123
|
+
"translationNotice": "스킬을 펼치면 사고 과정 번역 설정에서 선택한 서비스로 설명을 자동 전송합니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
|
|
124
|
+
"translationError": "번역하지 못해 원문을 표시합니다. 유료 서비스는 세션이 필요하므로 이 화면에서 사용할 수 없습니다.",
|
|
43
125
|
"saveError": "설정을 저장하지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 후 다시 시도해 주세요."
|
|
44
126
|
}
|
|
45
127
|
};
|
|
@@ -60,6 +142,17 @@ var zh_default = {
|
|
|
60
142
|
"incomplete": "部分技能源无法加载,列表可能不完整。",
|
|
61
143
|
"use": "使用",
|
|
62
144
|
"disabled": "已关闭",
|
|
145
|
+
"openFolder": "打开文件夹",
|
|
146
|
+
"openingFolder": "正在打开文件夹…",
|
|
147
|
+
"openFolderHint": "在 DSH 主机的文件管理器中打开此技能的文件夹。",
|
|
148
|
+
"openFolderUnavailable": "打开文件夹需要 DSH 主机具备桌面环境。",
|
|
149
|
+
"openFolderError": "无法打开技能文件夹。请检查 DSH 主机的文件管理器后重试。",
|
|
150
|
+
"translate": "查看译文",
|
|
151
|
+
"original": "查看原文",
|
|
152
|
+
"retry": "重试",
|
|
153
|
+
"translating": "正在翻译…",
|
|
154
|
+
"translationNotice": "展开技能时,会自动将描述发送至思考过程翻译设置中选择的服务。译文按该设置的目标语言缓存。",
|
|
155
|
+
"translationError": "翻译失败,已显示原文。付费服务需要会话,无法在此页面使用。",
|
|
63
156
|
"saveError": "无法保存设置。请关闭并重新打开此设置项,确认已保存的状态后重试。"
|
|
64
157
|
}
|
|
65
158
|
};
|
|
@@ -67,6 +160,7 @@ var zh_default = {
|
|
|
67
160
|
const channel = "/api";
|
|
68
161
|
const method = "dungsil-ui-setting-skills/list";
|
|
69
162
|
const saveMethod = "dungsil-ui-setting-skills/save";
|
|
163
|
+
const translateMethod = "dungsil-ui-setting-skills/translate";
|
|
70
164
|
// src/client.tsx
|
|
71
165
|
const name = "dungsil-ui-setting-skills";
|
|
72
166
|
const inject = [
|
|
@@ -83,8 +177,8 @@ const styles = `
|
|
|
83
177
|
.dsh-skills-settings ul { list-style: none; margin: 0; padding: 0; }
|
|
84
178
|
.dsh-skills-settings code { font-size: 12px; overflow-wrap: anywhere; }
|
|
85
179
|
.dsh-skills-settings [role=alert] { color: var(--dsw-alias-state-error-primary, #b54424); }
|
|
86
|
-
.dsh-skills-settings li { display:
|
|
87
|
-
.dsh-skills-settings details {
|
|
180
|
+
.dsh-skills-settings li { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 16px; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
181
|
+
.dsh-skills-settings details { min-width: 0; }
|
|
88
182
|
.dsh-skills-settings summary { display: flex; align-items: center; gap: 12px; padding: 16px 0; list-style: none; cursor: pointer; border-radius: var(--dsw-radius-md); }
|
|
89
183
|
.dsh-skills-settings summary::-webkit-details-marker { display: none; }
|
|
90
184
|
.dsh-skills-settings summary:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
@@ -97,9 +191,73 @@ const styles = `
|
|
|
97
191
|
.dsh-skills-title .dsh-skills-muted { flex: none; font-size: 12px; }
|
|
98
192
|
.dsh-skills-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
99
193
|
.dsh-skills-detail { padding: 0 0 16px 28px; overflow-wrap: anywhere; }
|
|
100
|
-
.dsh-skills-
|
|
194
|
+
.dsh-skills-actions { display: flex; align-items: center; align-self: start; gap: 12px; height: 78px; }
|
|
195
|
+
.dsh-skills-folder { width: 28px; padding: 0; }
|
|
196
|
+
.dsh-skills-open-error { grid-column: 1 / -1; padding: 0 0 16px 28px; }
|
|
101
197
|
.dsh-skills-list-header { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; border-bottom: .5px solid var(--dsw-alias-border-l2); }
|
|
102
198
|
`;
|
|
199
|
+
function SkillDescription({ skill, connection, language, available, expanded }) {
|
|
200
|
+
const text = localizedCopy(language);
|
|
201
|
+
const [translated, setTranslated] = (0, react.useState)();
|
|
202
|
+
const [original, setOriginal] = (0, react.useState)(false);
|
|
203
|
+
const [pending, setPending] = (0, react.useState)(false);
|
|
204
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
205
|
+
const [retry, setRetry] = (0, react.useState)(0);
|
|
206
|
+
(0, react.useEffect)(() => {
|
|
207
|
+
setTranslated(void 0);
|
|
208
|
+
setOriginal(false);
|
|
209
|
+
setPending(false);
|
|
210
|
+
setFailed(false);
|
|
211
|
+
if (!expanded || !available || !skill.path || !skill.description) return;
|
|
212
|
+
const request = new AbortController();
|
|
213
|
+
setPending(true);
|
|
214
|
+
(async () => {
|
|
215
|
+
try {
|
|
216
|
+
const result = await connection.rpc.call(channel, translateMethod, {
|
|
217
|
+
path: skill.path,
|
|
218
|
+
language
|
|
219
|
+
}, request.signal);
|
|
220
|
+
if (request.signal.aborted) return;
|
|
221
|
+
if (!result.ok || typeof result.value !== "string") throw new Error("Could not translate description");
|
|
222
|
+
setTranslated(result.value);
|
|
223
|
+
} catch {
|
|
224
|
+
if (!request.signal.aborted) setFailed(true);
|
|
225
|
+
} finally {
|
|
226
|
+
if (!request.signal.aborted) setPending(false);
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
229
|
+
return () => request.abort();
|
|
230
|
+
}, [
|
|
231
|
+
expanded,
|
|
232
|
+
skill.description,
|
|
233
|
+
skill.path,
|
|
234
|
+
language,
|
|
235
|
+
available,
|
|
236
|
+
connection,
|
|
237
|
+
retry
|
|
238
|
+
]);
|
|
239
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: original || translated === void 0 ? skill.description : translated }), available && skill.path && skill.description && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
240
|
+
pending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
241
|
+
role: "status",
|
|
242
|
+
children: text.translating
|
|
243
|
+
}),
|
|
244
|
+
translated !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
245
|
+
variant: "ghost",
|
|
246
|
+
size: "sm",
|
|
247
|
+
onClick: () => setOriginal((value) => !value),
|
|
248
|
+
children: original ? text.translate : text.original
|
|
249
|
+
}),
|
|
250
|
+
failed && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
251
|
+
role: "alert",
|
|
252
|
+
children: text.translationError
|
|
253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
254
|
+
variant: "ghost",
|
|
255
|
+
size: "sm",
|
|
256
|
+
onClick: () => setRetry((value) => value + 1),
|
|
257
|
+
children: text.retry
|
|
258
|
+
})] })
|
|
259
|
+
] })] });
|
|
260
|
+
}
|
|
103
261
|
function SkillsSettings({ ctx }) {
|
|
104
262
|
const locale = (0, react.useSyncExternalStore)((listener) => ctx.locale.subscribe(listener), () => ctx.locale.getSnapshot());
|
|
105
263
|
const text = localizedCopy(locale.active);
|
|
@@ -109,8 +267,11 @@ function SkillsSettings({ ctx }) {
|
|
|
109
267
|
const [error, setError] = (0, react.useState)(false);
|
|
110
268
|
const [saveError, setSaveError] = (0, react.useState)(false);
|
|
111
269
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
270
|
+
const [folderStatus, setFolderStatus] = (0, react.useState)({});
|
|
271
|
+
const openingPending = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
112
272
|
const pending = (0, react.useRef)(false);
|
|
113
273
|
const mounted = (0, react.useRef)(false);
|
|
274
|
+
const [expanded, setExpanded] = (0, react.useState)({});
|
|
114
275
|
const [source, setSource] = (0, react.useState)("user-agents");
|
|
115
276
|
const groups = ["user-agents", "user-dsh"].map((value) => ({
|
|
116
277
|
value,
|
|
@@ -160,6 +321,28 @@ function SkillsSettings({ ctx }) {
|
|
|
160
321
|
if (mounted.current) setBusy(false);
|
|
161
322
|
}
|
|
162
323
|
}
|
|
324
|
+
async function openFolder(path) {
|
|
325
|
+
if (openingPending.current.has(path)) return;
|
|
326
|
+
openingPending.current.add(path);
|
|
327
|
+
setFolderStatus((current) => ({
|
|
328
|
+
...current,
|
|
329
|
+
[path]: "opening"
|
|
330
|
+
}));
|
|
331
|
+
try {
|
|
332
|
+
if (!(await connection.rpc.call("/api", "dungsil-ui-setting-skills/open-folder", { path })).ok) throw new Error("Could not open skill folder");
|
|
333
|
+
if (mounted.current) setFolderStatus((current) => ({
|
|
334
|
+
...current,
|
|
335
|
+
[path]: void 0
|
|
336
|
+
}));
|
|
337
|
+
} catch {
|
|
338
|
+
if (mounted.current) setFolderStatus((current) => ({
|
|
339
|
+
...current,
|
|
340
|
+
[path]: "error"
|
|
341
|
+
}));
|
|
342
|
+
} finally {
|
|
343
|
+
openingPending.current.delete(path);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
163
346
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
164
347
|
className: "dsh-skills-settings",
|
|
165
348
|
"aria-label": text.nav,
|
|
@@ -169,6 +352,14 @@ function SkillsSettings({ ctx }) {
|
|
|
169
352
|
className: "dsh-skills-muted",
|
|
170
353
|
children: text.description
|
|
171
354
|
}),
|
|
355
|
+
view && !view.folderOpenAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
356
|
+
className: "dsh-skills-muted",
|
|
357
|
+
children: text.openFolderUnavailable
|
|
358
|
+
}),
|
|
359
|
+
view?.translationAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
360
|
+
className: "dsh-skills-muted",
|
|
361
|
+
children: text.translationNotice
|
|
362
|
+
}),
|
|
172
363
|
!view && !error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
173
364
|
role: "status",
|
|
174
365
|
children: text.loading
|
|
@@ -212,45 +403,98 @@ function SkillsSettings({ ctx }) {
|
|
|
212
403
|
" ",
|
|
213
404
|
group.skills.length
|
|
214
405
|
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: text.use })]
|
|
215
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
216
|
-
"
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
406
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
408
|
+
onToggle: (event) => {
|
|
409
|
+
const open = event.currentTarget.open;
|
|
410
|
+
setExpanded((current) => ({
|
|
411
|
+
...current,
|
|
412
|
+
[skill.path ?? skill.name]: open
|
|
413
|
+
}));
|
|
414
|
+
},
|
|
415
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
|
|
416
|
+
"aria-label": skill.name,
|
|
417
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutlineRegular, {
|
|
418
|
+
size: 16,
|
|
419
|
+
"aria-hidden": "true"
|
|
420
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
421
|
+
className: "dsh-skills-summary-text",
|
|
422
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
423
|
+
className: "dsh-skills-title",
|
|
424
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: skill.name }), view.disabled.includes(skill.name) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
425
|
+
className: "dsh-skills-muted",
|
|
426
|
+
children: text.disabled
|
|
427
|
+
})]
|
|
428
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
429
|
+
className: "dsh-skills-preview dsh-skills-muted",
|
|
430
|
+
"aria-hidden": "true",
|
|
431
|
+
children: skill.description
|
|
432
|
+
})]
|
|
433
|
+
})]
|
|
434
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
435
|
+
className: "dsh-skills-detail",
|
|
436
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillDescription, {
|
|
437
|
+
skill,
|
|
438
|
+
connection,
|
|
439
|
+
language: locale.active,
|
|
440
|
+
available: view.translationAvailable && source === group.value,
|
|
441
|
+
expanded: expanded[skill.path ?? skill.name] ?? false
|
|
442
|
+
}), skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
225
443
|
className: "dsh-skills-muted",
|
|
226
|
-
children:
|
|
444
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: skill.path })
|
|
227
445
|
})]
|
|
228
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
229
|
-
className: "dsh-skills-preview dsh-skills-muted",
|
|
230
|
-
"aria-hidden": "true",
|
|
231
|
-
children: skill.description
|
|
232
446
|
})]
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
447
|
+
}),
|
|
448
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
449
|
+
className: "dsh-skills-actions",
|
|
450
|
+
children: [skill.path && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
451
|
+
label: folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder,
|
|
452
|
+
side: "top",
|
|
453
|
+
portal: true,
|
|
454
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
455
|
+
className: "dsh-skills-folder",
|
|
456
|
+
variant: "ghost",
|
|
457
|
+
size: "sm",
|
|
458
|
+
"aria-label": `${folderStatus[skill.path] === "opening" ? text.openingFolder : text.openFolder}: ${skill.name}`,
|
|
459
|
+
"aria-busy": folderStatus[skill.path] === "opening",
|
|
460
|
+
disabled: !view.folderOpenAvailable || folderStatus[skill.path] === "opening",
|
|
461
|
+
"aria-description": view.folderOpenAvailable ? text.openFolderHint : text.openFolderUnavailable,
|
|
462
|
+
onClick: () => void openFolder(skill.path),
|
|
463
|
+
icon: folderStatus[skill.path] === "opening" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutlineRegular, {
|
|
464
|
+
size: 16,
|
|
465
|
+
"aria-hidden": "true"
|
|
466
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutlineRegular, {
|
|
467
|
+
size: 16,
|
|
468
|
+
"aria-hidden": "true"
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
472
|
+
label: `${text.use}: ${skill.name}`,
|
|
473
|
+
checked: !view.disabled.includes(skill.name),
|
|
474
|
+
disabled: busy || saveError,
|
|
475
|
+
onChange: (enabled) => void save(skill.name, !enabled)
|
|
476
|
+
})]
|
|
477
|
+
}),
|
|
478
|
+
skill.path && folderStatus[skill.path] === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
479
|
+
className: "dsh-skills-open-error",
|
|
480
|
+
role: "alert",
|
|
481
|
+
children: text.openFolderError
|
|
247
482
|
})
|
|
248
|
-
|
|
483
|
+
] }, skill.path ?? skill.name)) })] })
|
|
249
484
|
}, group.value))] })
|
|
250
485
|
]
|
|
251
486
|
});
|
|
252
487
|
}
|
|
253
488
|
function apply(ctx) {
|
|
489
|
+
ctx.effect(() => installSettingsIcon(name, [
|
|
490
|
+
en_default.ui.nav,
|
|
491
|
+
ko_default.ui.nav,
|
|
492
|
+
zh_default.ui.nav
|
|
493
|
+
], (container) => {
|
|
494
|
+
const root = (0, react_dom_client.createRoot)(container);
|
|
495
|
+
root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 }));
|
|
496
|
+
return () => root.unmount();
|
|
497
|
+
}));
|
|
254
498
|
ctx.effect(() => {
|
|
255
499
|
const style = document.createElement("style");
|
|
256
500
|
style.textContent = styles;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
1
5
|
import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
|
|
6
|
+
import { dshCachePath } from "@deepseek-ai/dsh-home-paths";
|
|
7
|
+
import { canOpenNativePath, openNativePath } from "@deepseek-ai/dsh-native-command";
|
|
2
8
|
import { SkillRegistry } from "@deepseek-ai/dsh-skill";
|
|
3
9
|
import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
|
|
4
10
|
// ../../node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.6/node_modules/@deepseek-ai/cosmokit/lib/index.js
|
|
@@ -890,12 +896,15 @@ defineMethod("transform", [
|
|
|
890
896
|
const channel = "/api";
|
|
891
897
|
const method = "dungsil-ui-setting-skills/list";
|
|
892
898
|
const saveMethod = "dungsil-ui-setting-skills/save";
|
|
899
|
+
const translateMethod = "dungsil-ui-setting-skills/translate";
|
|
900
|
+
const openFolderMethod = "dungsil-ui-setting-skills/open-folder";
|
|
893
901
|
// src/index.ts
|
|
894
902
|
const name = "dungsil-ui-setting-skills";
|
|
895
903
|
const inject = ["connection", "configEditor"];
|
|
896
904
|
const Config = Schema.object({ disabled: Schema.array(Schema.string()).default([]) });
|
|
897
905
|
function apply(ctx, config = {}) {
|
|
898
906
|
const editor = ctx.configEditor;
|
|
907
|
+
const cacheDir = dshCachePath("dungsil-ui-setting-skills", "translations-v1");
|
|
899
908
|
const entry = () => editor.entries().find((entry) => entry.options.name === "@dungsil/dsh-plugin-ui-setting-skills");
|
|
900
909
|
const disabled = () => entry()?.options.config?.disabled ?? config.disabled ?? [];
|
|
901
910
|
ctx.effect(() => {
|
|
@@ -930,7 +939,12 @@ function apply(ctx, config = {}) {
|
|
|
930
939
|
lifetime.abort();
|
|
931
940
|
await provider.dispose();
|
|
932
941
|
}, "ui-setting-skills: dispose discovery");
|
|
933
|
-
for (const endpoint of [
|
|
942
|
+
for (const endpoint of [
|
|
943
|
+
method,
|
|
944
|
+
saveMethod,
|
|
945
|
+
translateMethod,
|
|
946
|
+
openFolderMethod
|
|
947
|
+
]) ctx.effect(() => ctx.connection.fetch.register({
|
|
934
948
|
path: `${channel}/${endpoint}`,
|
|
935
949
|
methods: ["POST"],
|
|
936
950
|
requestBody: "buffered",
|
|
@@ -942,6 +956,103 @@ function apply(ctx, config = {}) {
|
|
|
942
956
|
return new Response("Invalid request", { status: 400 });
|
|
943
957
|
}
|
|
944
958
|
if (!body.success || body.data.method !== endpoint) return new Response("Invalid request", { status: 400 });
|
|
959
|
+
if (endpoint === "dungsil-ui-setting-skills/open-folder") {
|
|
960
|
+
const payload = body.data.payload;
|
|
961
|
+
if (!payload || typeof payload.path !== "string") return new Response("Invalid request", { status: 400 });
|
|
962
|
+
let result;
|
|
963
|
+
try {
|
|
964
|
+
const signal = AbortSignal.any([
|
|
965
|
+
request.signal,
|
|
966
|
+
lifetime.signal,
|
|
967
|
+
AbortSignal.timeout(15e3)
|
|
968
|
+
]);
|
|
969
|
+
if (!canOpenNativePath()) throw new Error("Native folder opening unavailable");
|
|
970
|
+
const observation = await provider.list({ signal });
|
|
971
|
+
const skill = ("candidates" in observation ? observation.candidates : observation).find((skill) => skill.path === payload.path && ["user-agents", "user-dsh"].includes(skill.source));
|
|
972
|
+
if (!skill?.path) throw new Error("Unknown user skill");
|
|
973
|
+
await openNativePath(dirname(skill.path), signal);
|
|
974
|
+
result = {
|
|
975
|
+
ok: true,
|
|
976
|
+
value: {}
|
|
977
|
+
};
|
|
978
|
+
} catch {
|
|
979
|
+
result = {
|
|
980
|
+
ok: false,
|
|
981
|
+
error: {
|
|
982
|
+
code: "open_folder_failed",
|
|
983
|
+
message: "Could not open skill folder.",
|
|
984
|
+
details: {}
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
return Response.json({
|
|
989
|
+
type: "server-response",
|
|
990
|
+
rpcId: body.data.rpcId,
|
|
991
|
+
result
|
|
992
|
+
}, { headers: { "Cache-Control": "no-store" } });
|
|
993
|
+
}
|
|
994
|
+
if (endpoint === "dungsil-ui-setting-skills/translate") {
|
|
995
|
+
const payload = body.data.payload;
|
|
996
|
+
if (!payload || typeof payload.path !== "string" || typeof payload.language !== "string") return new Response("Invalid request", { status: 400 });
|
|
997
|
+
let result;
|
|
998
|
+
try {
|
|
999
|
+
const signal = AbortSignal.any([request.signal, lifetime.signal]);
|
|
1000
|
+
const translator = ctx.get("cotTranslation");
|
|
1001
|
+
if (!translator) throw new Error("Translation unavailable");
|
|
1002
|
+
const observation = await provider.list({ signal });
|
|
1003
|
+
const skill = ("candidates" in observation ? observation.candidates : observation).find((skill) => skill.path === payload.path && ["user-agents", "user-dsh"].includes(skill.source));
|
|
1004
|
+
if (!skill) throw new Error("Unknown user skill");
|
|
1005
|
+
const { preferences } = await translator.limits(signal);
|
|
1006
|
+
const targetLanguage = preferences.targetLanguage === "auto" ? payload.language : preferences.targetLanguage;
|
|
1007
|
+
const key = createHash("sha256").update(JSON.stringify([
|
|
1008
|
+
preferences.provider,
|
|
1009
|
+
targetLanguage,
|
|
1010
|
+
skill.description
|
|
1011
|
+
])).digest("hex");
|
|
1012
|
+
const cachePath = join(cacheDir, `${key}.json`);
|
|
1013
|
+
let value;
|
|
1014
|
+
try {
|
|
1015
|
+
const cached = JSON.parse(await readFile(cachePath, "utf8"));
|
|
1016
|
+
if (typeof cached === "string") value = cached;
|
|
1017
|
+
} catch {}
|
|
1018
|
+
signal.throwIfAborted();
|
|
1019
|
+
if (value === void 0) {
|
|
1020
|
+
value = await translator.translate({
|
|
1021
|
+
text: skill.description,
|
|
1022
|
+
provider: preferences.provider,
|
|
1023
|
+
targetLanguage
|
|
1024
|
+
}, signal);
|
|
1025
|
+
signal.throwIfAborted();
|
|
1026
|
+
try {
|
|
1027
|
+
await writeFileAtomic(cachePath, JSON.stringify(value), {
|
|
1028
|
+
mode: 384,
|
|
1029
|
+
dirMode: 448
|
|
1030
|
+
});
|
|
1031
|
+
} catch {
|
|
1032
|
+
ctx.logger.warn("Could not cache skill description translation");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
signal.throwIfAborted();
|
|
1036
|
+
result = {
|
|
1037
|
+
ok: true,
|
|
1038
|
+
value
|
|
1039
|
+
};
|
|
1040
|
+
} catch {
|
|
1041
|
+
result = {
|
|
1042
|
+
ok: false,
|
|
1043
|
+
error: {
|
|
1044
|
+
code: "translation_failed",
|
|
1045
|
+
message: "Could not translate skill description.",
|
|
1046
|
+
details: {}
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
return Response.json({
|
|
1051
|
+
type: "server-response",
|
|
1052
|
+
rpcId: body.data.rpcId,
|
|
1053
|
+
result
|
|
1054
|
+
}, { headers: { "Cache-Control": "no-store" } });
|
|
1055
|
+
}
|
|
945
1056
|
if (endpoint === "dungsil-ui-setting-skills/save") {
|
|
946
1057
|
const payload = body.data.payload;
|
|
947
1058
|
if (!payload || typeof payload.name !== "string" || typeof payload.disabled !== "boolean") return new Response("Invalid request", { status: 400 });
|
|
@@ -992,6 +1103,8 @@ function apply(ctx, config = {}) {
|
|
|
992
1103
|
result = {
|
|
993
1104
|
ok: true,
|
|
994
1105
|
value: {
|
|
1106
|
+
translationAvailable: !!ctx.get("cotTranslation"),
|
|
1107
|
+
folderOpenAvailable: canOpenNativePath(),
|
|
995
1108
|
disabled: disabled(),
|
|
996
1109
|
complete: "candidates" in observation ? observation.complete : true,
|
|
997
1110
|
skills: skills.flatMap((skill) => skill.source === "user-agents" || skill.source === "user-dsh" ? [{
|
package/locale/en.json
CHANGED
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
"incomplete": "Some skill sources could not be loaded. This list may be incomplete.",
|
|
15
15
|
"use": "Use",
|
|
16
16
|
"disabled": "Off",
|
|
17
|
+
"openFolder": "Open folder",
|
|
18
|
+
"openingFolder": "Opening folder…",
|
|
19
|
+
"openFolderHint": "Open this skill’s folder in the file manager on the DSH host.",
|
|
20
|
+
"openFolderUnavailable": "Folder opening requires a desktop on the DSH host.",
|
|
21
|
+
"openFolderError": "Could not open the skill folder. Check the file manager on the DSH host and try again.",
|
|
22
|
+
"translate": "View translation",
|
|
23
|
+
"original": "View original",
|
|
24
|
+
"retry": "Retry",
|
|
25
|
+
"translating": "Translating…",
|
|
26
|
+
"translationNotice": "Expanding a skill automatically sends its description to the service selected in reasoning translation settings. Translations are cached using its target language.",
|
|
27
|
+
"translationError": "Could not translate. The original is shown. Paid services require a Session and are unavailable here.",
|
|
17
28
|
"saveError": "Could not save the setting. Close and reopen this section to check the saved state before trying again."
|
|
18
29
|
}
|
|
19
30
|
}
|
package/locale/ko.json
CHANGED
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
"incomplete": "일부 스킬 소스를 불러오지 못해 목록이 완전하지 않을 수 있습니다.",
|
|
15
15
|
"use": "사용",
|
|
16
16
|
"disabled": "꺼짐",
|
|
17
|
+
"openFolder": "폴더 열기",
|
|
18
|
+
"openingFolder": "폴더를 여는 중…",
|
|
19
|
+
"openFolderHint": "DSH 실행 호스트의 파일 탐색기에서 이 스킬의 폴더를 엽니다.",
|
|
20
|
+
"openFolderUnavailable": "폴더를 열려면 DSH 실행 호스트에 데스크톱 환경이 필요합니다.",
|
|
21
|
+
"openFolderError": "스킬 폴더를 열지 못했습니다. DSH 실행 호스트의 파일 탐색기를 확인한 후 다시 시도해 주세요.",
|
|
22
|
+
"translate": "번역 보기",
|
|
23
|
+
"original": "원문 보기",
|
|
24
|
+
"retry": "다시 시도",
|
|
25
|
+
"translating": "번역 중…",
|
|
26
|
+
"translationNotice": "스킬을 펼치면 사고 과정 번역 설정에서 선택한 서비스로 설명을 자동 전송합니다. 번역문은 해당 설정의 대상 언어에 따라 캐시에 저장합니다.",
|
|
27
|
+
"translationError": "번역하지 못해 원문을 표시합니다. 유료 서비스는 세션이 필요하므로 이 화면에서 사용할 수 없습니다.",
|
|
17
28
|
"saveError": "설정을 저장하지 못했습니다. 이 설정 항목을 닫았다가 다시 열어 저장 상태를 확인한 후 다시 시도해 주세요."
|
|
18
29
|
}
|
|
19
30
|
}
|
package/locale/zh.json
CHANGED
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
"incomplete": "部分技能源无法加载,列表可能不完整。",
|
|
15
15
|
"use": "使用",
|
|
16
16
|
"disabled": "已关闭",
|
|
17
|
+
"openFolder": "打开文件夹",
|
|
18
|
+
"openingFolder": "正在打开文件夹…",
|
|
19
|
+
"openFolderHint": "在 DSH 主机的文件管理器中打开此技能的文件夹。",
|
|
20
|
+
"openFolderUnavailable": "打开文件夹需要 DSH 主机具备桌面环境。",
|
|
21
|
+
"openFolderError": "无法打开技能文件夹。请检查 DSH 主机的文件管理器后重试。",
|
|
22
|
+
"translate": "查看译文",
|
|
23
|
+
"original": "查看原文",
|
|
24
|
+
"retry": "重试",
|
|
25
|
+
"translating": "正在翻译…",
|
|
26
|
+
"translationNotice": "展开技能时,会自动将描述发送至思考过程翻译设置中选择的服务。译文按该设置的目标语言缓存。",
|
|
27
|
+
"translationError": "翻译失败,已显示原文。付费服务需要会话,无法在此页面使用。",
|
|
17
28
|
"saveError": "无法保存设置。请关闭并重新打开此设置项,确认已保存的状态后重试。"
|
|
18
29
|
}
|
|
19
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dungsil/dsh-plugin-ui-setting-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "View loaded user skills in DeepSeek Harness settings.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"locale",
|
|
14
|
+
"assets",
|
|
14
15
|
"cordis.patch.yml"
|
|
15
16
|
],
|
|
16
17
|
"type": "module",
|
|
@@ -23,6 +24,11 @@
|
|
|
23
24
|
"publishConfig": {
|
|
24
25
|
"access": "public"
|
|
25
26
|
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.5",
|
|
29
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.5",
|
|
30
|
+
"@deepseek-ai/dsh-native-command": "0.2.1-rc.5"
|
|
31
|
+
},
|
|
26
32
|
"devDependencies": {
|
|
27
33
|
"@deepseek-ai/cordis": "4.0.5",
|
|
28
34
|
"@deepseek-ai/dsh-app-boot": "0.2.1-rc.5",
|
|
@@ -32,6 +38,7 @@
|
|
|
32
38
|
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
|
|
33
39
|
"@deepseek-ai/dsh-config-editor": "0.2.1-rc.5",
|
|
34
40
|
"@deepseek-ai/dsh-credentials-local": "0.2.1-rc.5",
|
|
41
|
+
"@deepseek-ai/dsh-experimental-client-ui-cot-translation": "0.2.1-rc.5",
|
|
35
42
|
"@deepseek-ai/dsh-skill": "0.2.1-rc.5",
|
|
36
43
|
"@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.5",
|
|
37
44
|
"@deepseek-ai/schemastery": "3.18.5",
|
|
@@ -39,7 +46,10 @@
|
|
|
39
46
|
"@dungsil/dsh-tsdown": "0.0.0",
|
|
40
47
|
"@dungsil/dsh-ui": "0.0.0",
|
|
41
48
|
"@types/react": "18.3.31",
|
|
49
|
+
"@types/react-dom": "18.3.7",
|
|
50
|
+
"happy-dom": "21.0.4",
|
|
42
51
|
"react": "18.3.1",
|
|
52
|
+
"react-dom": "18.3.1",
|
|
43
53
|
"tsdown": "0.23.4"
|
|
44
54
|
},
|
|
45
55
|
"peerDependencies": {
|