dsh-ui-tweaks 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +102 -96
- package/README.md +15 -4
- package/lib/archive.js +242 -0
- package/lib/archive.js.map +1 -0
- package/lib/client.js +767 -8
- package/lib/client.js.map +1 -1
- package/lib/config.js +11 -3
- package/lib/config.js.map +1 -1
- package/lib/index.js +12 -2
- package/lib/index.js.map +1 -1
- package/lib/mcp.js +502 -0
- package/lib/mcp.js.map +1 -0
- package/lib/types/archive.d.ts +72 -0
- package/lib/types/archive.d.ts.map +1 -0
- package/lib/types/client/archive.d.ts +42 -0
- package/lib/types/client/archive.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +76 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/mcp.d.ts +29 -0
- package/lib/types/client/mcp.d.ts.map +1 -0
- package/lib/types/config.d.ts +23 -3
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/mcp.d.ts +104 -0
- package/lib/types/mcp.d.ts.map +1 -0
- package/lib/types/web.d.ts +17 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/web.js +6 -6
- package/lib/web.js.map +1 -1
- package/package.json +117 -110
package/lib/client.js
CHANGED
|
@@ -1,5 +1,203 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({ id: "dsh-ui-tweaks", factory: (require) => {
|
|
2
2
|
var __modules = Object.create(null); var __cache = Object.create(null);
|
|
3
|
+
__modules["./archive.js"] = function(module, exports, require, __load_) {
|
|
4
|
+
"use strict";
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ARCHIVE_CSS = void 0;
|
|
7
|
+
exports.installArchiveStyles = installArchiveStyles;
|
|
8
|
+
exports.ArchiveSection = ArchiveSection;
|
|
9
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
10
|
+
/**
|
|
11
|
+
* dsh-ui-tweaks — Archive manager (browser half).
|
|
12
|
+
*
|
|
13
|
+
* Renders the "归档" settings section (shown in the Settings dialog; usable
|
|
14
|
+
* when the `archiveManagerEnabled` toggle in 界面调整 is on — otherwise it
|
|
15
|
+
* shows an invite card with a one-click enable). The section lists every
|
|
16
|
+
* archived session — enriched from the framework `useSessions` /
|
|
17
|
+
* `useWorkspaces` feeds — with per-row actions and batch actions that go
|
|
18
|
+
* through the same-origin archive route (`/_dsh/ui-tweaks/archive`):
|
|
19
|
+
*
|
|
20
|
+
* - **恢复** (Restore): remove the session from the archive set — its log and
|
|
21
|
+
* workspace slot are kept, so the conversation reappears in the normal
|
|
22
|
+
* sidebar list.
|
|
23
|
+
* - **删除** (Delete): PERMANENTLY delete the session — the server removes its
|
|
24
|
+
* durable log, workspace accounting, archive-set entry and caches; the list
|
|
25
|
+
* then refreshes so the row (and the session itself) disappear.
|
|
26
|
+
* - **全部恢复 / 全部删除**: the same actions for every archived session.
|
|
27
|
+
*
|
|
28
|
+
* Live (open/running) sessions are refused by the server; the client maps the
|
|
29
|
+
* `session-live` error code to localized copy.
|
|
30
|
+
* @module dsh-ui-tweaks/client/archive
|
|
31
|
+
*/
|
|
32
|
+
const react_1 = require("react");
|
|
33
|
+
/** Route matching the host half (src/archive.ts). */
|
|
34
|
+
const ARCHIVE_ROUTE = '/_dsh/ui-tweaks/archive';
|
|
35
|
+
/** Fetch one archive route call, attaching the server error code when present. */
|
|
36
|
+
async function archiveRequest(init) {
|
|
37
|
+
const response = await fetch(ARCHIVE_ROUTE, { credentials: 'same-origin', ...init });
|
|
38
|
+
let body;
|
|
39
|
+
try {
|
|
40
|
+
body = await response.json();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Non-JSON response (e.g. the route is not mounted yet) — fall through to
|
|
44
|
+
// a clean error instead of a JSON-parse exception.
|
|
45
|
+
body = undefined;
|
|
46
|
+
}
|
|
47
|
+
if (!response.ok || body === undefined || !body.ok) {
|
|
48
|
+
const failure = body;
|
|
49
|
+
const error = new Error(failure?.error?.message ?? `UI Tweaks archive request failed with HTTP ${response.status}`);
|
|
50
|
+
error.code = failure?.error?.code;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
return body.value;
|
|
54
|
+
}
|
|
55
|
+
/** Map a thrown archive error to user-facing copy. */
|
|
56
|
+
function errorMessage(error, t) {
|
|
57
|
+
const code = error?.code;
|
|
58
|
+
if (code === 'session-live')
|
|
59
|
+
return t('archiveLiveError');
|
|
60
|
+
return error instanceof Error ? error.message : String(error);
|
|
61
|
+
}
|
|
62
|
+
/** Re-pull the host session list after a permanent delete (the outward
|
|
63
|
+
* ISessions face omits refresh; the concrete runtime provides it). */
|
|
64
|
+
function refreshSessions(sessionsService) {
|
|
65
|
+
void sessionsService.refresh();
|
|
66
|
+
}
|
|
67
|
+
/** Workspace display title: the path's last non-empty segment. */
|
|
68
|
+
function basenameOf(path) {
|
|
69
|
+
if (path === undefined || path === '')
|
|
70
|
+
return '';
|
|
71
|
+
const cleaned = path.replace(/[\\/]+$/u, '');
|
|
72
|
+
const segments = cleaned.split(/[\\/]/u);
|
|
73
|
+
return segments.at(-1) ?? '';
|
|
74
|
+
}
|
|
75
|
+
/** Locale-aware relative time for a past timestamp, e.g. "3小时前". */
|
|
76
|
+
function relativeTime(ms, now, formatter) {
|
|
77
|
+
const diffSeconds = Math.round((ms - now) / 1000);
|
|
78
|
+
const abs = Math.abs(diffSeconds);
|
|
79
|
+
if (abs < 60)
|
|
80
|
+
return formatter.format(diffSeconds, 'second');
|
|
81
|
+
if (abs < 3600)
|
|
82
|
+
return formatter.format(Math.round(diffSeconds / 60), 'minute');
|
|
83
|
+
if (abs < 86400)
|
|
84
|
+
return formatter.format(Math.round(diffSeconds / 3600), 'hour');
|
|
85
|
+
if (abs < 604800)
|
|
86
|
+
return formatter.format(Math.round(diffSeconds / 86400), 'day');
|
|
87
|
+
return formatter.format(Math.round(diffSeconds / 604800), 'week');
|
|
88
|
+
}
|
|
89
|
+
exports.ARCHIVE_CSS = `
|
|
90
|
+
.dut-arc{display:grid;gap:12px;max-width:680px;padding:6px 2px 36px}
|
|
91
|
+
.dut-arc-head{display:flex;align-items:center;gap:10px;padding:14px 16px 0}
|
|
92
|
+
.dut-arc-head h2{margin:0;font-size:15px;font-weight:600;letter-spacing:-.01em;color:var(--dsw-alias-label-primary)}
|
|
93
|
+
.dut-arc-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);padding:2px 8px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1)}
|
|
94
|
+
.dut-arc-spacer{flex:1}
|
|
95
|
+
.dut-arc-btn{display:inline-flex;align-items:center;height:28px;padding:0 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}
|
|
96
|
+
.dut-arc-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
97
|
+
.dut-arc-btn:disabled{opacity:.5;cursor:default}
|
|
98
|
+
.dut-arc-btn.dut-arc-del:hover{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
|
|
99
|
+
.dut-arc-btn.dut-arc-confirm{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);color:var(--dsw-alias-state-error-primary);font-weight:600}
|
|
100
|
+
.dut-arc-body{display:grid;gap:2px;margin-top:4px}
|
|
101
|
+
.dut-arc-row{display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:12px}
|
|
102
|
+
.dut-arc-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
103
|
+
.dut-arc-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left;background:transparent;border:none;padding:0;cursor:default;color:var(--dsw-alias-label-primary);font:inherit}
|
|
104
|
+
.dut-arc-title{font-size:13.5px;font-weight:600;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
|
|
105
|
+
.dut-arc-sub{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
|
|
106
|
+
.dut-arc-empty{padding:32px 16px;text-align:center;font-size:12.5px;color:var(--dsw-alias-label-secondary)}
|
|
107
|
+
.dut-arc-alert{margin:8px 16px 0;padding:8px 12px;border-radius:10px;font-size:12px;line-height:1.5;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
108
|
+
.dut-arc-off{padding:20px 16px;display:grid;gap:12px}
|
|
109
|
+
.dut-arc-off p{margin:0;font-size:12.5px;line-height:1.6;color:var(--dsw-alias-label-secondary)}
|
|
110
|
+
`;
|
|
111
|
+
/** Install the Archive stylesheet once (idempotent); returns the disposer. */
|
|
112
|
+
function installArchiveStyles() {
|
|
113
|
+
const id = 'dsh-ui-tweaks-archive';
|
|
114
|
+
const existing = document.querySelector(`style[data-plugin-css="${id}"]`);
|
|
115
|
+
if (existing !== null)
|
|
116
|
+
return () => { };
|
|
117
|
+
const style = document.createElement('style');
|
|
118
|
+
style.dataset.plugin = 'dsh-ui-tweaks';
|
|
119
|
+
style.dataset.pluginCss = id;
|
|
120
|
+
style.textContent = exports.ARCHIVE_CSS;
|
|
121
|
+
document.head.appendChild(style);
|
|
122
|
+
return () => { style.remove(); };
|
|
123
|
+
}
|
|
124
|
+
function ArchiveSection({ controller, t, sessionsService, useSessions, useWorkspaces }) {
|
|
125
|
+
const settingsState = (0, react_1.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
126
|
+
const enabled = settingsState.value?.archiveManagerEnabled === true;
|
|
127
|
+
const sessions = useSessions(state => state);
|
|
128
|
+
const workspaces = useWorkspaces(state => state);
|
|
129
|
+
const [confirmId, setConfirmId] = (0, react_1.useState)(null);
|
|
130
|
+
const [confirmAll, setConfirmAll] = (0, react_1.useState)(false);
|
|
131
|
+
const [busy, setBusy] = (0, react_1.useState)(null);
|
|
132
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
133
|
+
const timeFormatter = (0, react_1.useMemo)(() => new Intl.RelativeTimeFormat(navigator.language, { numeric: 'auto' }), []);
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
(0, react_1.useEffect)(() => {
|
|
136
|
+
if (confirmId === null)
|
|
137
|
+
return;
|
|
138
|
+
const timer = window.setTimeout(() => { setConfirmId(current => (current === confirmId ? null : current)); }, 3000);
|
|
139
|
+
return () => { window.clearTimeout(timer); };
|
|
140
|
+
}, [confirmId]);
|
|
141
|
+
(0, react_1.useEffect)(() => {
|
|
142
|
+
if (!confirmAll)
|
|
143
|
+
return;
|
|
144
|
+
const timer = window.setTimeout(() => { setConfirmAll(false); }, 3000);
|
|
145
|
+
return () => { window.clearTimeout(timer); };
|
|
146
|
+
}, [confirmAll]);
|
|
147
|
+
const rows = (0, react_1.useMemo)(() => {
|
|
148
|
+
const byId = sessions.byId;
|
|
149
|
+
return workspaces.archivedSessionIds.map(id => ({ id, summary: byId[id] })).sort((a, b) => {
|
|
150
|
+
const ta = a.summary?.updatedAt ?? 0;
|
|
151
|
+
const tb = b.summary?.updatedAt ?? 0;
|
|
152
|
+
return tb - ta;
|
|
153
|
+
});
|
|
154
|
+
}, [workspaces.archivedSessionIds, sessions.byId]);
|
|
155
|
+
const run = (key, body, after) => {
|
|
156
|
+
setBusy(key);
|
|
157
|
+
setError(null);
|
|
158
|
+
void archiveRequest({
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
}).then(() => { after?.(); }).catch((reason) => {
|
|
163
|
+
setError(errorMessage(reason, t));
|
|
164
|
+
}).finally(() => { setBusy(current => (current === key ? null : current)); });
|
|
165
|
+
};
|
|
166
|
+
const restore = (id) => {
|
|
167
|
+
run(`restore:${id}`, { action: 'restore', sessionId: id });
|
|
168
|
+
};
|
|
169
|
+
const remove = (id) => {
|
|
170
|
+
if (confirmId !== id) {
|
|
171
|
+
setConfirmId(id);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
setConfirmId(null);
|
|
175
|
+
run(`delete:${id}`, { action: 'delete', sessionId: id }, () => { refreshSessions(sessionsService); });
|
|
176
|
+
};
|
|
177
|
+
const restoreAll = () => {
|
|
178
|
+
run('restore-all', { action: 'restore-all' });
|
|
179
|
+
};
|
|
180
|
+
const removeAll = () => {
|
|
181
|
+
if (!confirmAll) {
|
|
182
|
+
setConfirmAll(true);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
setConfirmAll(false);
|
|
186
|
+
run('delete-all', { action: 'delete-all' }, () => { refreshSessions(sessionsService); });
|
|
187
|
+
};
|
|
188
|
+
if (!enabled) {
|
|
189
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "dut-arc", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-panel dut-arc-off", children: [(0, jsx_runtime_1.jsx)("p", { children: t('archiveDisabledHint') }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-btn dut-btn-active", onClick: () => {
|
|
190
|
+
void controller.set('archiveManagerEnabled', true).catch(() => { setError(t('unavailable')); });
|
|
191
|
+
}, children: t('archiveEnable') }) })] }) }));
|
|
192
|
+
}
|
|
193
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "dut-arc", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-arc-head", children: [(0, jsx_runtime_1.jsx)("h2", { children: t('archiveTitle') }), (0, jsx_runtime_1.jsxs)("span", { className: "dut-arc-count", children: [rows.length, " ", t('archiveCount')] }), (0, jsx_runtime_1.jsx)("span", { className: "dut-arc-spacer" }), rows.length > 1 ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-arc-btn", disabled: busy !== null, onClick: restoreAll, children: busy === 'restore-all' ? t('archiveRestoring') : t('archiveRestoreAll') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-arc-btn dut-arc-del' + (confirmAll ? ' dut-arc-confirm' : ''), disabled: busy !== null, onClick: removeAll, children: busy === 'delete-all' ? t('archiveDeleteAll') + '…' : confirmAll ? t('archiveDeleteAll') + '?' : t('archiveDeleteAll') })] })) : null] }), error !== null ? (0, jsx_runtime_1.jsx)("div", { className: "dut-arc-alert", children: error }) : null, (0, jsx_runtime_1.jsx)("div", { className: "dut-arc-body", children: rows.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "dut-arc-empty", children: t('archiveEmpty') })) : rows.map(row => {
|
|
194
|
+
const summary = row.summary;
|
|
195
|
+
const title = summary?.displayTitle ?? row.id;
|
|
196
|
+
const sub = [basenameOf(summary?.cwd), summary !== undefined ? relativeTime(summary.updatedAt, now, timeFormatter) : ''].filter(Boolean).join(' · ');
|
|
197
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dut-arc-row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-arc-main", title: title, children: [(0, jsx_runtime_1.jsx)("span", { className: "dut-arc-title", children: title }), sub !== '' ? (0, jsx_runtime_1.jsx)("span", { className: "dut-arc-sub", children: sub }) : null] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-arc-btn", disabled: busy !== null, onClick: () => { restore(row.id); }, children: busy === `restore:${row.id}` ? t('archiveRestoring') : t('archiveRestore') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-arc-btn dut-arc-del' + (confirmId === row.id ? ' dut-arc-confirm' : ''), disabled: busy !== null, onClick: () => { remove(row.id); }, children: busy === `delete:${row.id}` ? t('archiveDelete') + '…' : confirmId === row.id ? t('archiveDelete') + '?' : t('archiveDelete') })] }, row.id));
|
|
198
|
+
}) })] }) }));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
3
201
|
__modules["./gitbar.js"] = function(module, exports, require, __load_) {
|
|
4
202
|
"use strict";
|
|
5
203
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -937,6 +1135,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
937
1135
|
const react_1 = require("react");
|
|
938
1136
|
const timeline_tsx_1 = __load_("./timeline.js");
|
|
939
1137
|
const gitbar_tsx_1 = __load_("./gitbar.js");
|
|
1138
|
+
const archive_tsx_1 = __load_("./archive.js");
|
|
1139
|
+
const mcp_tsx_1 = __load_("./mcp.js");
|
|
940
1140
|
const NS = 'ui-tweaks';
|
|
941
1141
|
const SETTINGS_ROUTE = '/_dsh/ui-tweaks/settings';
|
|
942
1142
|
const DEFAULT_FONT_SIZE = 16;
|
|
@@ -964,13 +1164,82 @@ const en = {
|
|
|
964
1164
|
presetWide: 'Wide',
|
|
965
1165
|
presetWideXl: 'Extra wide',
|
|
966
1166
|
timeline: 'Timeline',
|
|
967
|
-
timelineHint: 'Show a navigation rail on the right of the message area: hover to preview user messages, click to jump to them. Off by default;
|
|
1167
|
+
timelineHint: 'Show a navigation rail on the right of the message area: hover to preview user messages, click to jump to them. Off by default; turn it on to enable. Automatically hidden in short conversations.',
|
|
968
1168
|
timelineOn: 'On',
|
|
969
|
-
|
|
1169
|
+
timelineOff: 'Off',
|
|
970
1170
|
gitBar: 'Git bar',
|
|
971
|
-
gitBarHint: 'Show branch / diff / commit-message pills above the input when the session is inside a git repository.
|
|
1171
|
+
gitBarHint: 'Show branch / diff / commit-message pills above the input when the session is inside a git repository. Off by default; turn it on to enable. The bar auto-hides outside git repos.',
|
|
972
1172
|
gitBarOn: 'On',
|
|
973
1173
|
gitBarOff: 'Off',
|
|
1174
|
+
archiveManager: 'Archive manager',
|
|
1175
|
+
archiveManagerHint: 'Show an Archive section in the Settings dialog where archived sessions can be restored or permanently deleted. Off by default; turn it on to enable.',
|
|
1176
|
+
archiveManagerOn: 'On',
|
|
1177
|
+
archiveManagerOff: 'Off',
|
|
1178
|
+
mcpManager: 'MCP manager',
|
|
1179
|
+
mcpManagerHint: 'Show an MCP management section in the Settings dialog listing the configured MCP servers with their status and tools, and let you restart them. Off by default; turn it on to enable.',
|
|
1180
|
+
mcpManagerOn: 'On',
|
|
1181
|
+
mcpManagerOff: 'Off',
|
|
1182
|
+
mcpNav: 'MCP',
|
|
1183
|
+
mcpTitle: 'MCP servers',
|
|
1184
|
+
mcpEmpty: 'No MCP servers configured.',
|
|
1185
|
+
mcpStatusActive: 'Running',
|
|
1186
|
+
mcpStatusFailed: 'Failed',
|
|
1187
|
+
mcpStatusLoading: 'Loading',
|
|
1188
|
+
mcpStatusStopped: 'Stopped',
|
|
1189
|
+
mcpStatusDisabled: 'Disabled',
|
|
1190
|
+
mcpTools: 'tools',
|
|
1191
|
+
mcpEnv: 'Env',
|
|
1192
|
+
mcpAdd: 'Add server',
|
|
1193
|
+
mcpAddTitle: 'Add MCP server',
|
|
1194
|
+
mcpEditTitle: 'Edit MCP server',
|
|
1195
|
+
mcpEdit: 'Edit',
|
|
1196
|
+
mcpDelete: 'Delete',
|
|
1197
|
+
mcpEnabledAction: 'Enable',
|
|
1198
|
+
mcpDisabledAction: 'Disable',
|
|
1199
|
+
mcpSaved: 'Saved.',
|
|
1200
|
+
mcpRemoved: 'Removed.',
|
|
1201
|
+
mcpFormTab: 'Form',
|
|
1202
|
+
mcpYamlTab: 'YAML',
|
|
1203
|
+
mcpYamlHint: 'Fill in the MCP config directly (serverName / transport / command / args / env / toolCallTimeoutMs / url / headers …). It is validated before saving.',
|
|
1204
|
+
mcpYamlPlaceholder: 'serverName: my-server\ntransport: stdio\ncommand: npx\nargs:\n - "-y"\n - "@some/mcp-server"\nenv:\n KEY: value\ntoolCallTimeoutMs: 60000',
|
|
1205
|
+
mcpFieldId: 'Instance ID',
|
|
1206
|
+
mcpFieldIdHint: 'Loader entry id (letters, digits, - and _ only); it must be unique.',
|
|
1207
|
+
mcpFieldName: 'Name',
|
|
1208
|
+
mcpFieldNameHint: 'Tool namespace `mcp__<name>__*`; letters, digits, - and _ (1–32).',
|
|
1209
|
+
mcpFieldType: 'Type',
|
|
1210
|
+
mcpFieldTimeout: 'Timeout (ms)',
|
|
1211
|
+
mcpFieldCommand: 'Command',
|
|
1212
|
+
mcpFieldCommandPlaceholder: 'e.g. npx',
|
|
1213
|
+
mcpFieldArgs: 'Arguments (one per line)',
|
|
1214
|
+
mcpFieldEnv: 'Environment vars (optional, KEY=value per line)',
|
|
1215
|
+
mcpFieldUrl: 'URL',
|
|
1216
|
+
mcpFieldHeaders: 'Headers (optional, Key: value per line)',
|
|
1217
|
+
mcpFieldEnabled: 'Enabled',
|
|
1218
|
+
mcpSave: 'Save',
|
|
1219
|
+
mcpCancel: 'Cancel',
|
|
1220
|
+
mcpInvalidId: 'Instance ID may only contain letters, digits, - and _.',
|
|
1221
|
+
mcpInvalidName: 'Name may only contain letters, digits, - and _ (1–32 chars).',
|
|
1222
|
+
mcpInvalidTimeout: 'Timeout must be a positive integer (ms).',
|
|
1223
|
+
mcpInvalidUrl: 'URL must start with http:// or https://.',
|
|
1224
|
+
mcpInvalidCommand: 'Command is required.',
|
|
1225
|
+
mcpUnavailable: 'MCP manager unavailable.',
|
|
1226
|
+
mcpDisabledHint: 'MCP management is off. Turn it on in 界面调整 (UI Tweaks) to view and restart MCP servers here.',
|
|
1227
|
+
mcpEnable: 'Enable MCP manager',
|
|
1228
|
+
mcpServerDetail: 'Configured in the profile cordis.patch.yml as @deepseek-ai/dsh-mcp-client instances; add / edit / disable / delete write to that file and apply live.',
|
|
1229
|
+
archiveNav: 'Archive',
|
|
1230
|
+
archiveTitle: 'Archived sessions',
|
|
1231
|
+
archiveEmpty: 'No archived sessions.',
|
|
1232
|
+
archiveRestore: 'Restore',
|
|
1233
|
+
archiveRestoring: 'Working…',
|
|
1234
|
+
archiveDelete: 'Delete',
|
|
1235
|
+
archiveDeleteAll: 'Delete all',
|
|
1236
|
+
archiveRestoreAll: 'Restore all',
|
|
1237
|
+
archiveCount: 'sessions',
|
|
1238
|
+
archiveUnavailable: 'Archive unavailable.',
|
|
1239
|
+
archiveLiveError: 'This session is running; close it before permanently deleting it.',
|
|
1240
|
+
archiveDisabledHint: 'Archive management is off. Turn it on in 界面调整 (UI Tweaks) to restore or permanently delete archived sessions here.',
|
|
1241
|
+
archiveEnable: 'Enable archive management',
|
|
1242
|
+
archiveRestored: 'Restored.',
|
|
974
1243
|
railLabel: 'Chat timeline',
|
|
975
1244
|
roleUser: 'User',
|
|
976
1245
|
noText: '(no text)',
|
|
@@ -1040,13 +1309,82 @@ const zh = {
|
|
|
1040
1309
|
presetWide: '稍宽',
|
|
1041
1310
|
presetWideXl: '更宽',
|
|
1042
1311
|
timeline: '时间线',
|
|
1043
|
-
timelineHint: '
|
|
1312
|
+
timelineHint: '在消息区右侧显示导航轨:悬停预览用户消息、点击跳转。默认关闭,需手动开启;会话较短时自动隐藏。',
|
|
1044
1313
|
timelineOn: '开启',
|
|
1045
|
-
|
|
1314
|
+
timelineOff: '关闭',
|
|
1046
1315
|
gitBar: 'Git 状态栏',
|
|
1047
|
-
gitBarHint: '会话在 git 仓库内时,在输入框上方显示 分支 / 差异 / 提交说明
|
|
1316
|
+
gitBarHint: '会话在 git 仓库内时,在输入框上方显示 分支 / 差异 / 提交说明 胶囊。默认关闭,需手动开启;非 git 仓库时自动隐藏。',
|
|
1048
1317
|
gitBarOn: '开启',
|
|
1049
1318
|
gitBarOff: '关闭',
|
|
1319
|
+
archiveManager: '归档管理',
|
|
1320
|
+
archiveManagerHint: '在设置中显示「归档」页面:可查看、恢复或彻底删除已归档会话。默认关闭,需手动开启。',
|
|
1321
|
+
archiveManagerOn: '开启',
|
|
1322
|
+
archiveManagerOff: '关闭',
|
|
1323
|
+
mcpManager: 'MCP 管理',
|
|
1324
|
+
mcpManagerHint: '在设置中显示「MCP 管理」页面:查看已配置的 MCP 服务器及其状态与工具,并可重启。默认关闭,需手动开启。',
|
|
1325
|
+
mcpManagerOn: '开启',
|
|
1326
|
+
mcpManagerOff: '关闭',
|
|
1327
|
+
mcpNav: 'MCP 管理',
|
|
1328
|
+
mcpTitle: 'MCP 服务器',
|
|
1329
|
+
mcpEmpty: '未配置任何 MCP 服务器。',
|
|
1330
|
+
mcpStatusActive: '运行中',
|
|
1331
|
+
mcpStatusFailed: '错误',
|
|
1332
|
+
mcpStatusLoading: '加载中',
|
|
1333
|
+
mcpStatusStopped: '未运行',
|
|
1334
|
+
mcpStatusDisabled: '已停用',
|
|
1335
|
+
mcpTools: '个工具',
|
|
1336
|
+
mcpEnv: '环境变量',
|
|
1337
|
+
mcpAdd: '添加服务器',
|
|
1338
|
+
mcpAddTitle: '添加 MCP 服务器',
|
|
1339
|
+
mcpEditTitle: '编辑 MCP 服务器',
|
|
1340
|
+
mcpEdit: '编辑',
|
|
1341
|
+
mcpDelete: '删除',
|
|
1342
|
+
mcpEnabledAction: '启用',
|
|
1343
|
+
mcpDisabledAction: '停用',
|
|
1344
|
+
mcpSaved: '已保存。',
|
|
1345
|
+
mcpRemoved: '已删除。',
|
|
1346
|
+
mcpFormTab: '表单',
|
|
1347
|
+
mcpYamlTab: 'YAML',
|
|
1348
|
+
mcpYamlHint: '直接填写 MCP 配置(serverName / transport / command / args / env / toolCallTimeoutMs / url / headers …),保存前会校验格式。',
|
|
1349
|
+
mcpYamlPlaceholder: 'serverName: my-server\ntransport: stdio\ncommand: npx\nargs:\n - "-y"\n - "@some/mcp-server"\nenv:\n KEY: value\ntoolCallTimeoutMs: 60000',
|
|
1350
|
+
mcpFieldId: '实例 ID',
|
|
1351
|
+
mcpFieldIdHint: '加载器条目 ID(仅字母、数字、- 和 _),需唯一。',
|
|
1352
|
+
mcpFieldName: '名称',
|
|
1353
|
+
mcpFieldNameHint: '工具命名空间 `mcp__<名称>__*`;字母、数字、- 和 _(1–32 字符)。',
|
|
1354
|
+
mcpFieldType: '类型',
|
|
1355
|
+
mcpFieldTimeout: '超时时间 (ms)',
|
|
1356
|
+
mcpFieldCommand: '命令',
|
|
1357
|
+
mcpFieldCommandPlaceholder: '如 npx',
|
|
1358
|
+
mcpFieldArgs: '参数(每行一个)',
|
|
1359
|
+
mcpFieldEnv: '环境变量(可选,每行 KEY=值)',
|
|
1360
|
+
mcpFieldUrl: 'URL',
|
|
1361
|
+
mcpFieldHeaders: '请求头(可选,每行 Key: 值)',
|
|
1362
|
+
mcpFieldEnabled: '启用',
|
|
1363
|
+
mcpSave: '保存',
|
|
1364
|
+
mcpCancel: '取消',
|
|
1365
|
+
mcpInvalidId: '实例 ID 只能包含字母、数字、- 和 _。',
|
|
1366
|
+
mcpInvalidName: '名称只能包含字母、数字、- 和 _(1–32 字符)。',
|
|
1367
|
+
mcpInvalidTimeout: '超时时间必须是正整数(毫秒)。',
|
|
1368
|
+
mcpInvalidUrl: 'URL 必须以 http:// 或 https:// 开头。',
|
|
1369
|
+
mcpInvalidCommand: '命令不能为空。',
|
|
1370
|
+
mcpUnavailable: 'MCP 管理暂不可用。',
|
|
1371
|
+
mcpDisabledHint: 'MCP 管理尚未开启。在「界面调整」中开启“MCP 管理”后,可在此查看并重启 MCP 服务器。',
|
|
1372
|
+
mcpEnable: '开启 MCP 管理',
|
|
1373
|
+
mcpServerDetail: 'MCP 服务器配置在 profile 的 cordis.patch.yml(@deepseek-ai/dsh-mcp-client 实例);添加 / 编辑 / 停用 / 删除会写入该文件,改动实时生效。',
|
|
1374
|
+
archiveNav: '归档',
|
|
1375
|
+
archiveTitle: '已归档会话',
|
|
1376
|
+
archiveEmpty: '暂无归档会话。',
|
|
1377
|
+
archiveRestore: '恢复',
|
|
1378
|
+
archiveRestoring: '处理中…',
|
|
1379
|
+
archiveDelete: '删除',
|
|
1380
|
+
archiveDeleteAll: '全部删除',
|
|
1381
|
+
archiveRestoreAll: '全部恢复',
|
|
1382
|
+
archiveCount: '个会话',
|
|
1383
|
+
archiveUnavailable: '归档暂不可用。',
|
|
1384
|
+
archiveLiveError: '该会话正在运行,无法彻底删除,请先关闭该会话。',
|
|
1385
|
+
archiveDisabledHint: '归档管理尚未开启。在「界面调整」中开启“归档管理”后,可在此查看、恢复或彻底删除已归档会话。',
|
|
1386
|
+
archiveEnable: '开启归档管理',
|
|
1387
|
+
archiveRestored: '已恢复。',
|
|
1050
1388
|
railLabel: '对话时间线',
|
|
1051
1389
|
roleUser: '用户',
|
|
1052
1390
|
noText: '(无文本内容)',
|
|
@@ -1111,7 +1449,9 @@ function resolveValue(value) {
|
|
|
1111
1449
|
tableStyle: value?.tableStyle === 'claude' ? 'claude' : 'default',
|
|
1112
1450
|
dialogWidth: resolveDialogWidth(value?.dialogWidth),
|
|
1113
1451
|
timelineEnabled: value?.timelineEnabled ?? false,
|
|
1114
|
-
gitBarEnabled: value?.gitBarEnabled ??
|
|
1452
|
+
gitBarEnabled: value?.gitBarEnabled ?? false,
|
|
1453
|
+
archiveManagerEnabled: value?.archiveManagerEnabled ?? false,
|
|
1454
|
+
mcpManagerEnabled: value?.mcpManagerEnabled ?? false,
|
|
1115
1455
|
};
|
|
1116
1456
|
}
|
|
1117
1457
|
/** Scale one theme px value proportionally to the chosen base size. */
|
|
@@ -1420,6 +1760,12 @@ function SettingsSection({ controller, t }) {
|
|
|
1420
1760
|
const setGitBar = (value) => {
|
|
1421
1761
|
void controller.set('gitBarEnabled', value).then(() => { setStatus('applied'); }).catch(() => { setStatus('unavailable'); });
|
|
1422
1762
|
};
|
|
1763
|
+
const setArchiveManager = (value) => {
|
|
1764
|
+
void controller.set('archiveManagerEnabled', value).then(() => { setStatus('applied'); }).catch(() => { setStatus('unavailable'); });
|
|
1765
|
+
};
|
|
1766
|
+
const setMcpManager = (value) => {
|
|
1767
|
+
void controller.set('mcpManagerEnabled', value).then(() => { setStatus('applied'); }).catch(() => { setStatus('unavailable'); });
|
|
1768
|
+
};
|
|
1423
1769
|
const reset = (field) => {
|
|
1424
1770
|
void controller.unset(field).then(() => { setStatus('resetDone'); }).catch(() => { setStatus('unavailable'); });
|
|
1425
1771
|
};
|
|
@@ -1431,12 +1777,44 @@ function SettingsSection({ controller, t }) {
|
|
|
1431
1777
|
}
|
|
1432
1778
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "dut-settings", children: [(0, jsx_runtime_1.jsxs)("header", { className: "dut-settings-header", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-logo", children: "\uD83C\uDFA8" }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { children: t('settingsTitle') }), (0, jsx_runtime_1.jsx)("p", { children: t('settingsIntro') })] })] }), !writable ? (0, jsx_runtime_1.jsx)("div", { className: "dut-alert warning", children: t('readOnly') }) : null, status === undefined ? null : (0, jsx_runtime_1.jsx)("div", { className: "dut-status", children: t(status) }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionText') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('fontSize') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.fontSize <= MIN_FONT_SIZE, onClick: () => { stepFontSize(-1); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: MIN_FONT_SIZE, max: MAX_FONT_SIZE, step: 1, value: draft, disabled: !writable, onChange: (event) => { setDraft(event.target.value); }, onBlur: (event) => { commitFontSize(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
|
|
1433
1779
|
commitFontSize(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.fontSize >= MAX_FONT_SIZE, onClick: () => { stepFontSize(1); }, children: "+" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-btn' + (resolved.fontSize === DEFAULT_FONT_SIZE ? ' dut-btn-active' : ''), disabled: !writable, onClick: () => { reset('fontSize'); }, children: t('defaultAction') })] })] }), (0, jsx_runtime_1.jsx)("small", { children: t('fontSizeHint') })] })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionContent') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-field", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('tableStyle') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.tableStyle === 'claude' ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { pickTableStyle('claude'); }, children: t('tableStyleClaude') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.tableStyle === 'default' ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { pickTableStyle('default'); }, children: t('tableStyleDefault') })] }) })] }) })] }), (0, jsx_runtime_1.jsxs)("section", { className: "dut-panel", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-section-label", children: t('sectionLayout') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('dialogWidth') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-stepper", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "\u2212", disabled: !writable || resolved.dialogWidth <= MIN_DIALOG_WIDTH, onClick: () => { stepDialogWidth(-20); }, children: "\u2212" }), (0, jsx_runtime_1.jsx)("input", { type: "number", min: MIN_DIALOG_WIDTH, max: MAX_DIALOG_WIDTH, step: 20, value: widthDraft, disabled: !writable, onChange: (event) => { setWidthDraft(event.target.value); }, onBlur: (event) => { commitDialogWidth(event.target.value); }, onKeyDown: (event) => { if (event.key === 'Enter')
|
|
1434
|
-
commitDialogWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.dialogWidth >= MAX_DIALOG_WIDTH, onClick: () => { stepDialogWidth(20); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 880 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(880); }, children: [t('presetWide'), " \u00B7 880"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 1024 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(1024); }, children: [t('presetWideXl'), " \u00B7 1024"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 748 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(748); }, children: [t('presetDefault'), " \u00B7 748"] })] }) }), (0, jsx_runtime_1.jsx)("small", { children: t('dialogWidthHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('timeline') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(true); }, children: t('timelineOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(false); }, children: t('
|
|
1780
|
+
commitDialogWidth(event.target.value); } }), (0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": "+", disabled: !writable || resolved.dialogWidth >= MAX_DIALOG_WIDTH, onClick: () => { stepDialogWidth(20); }, children: "+" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-presets", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 880 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(880); }, children: [t('presetWide'), " \u00B7 880"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 1024 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(1024); }, children: [t('presetWideXl'), " \u00B7 1024"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: resolved.dialogWidth === 748 ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { applyWidthPreset(748); }, children: [t('presetDefault'), " \u00B7 748"] })] }) }), (0, jsx_runtime_1.jsx)("small", { children: t('dialogWidthHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('timeline') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(true); }, children: t('timelineOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.timelineEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setTimeline(false); }, children: t('timelineOff') })] }) })] }), (0, jsx_runtime_1.jsx)("small", { children: t('timelineHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('gitBar') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(true); }, children: t('gitBarOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.gitBarEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setGitBar(false); }, children: t('gitBarOff') })] }) })] }), (0, jsx_runtime_1.jsx)("small", { children: t('gitBarHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('archiveManager') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(true); }, children: t('archiveManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.archiveManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setArchiveManager(false); }, children: t('archiveManagerOff') })] }) })] }), (0, jsx_runtime_1.jsx)("small", { children: t('archiveManagerHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-field", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-field-top", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpManager') }), (0, jsx_runtime_1.jsx)("div", { className: "dut-controls", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(true); }, children: t('mcpManagerOn') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: !resolved.mcpManagerEnabled ? 'dut-seg-active' : '', disabled: !writable, onClick: () => { setMcpManager(false); }, children: t('mcpManagerOff') })] }) })] }), (0, jsx_runtime_1.jsx)("small", { children: t('mcpManagerHint') })] })] })] }));
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Register a Settings section only while its enable flag is on, so the
|
|
1784
|
+
* Settings nav row appears / disappears live with the toggle in the UI Tweaks
|
|
1785
|
+
* section. Waits for the `settings.section` declaration (like the static
|
|
1786
|
+
* inject), then keeps the section registered exactly while
|
|
1787
|
+
* `isEnabled(controller value)` holds: toggling the switch in the UI Tweaks
|
|
1788
|
+
* section mounts or disposes the section, and the settings shell's nav
|
|
1789
|
+
* (projected from the section ledger) updates in place.
|
|
1790
|
+
*/
|
|
1791
|
+
function installConditionalSection(ctx, controller, isEnabled, registerSection) {
|
|
1792
|
+
ctx.slots.inject('settings.section', () => {
|
|
1793
|
+
let dispose;
|
|
1794
|
+
const sync = () => {
|
|
1795
|
+
const enabled = isEnabled(controller.getSnapshot().value);
|
|
1796
|
+
if (enabled && dispose === undefined) {
|
|
1797
|
+
dispose = registerSection();
|
|
1798
|
+
}
|
|
1799
|
+
else if (!enabled && dispose !== undefined) {
|
|
1800
|
+
dispose();
|
|
1801
|
+
dispose = undefined;
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
sync();
|
|
1805
|
+
const unsubscribe = controller.subscribe(sync);
|
|
1806
|
+
return () => {
|
|
1807
|
+
unsubscribe();
|
|
1808
|
+
dispose?.();
|
|
1809
|
+
};
|
|
1810
|
+
});
|
|
1435
1811
|
}
|
|
1436
1812
|
function apply(ctx) {
|
|
1437
1813
|
ctx.effect(installBaseStyles, 'dsh-ui-tweaks: base styles');
|
|
1438
1814
|
ctx.effect(timeline_tsx_1.installTimelineStyles, 'dsh-ui-tweaks: timeline styles');
|
|
1439
1815
|
ctx.effect(gitbar_tsx_1.installGitBarStyles, 'dsh-ui-tweaks: gitbar styles');
|
|
1816
|
+
ctx.effect(archive_tsx_1.installArchiveStyles, 'dsh-ui-tweaks: archive styles');
|
|
1817
|
+
ctx.effect(mcp_tsx_1.installMcpStyles, 'dsh-ui-tweaks: mcp styles');
|
|
1440
1818
|
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-ui-tweaks: locale');
|
|
1441
1819
|
const t = ctx.locale.bind(NS);
|
|
1442
1820
|
const controller = new SettingsClient();
|
|
@@ -1477,6 +1855,387 @@ function apply(ctx) {
|
|
|
1477
1855
|
locale: NS,
|
|
1478
1856
|
inject: () => ({ controller, sessionsService: ctx.sessions }),
|
|
1479
1857
|
}, gitbar_tsx_1.GitBar));
|
|
1858
|
+
// Archive manager: a Settings section ("归档") that lists archived sessions
|
|
1859
|
+
// and can restore or permanently delete them. Registered only while the
|
|
1860
|
+
// archiveManagerEnabled toggle in the UI Tweaks section is on, so the nav
|
|
1861
|
+
// row appears / disappears live with the switch (the settings shell
|
|
1862
|
+
// projects its nav from the section ledger).
|
|
1863
|
+
installConditionalSection(ctx, controller, (value) => value?.archiveManagerEnabled === true, () => ctx.slots.register({
|
|
1864
|
+
name: 'settings.section',
|
|
1865
|
+
id: 'archive',
|
|
1866
|
+
order: 50,
|
|
1867
|
+
label: () => t('archiveNav'),
|
|
1868
|
+
locale: NS,
|
|
1869
|
+
inject: () => ({ controller, t, sessionsService: ctx.sessions }),
|
|
1870
|
+
}, archive_tsx_1.ArchiveSection));
|
|
1871
|
+
// MCP manager: a Settings section ("MCP 管理") that lists the configured MCP
|
|
1872
|
+
// servers with their status and tools, and restarts them. Registered only
|
|
1873
|
+
// while the mcpManagerEnabled toggle in the UI Tweaks section is on, so the
|
|
1874
|
+
// nav row appears / disappears live with the switch.
|
|
1875
|
+
installConditionalSection(ctx, controller, (value) => value?.mcpManagerEnabled === true, () => ctx.slots.register({
|
|
1876
|
+
name: 'settings.section',
|
|
1877
|
+
id: 'mcp',
|
|
1878
|
+
order: 60,
|
|
1879
|
+
label: () => t('mcpNav'),
|
|
1880
|
+
locale: NS,
|
|
1881
|
+
inject: () => ({ controller, t }),
|
|
1882
|
+
}, mcp_tsx_1.McpSection));
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
__modules["./mcp.js"] = function(module, exports, require, __load_) {
|
|
1886
|
+
"use strict";
|
|
1887
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1888
|
+
exports.MCP_CSS = void 0;
|
|
1889
|
+
exports.installMcpStyles = installMcpStyles;
|
|
1890
|
+
exports.McpSection = McpSection;
|
|
1891
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1892
|
+
/**
|
|
1893
|
+
* dsh-ui-tweaks — MCP manager (browser half).
|
|
1894
|
+
*
|
|
1895
|
+
* Renders the "MCP 管理" settings section (usable when the mcpManagerEnabled
|
|
1896
|
+
* toggle in 界面调整 is on — otherwise it shows an invite card). The section
|
|
1897
|
+
* lists every configured MCP server with its status (active / failed / loading
|
|
1898
|
+
* / stopped / disabled), command / url, env names, registered tools, and
|
|
1899
|
+
* offers per-server actions: **重启** (runtime reload), **启用/停用**,
|
|
1900
|
+
* **编辑**, and **删除** (two-click confirm). An **添加服务器** button opens
|
|
1901
|
+
* the editor, which supports BOTH a structured **表单** and a raw **YAML**
|
|
1902
|
+
* mode (the config is validated on the server before the profile's
|
|
1903
|
+
* `cordis.patch.yml` is rewritten; DSH's patch watcher then hot-reloads the
|
|
1904
|
+
* loader so the server starts/stops live).
|
|
1905
|
+
* @module dsh-ui-tweaks/client/mcp
|
|
1906
|
+
*/
|
|
1907
|
+
const react_1 = require("react");
|
|
1908
|
+
const react_dom_1 = require("react-dom");
|
|
1909
|
+
/** Route matching the host half (src/mcp.ts). */
|
|
1910
|
+
const MCP_ROUTE = '/_dsh/ui-tweaks/mcp';
|
|
1911
|
+
async function mcpRequest(init) {
|
|
1912
|
+
const response = await fetch(MCP_ROUTE, { credentials: 'same-origin', ...init });
|
|
1913
|
+
let body;
|
|
1914
|
+
try {
|
|
1915
|
+
body = await response.json();
|
|
1916
|
+
}
|
|
1917
|
+
catch {
|
|
1918
|
+
body = undefined;
|
|
1919
|
+
}
|
|
1920
|
+
if (!response.ok || body === undefined || !body.ok) {
|
|
1921
|
+
const failure = body;
|
|
1922
|
+
const message = body === undefined
|
|
1923
|
+
? `MCP route unavailable (HTTP ${response.status}; restart DSH to load the plugin server code)`
|
|
1924
|
+
: failure?.error?.message ?? `UI Tweaks MCP request failed with HTTP ${response.status}`;
|
|
1925
|
+
const error = new Error(message);
|
|
1926
|
+
error.code = failure?.error?.code;
|
|
1927
|
+
throw error;
|
|
1928
|
+
}
|
|
1929
|
+
return body.value;
|
|
1930
|
+
}
|
|
1931
|
+
/** Split a textarea into one trimmed entry per non-empty line. */
|
|
1932
|
+
function linesToArray(text) {
|
|
1933
|
+
return text.split(/\r?\n/u).map(line => line.trim()).filter(line => line.length > 0);
|
|
1934
|
+
}
|
|
1935
|
+
/** Parse `KEY=value` (or `Key: value`) lines into a record; throws on malformed lines. */
|
|
1936
|
+
function linesToRecord(text, separator) {
|
|
1937
|
+
const out = {};
|
|
1938
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
1939
|
+
const trimmed = line.trim();
|
|
1940
|
+
if (trimmed.length === 0)
|
|
1941
|
+
continue;
|
|
1942
|
+
const at = trimmed.indexOf(separator);
|
|
1943
|
+
if (at <= 0)
|
|
1944
|
+
throw new Error(`格式不正确:${trimmed}`);
|
|
1945
|
+
const key = trimmed.slice(0, at).trim();
|
|
1946
|
+
const value = trimmed.slice(at + 1).trim();
|
|
1947
|
+
if (key.length === 0)
|
|
1948
|
+
throw new Error(`格式不正确:${trimmed}`);
|
|
1949
|
+
out[key] = value;
|
|
1950
|
+
}
|
|
1951
|
+
return out;
|
|
1952
|
+
}
|
|
1953
|
+
function recordToLines(record, separator) {
|
|
1954
|
+
return Object.entries(record).map(([key, value]) => `${key}${separator}${value}`).join('\n');
|
|
1955
|
+
}
|
|
1956
|
+
exports.MCP_CSS = `
|
|
1957
|
+
.dut-mcp{display:grid;gap:12px;max-width:680px;padding:6px 2px 36px}
|
|
1958
|
+
.dut-mcp-head{display:flex;align-items:center;gap:10px;padding:14px 16px 0}
|
|
1959
|
+
.dut-mcp-head h2{margin:0;font-size:15px;font-weight:600;letter-spacing:-.01em;color:var(--dsw-alias-label-primary)}
|
|
1960
|
+
.dut-mcp-count{font-size:11.5px;color:var(--dsw-alias-label-secondary);padding:2px 8px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1)}
|
|
1961
|
+
.dut-mcp-spacer{flex:1}
|
|
1962
|
+
.dut-mcp-btn{display:inline-flex;align-items:center;height:28px;padding:0 12px;border-radius:999px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}
|
|
1963
|
+
.dut-mcp-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1964
|
+
.dut-mcp-btn:disabled{opacity:.5;cursor:default}
|
|
1965
|
+
.dut-mcp-btn.dut-mcp-del:hover{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
|
|
1966
|
+
.dut-mcp-btn.dut-mcp-confirm{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 55%,transparent);color:var(--dsw-alias-state-error-primary);font-weight:600}
|
|
1967
|
+
.dut-mcp-body{display:grid;gap:2px;margin-top:4px}
|
|
1968
|
+
.dut-mcp-row{display:grid;gap:6px;padding:10px 16px;border-radius:12px}
|
|
1969
|
+
.dut-mcp-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1970
|
+
.dut-mcp-row-main{display:flex;align-items:center;gap:8px;min-width:0}
|
|
1971
|
+
.dut-mcp-name{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
|
|
1972
|
+
.dut-mcp-badge{flex:none;font-size:11px;line-height:1;padding:4px 9px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}
|
|
1973
|
+
.dut-mcp-badge.dut-mcp-ok{background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);border-color:transparent;color:var(--dsw-alias-state-success-primary)}
|
|
1974
|
+
.dut-mcp-badge.dut-mcp-err{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 12%,transparent);border-color:transparent;color:var(--dsw-alias-state-error-primary)}
|
|
1975
|
+
.dut-mcp-badge.dut-mcp-warn{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 14%,transparent);border-color:transparent;color:var(--dsw-alias-state-warn-label)}
|
|
1976
|
+
.dut-mcp-sub{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
|
|
1977
|
+
.dut-mcp-toggle{flex:none;font-size:11.5px;color:var(--dsw-alias-label-tertiary);background:transparent;border:none;padding:0;cursor:pointer;text-align:left}
|
|
1978
|
+
.dut-mcp-toggle:hover{color:var(--dsw-alias-label-primary)}
|
|
1979
|
+
.dut-mcp-tools{margin-left:2px;display:flex;flex-wrap:wrap;gap:4px}
|
|
1980
|
+
.dut-mcp-tool{font-size:11px;line-height:1;padding:3px 8px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary)}
|
|
1981
|
+
.dut-mcp-note{margin:2px 16px 0;padding:8px 12px;border-radius:10px;font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1)}
|
|
1982
|
+
.dut-mcp-empty{padding:32px 16px;text-align:center;font-size:12.5px;color:var(--dsw-alias-label-secondary)}
|
|
1983
|
+
.dut-mcp-alert{margin:8px 16px 0;padding:8px 12px;border-radius:10px;font-size:12px;line-height:1.5;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
1984
|
+
.dut-mcp-notice{margin:8px 16px 0;padding:8px 12px;border-radius:10px;font-size:12px;line-height:1.5;background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 12%,transparent);color:var(--dsw-alias-state-success-primary)}
|
|
1985
|
+
.dut-mcp-off{padding:20px 16px;display:grid;gap:12px}
|
|
1986
|
+
.dut-mcp-off p{margin:0;font-size:12.5px;line-height:1.6;color:var(--dsw-alias-label-secondary)}
|
|
1987
|
+
.dut-mcp-overlay{z-index:1100;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}
|
|
1988
|
+
.dut-mcp-mask{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);position:absolute;inset:0}
|
|
1989
|
+
.dut-mcp-panel{z-index:1;background:var(--dsw-alias-bg-layer-2);box-sizing:border-box;width:720px;max-width:calc(100vw - 48px);height:min(700px,100vh - 48px);box-shadow:var(--dsw-shadow-lv3);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;display:flex;flex-direction:column;overflow:hidden}
|
|
1990
|
+
.dut-mcp-panel-head{box-sizing:border-box;flex:none;display:flex;align-items:center;gap:10px;height:52px;padding:0 12px 0 20px}
|
|
1991
|
+
.dut-mcp-panel-head h2{margin:0;font-size:16px;font-weight:600;letter-spacing:-.01em;color:var(--dsw-alias-label-primary)}
|
|
1992
|
+
.dut-mcp-panel-close{cursor:pointer;width:28px;height:28px;color:var(--dsw-alias-label-primary);background:transparent;border:none;border-radius:28px;display:inline-flex;justify-content:center;align-items:center;padding:0;font-size:13px}
|
|
1993
|
+
.dut-mcp-panel-close:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1994
|
+
.dut-mcp-tabs{flex:none;display:inline-flex;padding:3px;gap:3px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-2);margin:0 20px 10px;align-self:flex-start}
|
|
1995
|
+
.dut-mcp-tabs button{border:none;border-radius:7px;padding:5px 14px;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;cursor:pointer}
|
|
1996
|
+
.dut-mcp-tabs button.dut-mcp-tab-on{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 12%,transparent);color:var(--dsw-alias-state-business-primary);font-weight:600}
|
|
1997
|
+
.dut-mcp-form{flex:1;min-height:0;overflow-y:auto;padding:0 20px 20px;display:grid;gap:12px;align-content:start}
|
|
1998
|
+
.dut-mcp-field{display:grid;gap:6px}
|
|
1999
|
+
.dut-mcp-field>span{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
2000
|
+
.dut-mcp-field small{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
2001
|
+
.dut-mcp-input{box-sizing:border-box;width:100%;height:34px;padding:0 10px;border-radius:9px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
2002
|
+
.dut-mcp-input:focus{outline:none;border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,transparent)}
|
|
2003
|
+
.dut-mcp-input:disabled{opacity:.55;cursor:not-allowed}
|
|
2004
|
+
textarea.dut-mcp-input{height:auto;min-height:72px;padding:8px 10px;line-height:1.5;resize:vertical;font-family:var(--dsw-font-markdown-code-block-font-family, Consolas, monospace);font-size:12px}
|
|
2005
|
+
textarea.dut-mcp-input-lg{min-height:120px}
|
|
2006
|
+
textarea.dut-mcp-yaml{min-height:320px}
|
|
2007
|
+
.dut-mcp-row2{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:start}
|
|
2008
|
+
.dut-mcp-field .dut-seg{width:fit-content}
|
|
2009
|
+
.dut-mcp-row2 .dut-seg{height:34px;box-sizing:border-box;align-items:center}
|
|
2010
|
+
.dut-mcp-row2 .dut-seg button{flex:none;white-space:nowrap;padding:0 16px;height:26px}
|
|
2011
|
+
.dut-mcp-foot{flex:none;display:flex;align-items:center;gap:8px;justify-content:flex-end;padding:12px 20px 16px;border-top:1px solid var(--dsw-alias-border-l1)}
|
|
2012
|
+
.dut-mcp-error{margin:0 20px 10px;padding:8px 12px;border-radius:10px;font-size:12px;line-height:1.5;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
2013
|
+
`;
|
|
2014
|
+
/** Install the MCP stylesheet once (idempotent); returns the disposer. */
|
|
2015
|
+
function installMcpStyles() {
|
|
2016
|
+
const id = 'dsh-ui-tweaks-mcp';
|
|
2017
|
+
const existing = document.querySelector(`style[data-plugin-css="${id}"]`);
|
|
2018
|
+
if (existing !== null)
|
|
2019
|
+
return () => { };
|
|
2020
|
+
const style = document.createElement('style');
|
|
2021
|
+
style.dataset.plugin = 'dsh-ui-tweaks';
|
|
2022
|
+
style.dataset.pluginCss = id;
|
|
2023
|
+
style.textContent = exports.MCP_CSS;
|
|
2024
|
+
document.head.appendChild(style);
|
|
2025
|
+
return () => { style.remove(); };
|
|
2026
|
+
}
|
|
2027
|
+
function statusLabel(status, t) {
|
|
2028
|
+
switch (status) {
|
|
2029
|
+
case 'active': return t('mcpStatusActive');
|
|
2030
|
+
case 'failed': return t('mcpStatusFailed');
|
|
2031
|
+
case 'loading': return t('mcpStatusLoading');
|
|
2032
|
+
case 'stopped': return t('mcpStatusStopped');
|
|
2033
|
+
case 'disabled': return t('mcpStatusDisabled');
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function statusClass(status) {
|
|
2037
|
+
switch (status) {
|
|
2038
|
+
case 'active': return ' dut-mcp-ok';
|
|
2039
|
+
case 'failed': return ' dut-mcp-err';
|
|
2040
|
+
case 'loading': return ' dut-mcp-warn';
|
|
2041
|
+
default: return '';
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
function McpSection({ controller, t }) {
|
|
2045
|
+
const settingsState = (0, react_1.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
2046
|
+
const enabled = settingsState.value?.mcpManagerEnabled === true;
|
|
2047
|
+
const [servers, setServers] = (0, react_1.useState)(null);
|
|
2048
|
+
const [busy, setBusy] = (0, react_1.useState)(null);
|
|
2049
|
+
const [confirmId, setConfirmId] = (0, react_1.useState)(null);
|
|
2050
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
2051
|
+
const [notice, setNotice] = (0, react_1.useState)(null);
|
|
2052
|
+
const [expanded, setExpanded] = (0, react_1.useState)(null);
|
|
2053
|
+
const [editing, setEditing] = (0, react_1.useState)(null);
|
|
2054
|
+
const generation = (0, react_1.useMemo)(() => ({ current: 0 }), []);
|
|
2055
|
+
const load = () => {
|
|
2056
|
+
const gen = ++generation.current;
|
|
2057
|
+
setError(null);
|
|
2058
|
+
void mcpRequest().then((snapshot) => {
|
|
2059
|
+
if (gen !== generation.current)
|
|
2060
|
+
return;
|
|
2061
|
+
setServers(snapshot.servers);
|
|
2062
|
+
}).catch((reason) => {
|
|
2063
|
+
if (gen !== generation.current)
|
|
2064
|
+
return;
|
|
2065
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2066
|
+
setServers([]);
|
|
2067
|
+
});
|
|
2068
|
+
};
|
|
2069
|
+
(0, react_1.useEffect)(() => {
|
|
2070
|
+
if (!enabled)
|
|
2071
|
+
return;
|
|
2072
|
+
load();
|
|
2073
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2074
|
+
}, [enabled]);
|
|
2075
|
+
const run = (key, body, after) => {
|
|
2076
|
+
setBusy(key);
|
|
2077
|
+
setError(null);
|
|
2078
|
+
setNotice(null);
|
|
2079
|
+
void mcpRequest({
|
|
2080
|
+
method: 'POST',
|
|
2081
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2082
|
+
body: JSON.stringify(body),
|
|
2083
|
+
}).then(() => { after?.(); }).catch((reason) => {
|
|
2084
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2085
|
+
}).finally(() => { setBusy(current => (current === key ? null : current)); });
|
|
2086
|
+
};
|
|
2087
|
+
/** Re-fetch the list every `interval` ms until `stop` returns true (or attempts run out).
|
|
2088
|
+
* The DSH patch watcher applies changes asynchronously and an MCP server takes
|
|
2089
|
+
* time to spawn/connect, so a single delayed reload can miss the transition. */
|
|
2090
|
+
const pollAfter = (stop, attempts = 12, interval = 1000) => {
|
|
2091
|
+
let left = attempts;
|
|
2092
|
+
const tick = () => {
|
|
2093
|
+
const gen = ++generation.current;
|
|
2094
|
+
void mcpRequest().then((snapshot) => {
|
|
2095
|
+
if (gen !== generation.current)
|
|
2096
|
+
return;
|
|
2097
|
+
setServers(snapshot.servers);
|
|
2098
|
+
left -= 1;
|
|
2099
|
+
if (left <= 0 || stop(snapshot.servers))
|
|
2100
|
+
return;
|
|
2101
|
+
window.setTimeout(tick, interval);
|
|
2102
|
+
}).catch(() => {
|
|
2103
|
+
left -= 1;
|
|
2104
|
+
if (left > 0)
|
|
2105
|
+
window.setTimeout(tick, interval);
|
|
2106
|
+
});
|
|
2107
|
+
};
|
|
2108
|
+
tick();
|
|
2109
|
+
};
|
|
2110
|
+
const remove = (id) => {
|
|
2111
|
+
if (confirmId !== id) {
|
|
2112
|
+
setConfirmId(id);
|
|
2113
|
+
window.setTimeout(() => { setConfirmId(current => (current === id ? null : current)); }, 3000);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
setConfirmId(null);
|
|
2117
|
+
run(`remove:${id}`, { action: 'remove', id }, () => {
|
|
2118
|
+
setNotice(t('mcpRemoved'));
|
|
2119
|
+
pollAfter(servers => !servers.some(server => server.id === id), 10);
|
|
2120
|
+
});
|
|
2121
|
+
};
|
|
2122
|
+
const setEnabled = (id, enabled) => {
|
|
2123
|
+
run(`set-enabled:${id}`, { action: 'set-enabled', id, enabled }, () => {
|
|
2124
|
+
// Keep refreshing until the target reaches a TERMINAL status. The
|
|
2125
|
+
// `disabled` flag flips within ~1s, but the MCP spawn + connect takes
|
|
2126
|
+
// longer — stopping at the flag would freeze the row mid-transition.
|
|
2127
|
+
pollAfter(servers => {
|
|
2128
|
+
const target = servers.find(server => server.id === id);
|
|
2129
|
+
if (target === undefined)
|
|
2130
|
+
return true;
|
|
2131
|
+
return enabled
|
|
2132
|
+
? target.status === 'active' || target.status === 'failed'
|
|
2133
|
+
: target.status === 'disabled';
|
|
2134
|
+
}, 45, 1000);
|
|
2135
|
+
});
|
|
2136
|
+
};
|
|
2137
|
+
if (!enabled) {
|
|
2138
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "dut-mcp", children: (0, jsx_runtime_1.jsxs)("div", { className: "dut-panel dut-mcp-off", children: [(0, jsx_runtime_1.jsx)("p", { children: t('mcpDisabledHint') }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-btn dut-btn-active", onClick: () => {
|
|
2139
|
+
void controller.set('mcpManagerEnabled', true).catch(() => { setError(t('unavailable')); });
|
|
2140
|
+
}, children: t('mcpEnable') }) })] }) }));
|
|
2141
|
+
}
|
|
2142
|
+
const list = servers ?? [];
|
|
2143
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-panel", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-head", children: [(0, jsx_runtime_1.jsx)("h2", { children: t('mcpTitle') }), (0, jsx_runtime_1.jsxs)("span", { className: "dut-mcp-count", children: [list.length, " \u00B7 ", list.filter(s => s.status === 'active').length, " ", t('mcpStatusActive')] }), (0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-spacer" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-mcp-btn", onClick: () => { setEditing('new'); }, children: t('mcpAdd') })] }), (0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-note", children: t('mcpServerDetail') }), error !== null ? (0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-alert", children: error }) : null, notice !== null ? (0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-notice", children: notice }) : null, (0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-body", children: servers === null ? ((0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-empty", children: t('loading') })) : list.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-empty", children: t('mcpEmpty') })) : list.map(server => {
|
|
2144
|
+
const command = server.url !== undefined ? server.url : [server.command, ...server.args].filter(Boolean).join(' ');
|
|
2145
|
+
const open = expanded === server.id;
|
|
2146
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-row-main", children: [(0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-name", children: server.serverName }), (0, jsx_runtime_1.jsx)("span", { className: 'dut-mcp-badge' + statusClass(server.status), children: statusLabel(server.status, t) }), (0, jsx_runtime_1.jsxs)("span", { className: "dut-mcp-sub", children: ["\u00B7 ", server.toolCount, " ", t('mcpTools')] }), (0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-spacer" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-mcp-btn", disabled: busy !== null, onClick: () => { setEnabled(server.id, server.disabled); }, children: server.disabled ? t('mcpEnabledAction') : t('mcpDisabledAction') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-mcp-btn", disabled: busy !== null, onClick: () => { setEditing(server); }, children: t('mcpEdit') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: 'dut-mcp-btn dut-mcp-del' + (confirmId === server.id ? ' dut-mcp-confirm' : ''), disabled: busy !== null, onClick: () => { remove(server.id); }, children: confirmId === server.id ? t('mcpDelete') + '?' : t('mcpDelete') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-sub", title: command, children: [server.id, " \u00B7 ", server.transport, " \u00B7 ", command] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "dut-mcp-toggle", onClick: () => { setExpanded(open ? null : server.id); }, children: [open ? '▾ ' : '▸ ', server.toolCount, " ", t('mcpTools'), server.env && Object.keys(server.env).length > 0 ? ` · ${t('mcpEnv')}: ${Object.keys(server.env).join(', ')}` : ''] }), open ? ((0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-tools", children: server.tools.length === 0 ? (0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-sub", children: "\u2014" }) : server.tools.map(tool => (0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-tool", children: tool }, tool)) })) : null] }, server.id));
|
|
2147
|
+
}) })] }), editing !== null ? ((0, jsx_runtime_1.jsx)(McpEditor, { initial: editing === 'new' ? null : editing, t: t, onClose: () => { setEditing(null); }, onSaved: () => { setEditing(null); setNotice(t('mcpSaved')); pollAfter(() => false, 10); } }, editing === 'new' ? 'new' : editing.id)) : null] }));
|
|
2148
|
+
}
|
|
2149
|
+
function McpEditor({ initial, t, onClose, onSaved }) {
|
|
2150
|
+
const isEdit = initial !== null;
|
|
2151
|
+
const [mode, setMode] = (0, react_1.useState)('form');
|
|
2152
|
+
const [id, setId] = (0, react_1.useState)(initial?.id ?? '');
|
|
2153
|
+
const [serverName, setServerName] = (0, react_1.useState)(initial?.serverName ?? '');
|
|
2154
|
+
const [transport, setTransport] = (0, react_1.useState)(initial?.transport === 'streamable-http' ? 'streamable-http' : 'stdio');
|
|
2155
|
+
const [timeoutText, setTimeoutText] = (0, react_1.useState)(initial?.toolCallTimeoutMs !== undefined ? String(initial.toolCallTimeoutMs) : '');
|
|
2156
|
+
const [command, setCommand] = (0, react_1.useState)(initial?.command ?? '');
|
|
2157
|
+
const [argsText, setArgsText] = (0, react_1.useState)(initial ? initial.args.join('\n') : '');
|
|
2158
|
+
const [envText, setEnvText] = (0, react_1.useState)(initial ? recordToLines(initial.env, '=') : '');
|
|
2159
|
+
const [url, setUrl] = (0, react_1.useState)(initial?.url ?? '');
|
|
2160
|
+
const [headersText, setHeadersText] = (0, react_1.useState)(initial ? recordToLines(initial.headers, ':') : '');
|
|
2161
|
+
const [disabled, setDisabled] = (0, react_1.useState)(initial?.disabled ?? false);
|
|
2162
|
+
const [yamlText, setYamlText] = (0, react_1.useState)(initial?.yaml ?? '');
|
|
2163
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
2164
|
+
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
2165
|
+
(0, react_1.useEffect)(() => {
|
|
2166
|
+
const onKeyDown = (event) => {
|
|
2167
|
+
if (event.key === 'Escape')
|
|
2168
|
+
onClose();
|
|
2169
|
+
};
|
|
2170
|
+
document.addEventListener('keydown', onKeyDown);
|
|
2171
|
+
return () => { document.removeEventListener('keydown', onKeyDown); };
|
|
2172
|
+
}, [onClose]);
|
|
2173
|
+
const validateForm = () => {
|
|
2174
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/u.test(id))
|
|
2175
|
+
return t('mcpInvalidId');
|
|
2176
|
+
if (!/^[A-Za-z0-9_-]{1,32}$/u.test(serverName))
|
|
2177
|
+
return t('mcpInvalidName');
|
|
2178
|
+
if (timeoutText !== '' && (!/^\d+$/u.test(timeoutText) || Number(timeoutText) <= 0))
|
|
2179
|
+
return t('mcpInvalidTimeout');
|
|
2180
|
+
if (transport === 'streamable-http') {
|
|
2181
|
+
if (!/^https?:\/\//u.test(url))
|
|
2182
|
+
return t('mcpInvalidUrl');
|
|
2183
|
+
}
|
|
2184
|
+
else if (command.trim().length === 0) {
|
|
2185
|
+
return t('mcpInvalidCommand');
|
|
2186
|
+
}
|
|
2187
|
+
return null;
|
|
2188
|
+
};
|
|
2189
|
+
const save = () => {
|
|
2190
|
+
setError(null);
|
|
2191
|
+
setBusy(true);
|
|
2192
|
+
let payload;
|
|
2193
|
+
if (mode === 'yaml') {
|
|
2194
|
+
payload = { action: 'save', server: { id, disabled }, yaml: yamlText };
|
|
2195
|
+
}
|
|
2196
|
+
else {
|
|
2197
|
+
const invalid = validateForm();
|
|
2198
|
+
if (invalid !== null) {
|
|
2199
|
+
setError(invalid);
|
|
2200
|
+
setBusy(false);
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
let env = {};
|
|
2204
|
+
let headers = {};
|
|
2205
|
+
try {
|
|
2206
|
+
env = linesToRecord(envText, '=');
|
|
2207
|
+
headers = linesToRecord(headersText, ':');
|
|
2208
|
+
}
|
|
2209
|
+
catch (parseError) {
|
|
2210
|
+
setError(parseError instanceof Error ? parseError.message : String(parseError));
|
|
2211
|
+
setBusy(false);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
payload = {
|
|
2215
|
+
action: 'save',
|
|
2216
|
+
server: {
|
|
2217
|
+
id,
|
|
2218
|
+
serverName,
|
|
2219
|
+
transport,
|
|
2220
|
+
command,
|
|
2221
|
+
args: linesToArray(argsText),
|
|
2222
|
+
env,
|
|
2223
|
+
url,
|
|
2224
|
+
headers,
|
|
2225
|
+
...(timeoutText !== '' ? { toolCallTimeoutMs: Number(timeoutText) } : {}),
|
|
2226
|
+
disabled,
|
|
2227
|
+
},
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
void mcpRequest({
|
|
2231
|
+
method: 'POST',
|
|
2232
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2233
|
+
body: JSON.stringify(payload),
|
|
2234
|
+
}).then(() => { onSaved(); }).catch((reason) => {
|
|
2235
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
2236
|
+
}).finally(() => { setBusy(false); });
|
|
2237
|
+
};
|
|
2238
|
+
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-overlay", role: "presentation", children: [(0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-mask", "aria-hidden": "true", onClick: onClose }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-panel", role: "dialog", "aria-modal": "true", "aria-label": isEdit ? t('mcpEditTitle') : t('mcpAddTitle'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-panel-head", children: [(0, jsx_runtime_1.jsx)("h2", { children: isEdit ? t('mcpEditTitle') : t('mcpAddTitle') }), (0, jsx_runtime_1.jsx)("span", { className: "dut-mcp-spacer" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-mcp-panel-close", onClick: onClose, "aria-label": "\u2715", children: "\u2715" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-tabs", role: "tablist", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": mode === 'form', className: mode === 'form' ? 'dut-mcp-tab-on' : '', onClick: () => { setMode('form'); }, children: t('mcpFormTab') }), (0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": mode === 'yaml', className: mode === 'yaml' ? 'dut-mcp-tab-on' : '', onClick: () => { setMode('yaml'); }, children: t('mcpYamlTab') })] }), error !== null ? (0, jsx_runtime_1.jsx)("div", { className: "dut-mcp-error", children: error }) : null, mode === 'yaml' ? ((0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-form", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-row2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldId') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", value: id, disabled: isEdit, onChange: event => { setId(event.target.value); } }), (0, jsx_runtime_1.jsx)("small", { children: t('mcpFieldIdHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldEnabled') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: !disabled ? 'dut-seg-active' : '', onClick: () => { setDisabled(false); }, children: t('mcpEnabledAction') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: disabled ? 'dut-seg-active' : '', onClick: () => { setDisabled(true); }, children: t('mcpDisabledAction') })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpYamlTab') }), (0, jsx_runtime_1.jsx)("textarea", { className: "dut-mcp-input dut-mcp-yaml", value: yamlText, placeholder: t('mcpYamlPlaceholder'), spellCheck: false, onChange: event => { setYamlText(event.target.value); } }), (0, jsx_runtime_1.jsx)("small", { children: t('mcpYamlHint') })] })] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-form", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-row2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldId') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", value: id, disabled: isEdit, onChange: event => { setId(event.target.value); } }), (0, jsx_runtime_1.jsx)("small", { children: t('mcpFieldIdHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldEnabled') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: !disabled ? 'dut-seg-active' : '', onClick: () => { setDisabled(false); }, children: t('mcpEnabledAction') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: disabled ? 'dut-seg-active' : '', onClick: () => { setDisabled(true); }, children: t('mcpDisabledAction') })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-row2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldName') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", value: serverName, onChange: event => { setServerName(event.target.value); } }), (0, jsx_runtime_1.jsx)("small", { children: t('mcpFieldNameHint') })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldType') }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-seg", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: transport === 'stdio' ? 'dut-seg-active' : '', onClick: () => { setTransport('stdio'); }, children: "stdio" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: transport === 'streamable-http' ? 'dut-seg-active' : '', onClick: () => { setTransport('streamable-http'); }, children: "HTTP" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldTimeout') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", inputMode: "numeric", value: timeoutText, placeholder: "60000", onChange: event => { setTimeoutText(event.target.value); } })] }), transport === 'stdio' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldCommand') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", value: command, placeholder: t('mcpFieldCommandPlaceholder'), onChange: event => { setCommand(event.target.value); } })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldArgs') }), (0, jsx_runtime_1.jsx)("textarea", { className: "dut-mcp-input", value: argsText, placeholder: '-y\n@some/mcp-server', spellCheck: false, onChange: event => { setArgsText(event.target.value); } })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldEnv') }), (0, jsx_runtime_1.jsx)("textarea", { className: "dut-mcp-input dut-mcp-input-lg", value: envText, placeholder: 'KEY=value', spellCheck: false, onChange: event => { setEnvText(event.target.value); } })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldUrl') }), (0, jsx_runtime_1.jsx)("input", { className: "dut-mcp-input", value: url, placeholder: "https://mcp.example.com/sse", onChange: event => { setUrl(event.target.value); } })] }), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('mcpFieldHeaders') }), (0, jsx_runtime_1.jsx)("textarea", { className: "dut-mcp-input dut-mcp-input-lg", value: headersText, placeholder: 'Authorization: Bearer …', spellCheck: false, onChange: event => { setHeadersText(event.target.value); } })] })] }))] })), (0, jsx_runtime_1.jsxs)("div", { className: "dut-mcp-foot", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-mcp-btn", disabled: busy, onClick: onClose, children: t('mcpCancel') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "dut-btn dut-btn-active", disabled: busy, onClick: save, children: t('mcpSave') })] })] })] }), document.body);
|
|
1480
2239
|
}
|
|
1481
2240
|
};
|
|
1482
2241
|
__modules["./timeline.js"] = function(module, exports, require, __load_) {
|