@rezti/dsh-rez-suite 0.1.4 → 0.1.6

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/lib/client.js CHANGED
@@ -4,82 +4,10 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ (function(){var id="dsh-rez-suite-css";if(typeof document==="undefined"||document.getElementById(id))return;var s=document.createElement("style");s.id=id;s.textContent=".WRGRRW_shell {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n display: flex;\n}\n\n.WRGRRW_card {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-3);\n border-radius: 12px;\n list-style: none;\n transition: border-color .16s, background .16s;\n}\n\n.WRGRRW_card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.WRGRRW_cardOpen {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.WRGRRW_header {\n appearance: none;\n width: 100%;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 12px;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n display: flex;\n}\n\n.WRGRRW_header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.WRGRRW_headText {\n flex-direction: column;\n flex: 1;\n gap: 4px;\n min-width: 0;\n display: flex;\n}\n\n.WRGRRW_name {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.WRGRRW_description {\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n\n.WRGRRW_chevron {\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n transition: transform .16s;\n}\n\n.WRGRRW_chevronOpen {\n transform: rotate(180deg);\n}\n\n.WRGRRW_body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px 12px;\n padding-top: 8px;\n}\n\n.WRGRRW_tabBar {\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n flex: none;\n gap: 2px;\n display: flex;\n}\n\n.WRGRRW_tab {\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n background: none;\n border: none;\n border-bottom: 2px solid #0000;\n border-radius: 6px 6px 0 0;\n padding: 7px 14px;\n font-size: 13px;\n}\n\n.WRGRRW_tab:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.WRGRRW_tab[data-active] {\n color: var(--dsw-alias-label-primary);\n border-bottom-color: var(--dsw-alias-state-business-primary);\n font-weight: 600;\n}\n\n.WRGRRW_tabBody {\n flex-direction: column;\n gap: 12px;\n min-width: 0;\n padding: 12px 0 4px;\n display: flex;\n}\n\n.WRGRRW_section {\n border: 1px solid var(--dsw-alias-border-l1);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base));\n border-radius: 10px;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n display: flex;\n}\n\n.WRGRRW_sectionTitle {\n color: var(--dsw-alias-label-primary);\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n\n.WRGRRW_field {\n flex-direction: column;\n align-items: stretch;\n gap: 4px;\n display: flex;\n}\n\n.WRGRRW_label {\n color: var(--dsw-alias-label-secondary);\n font-size: 12.5px;\n}\n\n.WRGRRW_input, .WRGRRW_select {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n outline: none;\n padding: 6px 8px;\n font-size: 13px;\n}\n\n.WRGRRW_checkbox {\n justify-self: start;\n width: 15px;\n height: 15px;\n}\n\n.WRGRRW_toolbar {\n flex-wrap: wrap;\n flex: none;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.WRGRRW_button {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-3));\n color: var(--dsw-alias-label-primary);\n cursor: pointer;\n border-radius: 8px;\n padding: 7px 12px;\n font-size: 13px;\n}\n\n.WRGRRW_button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.WRGRRW_button:disabled {\n opacity: .4;\n cursor: default;\n}\n\n.WRGRRW_primaryButton {\n background: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.WRGRRW_dangerButton {\n background: var(--dsw-alias-state-danger, #c62828);\n border-color: var(--dsw-alias-state-danger, #c62828);\n color: #fff;\n}\n\n.WRGRRW_qr {\n background: #fff;\n border-radius: 8px;\n width: 200px;\n height: 200px;\n margin: 8px 0;\n display: block;\n}\n\n.WRGRRW_message {\n color: var(--dsw-alias-label-secondary);\n margin: 0;\n font-size: 12.5px;\n line-height: 1.5;\n}\n\n.WRGRRW_error {\n color: var(--dsw-alias-state-danger, #c62828);\n}\n\n.WRGRRW_tableWrap {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n overflow: auto;\n}\n\n.WRGRRW_table {\n border-collapse: collapse;\n width: 100%;\n font-size: 12.5px;\n}\n\n.WRGRRW_table th {\n z-index: 1;\n text-align: left;\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n padding: 8px 10px;\n position: sticky;\n top: 0;\n}\n\n.WRGRRW_table td {\n border-top: 1px solid var(--dsw-alias-border-l1);\n vertical-align: top;\n padding: 8px 10px;\n}\n\n.WRGRRW_badge {\n border-radius: 999px;\n padding: 2px 8px;\n font-size: 11px;\n display: inline-block;\n}\n\n.WRGRRW_badgeOk {\n color: #229a55;\n background: #229a5524;\n}\n\n.WRGRRW_badgeFail {\n color: #c62828;\n background: #c6282824;\n}\n\n.WRGRRW_cards {\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 8px;\n display: grid;\n}\n\n.WRGRRW_stat {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n padding: 10px;\n}\n\n.WRGRRW_cardLabel {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n}\n\n.WRGRRW_cardValue {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n}\n";document.head.appendChild(s);})();
7
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
- let react_dom_client = require("react-dom/client");
9
9
  let react = require("react");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
- //#region src/protocol.ts
12
- const REZ_API = {
13
- config: "/api/dsh-rez-suite/config",
14
- test: "/api/dsh-rez-suite/test",
15
- status: "/api/dsh-rez-suite/status",
16
- audit: "/api/dsh-rez-suite/audit",
17
- auditReset: "/api/dsh-rez-suite/audit/reset",
18
- weixin: "/api/dsh-rez-suite/weixin",
19
- weixinVerify: "/api/dsh-rez-suite/weixin/verify"
20
- };
21
- //#endregion
22
- //#region src/client/api.ts
23
- /**
24
- * Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
25
- */
26
- var RezApiError = class extends Error {
27
- constructor(message) {
28
- super(message);
29
- this.name = "RezApiError";
30
- }
31
- };
32
- async function readJson(response) {
33
- let body;
34
- try {
35
- body = await response.json();
36
- } catch {
37
- throw new RezApiError("HTTP " + response.status + ": invalid JSON response");
38
- }
39
- if (!response.ok) throw new RezApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
40
- return body;
41
- }
42
- var RezApi = class {
43
- async getConfig() {
44
- return (await readJson(await fetch(REZ_API.config))).config;
45
- }
46
- async saveConfig(patch) {
47
- return (await readJson(await fetch(REZ_API.config, {
48
- method: "PUT",
49
- headers: { "content-type": "application/json" },
50
- body: JSON.stringify(patch)
51
- }))).config;
52
- }
53
- async test() {
54
- return (await readJson(await fetch(REZ_API.test, { method: "POST" }))).results;
55
- }
56
- async status() {
57
- return readJson(await fetch(REZ_API.status));
58
- }
59
- async audit() {
60
- return (await readJson(await fetch(REZ_API.audit))).audit;
61
- }
62
- async resetAudit() {
63
- await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
64
- }
65
- async weixinStatus() {
66
- return readJson(await fetch(REZ_API.weixin));
67
- }
68
- async weixinLogin() {
69
- return readJson(await fetch(REZ_API.weixin, { method: "POST" }));
70
- }
71
- async weixinVerify(code) {
72
- return readJson(await fetch(REZ_API.weixinVerify, {
73
- method: "POST",
74
- headers: { "content-type": "application/json" },
75
- body: JSON.stringify({ code })
76
- }));
77
- }
78
- async weixinLogout() {
79
- return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
80
- }
81
- };
82
- //#endregion
83
11
  //#region src/client/locales.ts
84
12
  /**
85
13
  * dsh-rez-suite surface copy: zh is the key source, en mirrors every key.
@@ -87,6 +15,8 @@ var RezApi = class {
87
15
  const zh = {
88
16
  "entry.label": "Rez",
89
17
  "entry.tooltip": "Rez Suite 配置与审计面板",
18
+ "settings.title": "ReZ-TI",
19
+ "settings.description": "公司 MCP(Odoo / Nextcloud / 企微 / HA)与个人微信扫码通道。",
90
20
  "panel.title": "Rez Suite",
91
21
  "tab.config": "配置",
92
22
  "tab.weixin": "微信",
@@ -106,16 +36,23 @@ const zh = {
106
36
  "config.env": "环境变量 (JSON)",
107
37
  "config.headers": "Headers (JSON)",
108
38
  "config.jsonInvalid": "JSON 格式错误:{error}",
39
+ "config.mcpHint": "四个公司 MCP 在这里配。密钥写入 ~/.dsh/.credentials.yaml(REZ_*)。新填的密钥会立刻拉起连接;改 URL 后如果已经连上,需要重启 dsh web。",
40
+ "config.secretConfigured": "已配置(留空则保持)",
41
+ "config.secretMissing": "未配置",
109
42
  "config.odoo": "Odoo",
110
- "config.odoo.url": "Odoo URL",
111
- "config.odoo.apiKey": "API Key",
43
+ "config.odoo.url": "Odoo MCP URL",
44
+ "config.odoo.apiKey": "API Token",
112
45
  "config.nextcloud": "Nextcloud",
113
- "config.nextcloud.url": "Nextcloud URL",
46
+ "config.nextcloud.url": "Nextcloud 主机",
114
47
  "config.nextcloud.username": "用户名",
115
48
  "config.nextcloud.appPassword": "App Password",
116
49
  "config.wecom": "企业微信",
117
50
  "config.wecom.botId": "Bot ID",
118
51
  "config.wecom.secret": "Secret",
52
+ "config.wecom.webhook": "群机器人 Webhook",
53
+ "config.ha": "Home Assistant",
54
+ "config.ha.url": "HA MCP URL",
55
+ "config.ha.token": "长期访问令牌",
119
56
  "config.fs": "本地文件 (fs-mcp-server)",
120
57
  "config.fs.root": "根目录",
121
58
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -174,6 +111,8 @@ const zh = {
174
111
  const en = {
175
112
  "entry.label": "Rez",
176
113
  "entry.tooltip": "Rez Suite configuration and audit panel",
114
+ "settings.title": "ReZ-TI",
115
+ "settings.description": "Company MCP (Odoo / Nextcloud / WeCom / HA) and personal WeChat scan-and-chat.",
177
116
  "panel.title": "Rez Suite",
178
117
  "tab.config": "Config",
179
118
  "tab.weixin": "WeChat",
@@ -193,16 +132,23 @@ const en = {
193
132
  "config.env": "Env vars (JSON)",
194
133
  "config.headers": "Headers (JSON)",
195
134
  "config.jsonInvalid": "Invalid JSON: {error}",
135
+ "config.mcpHint": "Company MCP lives here. Secrets go to ~/.dsh/.credentials.yaml (REZ_*). A newly saved secret starts the connection immediately; URL changes after a live connection need a dsh web restart.",
136
+ "config.secretConfigured": "Configured (leave blank to keep)",
137
+ "config.secretMissing": "Not configured",
196
138
  "config.odoo": "Odoo",
197
- "config.odoo.url": "Odoo URL",
198
- "config.odoo.apiKey": "API Key",
139
+ "config.odoo.url": "Odoo MCP URL",
140
+ "config.odoo.apiKey": "API token",
199
141
  "config.nextcloud": "Nextcloud",
200
- "config.nextcloud.url": "Nextcloud URL",
142
+ "config.nextcloud.url": "Nextcloud host",
201
143
  "config.nextcloud.username": "Username",
202
- "config.nextcloud.appPassword": "App Password",
144
+ "config.nextcloud.appPassword": "App password",
203
145
  "config.wecom": "WeCom",
204
146
  "config.wecom.botId": "Bot ID",
205
147
  "config.wecom.secret": "Secret",
148
+ "config.wecom.webhook": "Group bot webhook",
149
+ "config.ha": "Home Assistant",
150
+ "config.ha.url": "HA MCP URL",
151
+ "config.ha.token": "Long-lived access token",
206
152
  "config.fs": "Local files (fs-mcp-server)",
207
153
  "config.fs.root": "Root directory",
208
154
  "config.sqlite": "SQLite (sqlite-mcp-server)",
@@ -265,6 +211,78 @@ function t(dict, key, values) {
265
211
  return text;
266
212
  }
267
213
  //#endregion
214
+ //#region src/protocol.ts
215
+ const REZ_API = {
216
+ config: "/api/dsh-rez-suite/config",
217
+ test: "/api/dsh-rez-suite/test",
218
+ status: "/api/dsh-rez-suite/status",
219
+ audit: "/api/dsh-rez-suite/audit",
220
+ auditReset: "/api/dsh-rez-suite/audit/reset",
221
+ weixin: "/api/dsh-rez-suite/weixin",
222
+ weixinVerify: "/api/dsh-rez-suite/weixin/verify"
223
+ };
224
+ //#endregion
225
+ //#region src/client/api.ts
226
+ /**
227
+ * Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
228
+ */
229
+ var RezApiError = class extends Error {
230
+ constructor(message) {
231
+ super(message);
232
+ this.name = "RezApiError";
233
+ }
234
+ };
235
+ async function readJson(response) {
236
+ let body;
237
+ try {
238
+ body = await response.json();
239
+ } catch {
240
+ throw new RezApiError("HTTP " + response.status + ": invalid JSON response");
241
+ }
242
+ if (!response.ok) throw new RezApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : "HTTP " + response.status);
243
+ return body;
244
+ }
245
+ var RezApi = class {
246
+ async getConfig() {
247
+ return (await readJson(await fetch(REZ_API.config))).config;
248
+ }
249
+ async saveConfig(patch) {
250
+ return (await readJson(await fetch(REZ_API.config, {
251
+ method: "PUT",
252
+ headers: { "content-type": "application/json" },
253
+ body: JSON.stringify(patch)
254
+ }))).config;
255
+ }
256
+ async test() {
257
+ return (await readJson(await fetch(REZ_API.test, { method: "POST" }))).results;
258
+ }
259
+ async status() {
260
+ return readJson(await fetch(REZ_API.status));
261
+ }
262
+ async audit() {
263
+ return (await readJson(await fetch(REZ_API.audit))).audit;
264
+ }
265
+ async resetAudit() {
266
+ await readJson(await fetch(REZ_API.auditReset, { method: "POST" }));
267
+ }
268
+ async weixinStatus() {
269
+ return readJson(await fetch(REZ_API.weixin));
270
+ }
271
+ async weixinLogin() {
272
+ return readJson(await fetch(REZ_API.weixin, { method: "POST" }));
273
+ }
274
+ async weixinVerify(code) {
275
+ return readJson(await fetch(REZ_API.weixinVerify, {
276
+ method: "POST",
277
+ headers: { "content-type": "application/json" },
278
+ body: JSON.stringify({ code })
279
+ }));
280
+ }
281
+ async weixinLogout() {
282
+ return readJson(await fetch(REZ_API.weixin, { method: "DELETE" }));
283
+ }
284
+ };
285
+ //#endregion
268
286
  //#region src/client/panel/helpers.ts
269
287
  /**
270
288
  * Shared panel helpers: active-language dictionary pick and error extraction.
@@ -285,38 +303,39 @@ var panel_module_default = {
285
303
  "badge": "WRGRRW_badge",
286
304
  "badgeFail": "WRGRRW_badgeFail",
287
305
  "badgeOk": "WRGRRW_badgeOk",
306
+ "body": "WRGRRW_body",
288
307
  "button": "WRGRRW_button",
289
308
  "card": "WRGRRW_card",
290
309
  "cardLabel": "WRGRRW_cardLabel",
310
+ "cardOpen": "WRGRRW_cardOpen",
291
311
  "cards": "WRGRRW_cards",
292
312
  "cardValue": "WRGRRW_cardValue",
293
313
  "checkbox": "WRGRRW_checkbox",
314
+ "chevron": "WRGRRW_chevron",
315
+ "chevronOpen": "WRGRRW_chevronOpen",
294
316
  "dangerButton": "WRGRRW_dangerButton",
295
- "entry": "WRGRRW_entry",
296
- "entryIcon": "WRGRRW_entryIcon",
297
- "entryLabel": "WRGRRW_entryLabel",
317
+ "description": "WRGRRW_description",
298
318
  "error": "WRGRRW_error",
299
319
  "field": "WRGRRW_field",
300
- "iconButton": "WRGRRW_iconButton",
320
+ "header": "WRGRRW_header",
321
+ "headText": "WRGRRW_headText",
301
322
  "input": "WRGRRW_input",
302
323
  "label": "WRGRRW_label",
303
324
  "message": "WRGRRW_message",
304
- "panel": "WRGRRW_panel",
305
- "panelContent": "WRGRRW_panelContent",
306
- "panelHeader": "WRGRRW_panelHeader",
307
- "panelTitle": "WRGRRW_panelTitle",
325
+ "name": "WRGRRW_name",
308
326
  "primaryButton": "WRGRRW_primaryButton",
309
327
  "qr": "WRGRRW_qr",
310
328
  "section": "WRGRRW_section",
311
329
  "sectionTitle": "WRGRRW_sectionTitle",
312
330
  "select": "WRGRRW_select",
331
+ "shell": "WRGRRW_shell",
332
+ "stat": "WRGRRW_stat",
313
333
  "tab": "WRGRRW_tab",
314
334
  "tabBar": "WRGRRW_tabBar",
315
335
  "tabBody": "WRGRRW_tabBody",
316
336
  "table": "WRGRRW_table",
317
337
  "tableWrap": "WRGRRW_tableWrap",
318
- "toolbar": "WRGRRW_toolbar",
319
- "view": "WRGRRW_view"
338
+ "toolbar": "WRGRRW_toolbar"
320
339
  };
321
340
  //#endregion
322
341
  //#region src/client/panel/AuditTab.tsx
@@ -387,7 +406,7 @@ function AuditTab({ api }) {
387
406
  className: panel_module_default.cards,
388
407
  children: [
389
408
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
390
- className: panel_module_default.card,
409
+ className: panel_module_default.stat,
391
410
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
392
411
  className: panel_module_default.cardLabel,
393
412
  children: tt("audit.totalCalls")
@@ -397,7 +416,7 @@ function AuditTab({ api }) {
397
416
  })]
398
417
  }),
399
418
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
400
- className: panel_module_default.card,
419
+ className: panel_module_default.stat,
401
420
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
402
421
  className: panel_module_default.cardLabel,
403
422
  children: tt("audit.inputTokens")
@@ -407,7 +426,7 @@ function AuditTab({ api }) {
407
426
  })]
408
427
  }),
409
428
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
410
- className: panel_module_default.card,
429
+ className: panel_module_default.stat,
411
430
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
412
431
  className: panel_module_default.cardLabel,
413
432
  children: tt("audit.outputTokens")
@@ -417,7 +436,7 @@ function AuditTab({ api }) {
417
436
  })]
418
437
  }),
419
438
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
420
- className: panel_module_default.card,
439
+ className: panel_module_default.stat,
421
440
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
422
441
  className: panel_module_default.cardLabel,
423
442
  children: tt("audit.totalCost")
@@ -427,7 +446,7 @@ function AuditTab({ api }) {
427
446
  })]
428
447
  }),
429
448
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
430
- className: panel_module_default.card,
449
+ className: panel_module_default.stat,
431
450
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
432
451
  className: panel_module_default.cardLabel,
433
452
  children: tt("audit.monthCost")
@@ -437,7 +456,7 @@ function AuditTab({ api }) {
437
456
  })]
438
457
  }),
439
458
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
440
- className: panel_module_default.card,
459
+ className: panel_module_default.stat,
441
460
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
442
461
  className: panel_module_default.cardLabel,
443
462
  children: tt("audit.budget")
@@ -484,8 +503,8 @@ function AuditTab({ api }) {
484
503
  //#endregion
485
504
  //#region src/client/panel/ConfigTab.tsx
486
505
  /**
487
- * Config tab: role, billing rates, and a generic editor for every MCP server
488
- * in config.servers (stdio and streamable-http), with one-click tests.
506
+ * Config tab: company MCP connections (Odoo / Nextcloud / WeCom / HA),
507
+ * role, and billing. Secrets are write-only and land in credentials.yaml.
489
508
  */
490
509
  const ROLES = [
491
510
  {
@@ -505,6 +524,12 @@ const ROLES = [
505
524
  label: "role.operations"
506
525
  }
507
526
  ];
527
+ const EMPTY_SECRETS = {
528
+ odoo: "",
529
+ nextcloud: "",
530
+ wechat: "",
531
+ homeassistant: ""
532
+ };
508
533
  function Field({ label, children }) {
509
534
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
510
535
  className: panel_module_default.field,
@@ -523,42 +548,81 @@ function Section({ title, children }) {
523
548
  }), children]
524
549
  });
525
550
  }
526
- function parseRecord(text) {
527
- const value = JSON.parse(text);
528
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("expected a JSON object");
529
- const out = {};
530
- for (const [key, item] of Object.entries(value)) out[key] = String(item);
531
- return out;
551
+ function SecretField({ label, configured, value, onChange }) {
552
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
553
+ label,
554
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
555
+ className: panel_module_default.input,
556
+ type: "password",
557
+ autoComplete: "off",
558
+ value,
559
+ placeholder: configured ? tt("config.secretConfigured") : tt("config.secretMissing"),
560
+ onChange: (event) => {
561
+ onChange(event.target.value);
562
+ }
563
+ })
564
+ });
565
+ }
566
+ function McpSection({ title, row, secret, onEnabled, onUrl, onUsername, onSecret, urlLabel, secretLabel, usernameLabel }) {
567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
568
+ title,
569
+ children: [
570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
571
+ label: tt("config.enabled"),
572
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
573
+ className: panel_module_default.checkbox,
574
+ type: "checkbox",
575
+ checked: row.enabled,
576
+ onChange: (event) => {
577
+ onEnabled(event.target.checked);
578
+ }
579
+ })
580
+ }),
581
+ onUrl !== void 0 && urlLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
582
+ label: urlLabel,
583
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
584
+ className: panel_module_default.input,
585
+ value: row.url ?? "",
586
+ onChange: (event) => {
587
+ onUrl(event.target.value);
588
+ }
589
+ })
590
+ }),
591
+ onUsername !== void 0 && usernameLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
592
+ label: usernameLabel,
593
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
594
+ className: panel_module_default.input,
595
+ value: row.username ?? "",
596
+ onChange: (event) => {
597
+ onUsername(event.target.value);
598
+ }
599
+ })
600
+ }),
601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
602
+ label: secretLabel,
603
+ configured: row.secretConfigured,
604
+ value: secret,
605
+ onChange: onSecret
606
+ }),
607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
608
+ className: panel_module_default.message,
609
+ children: row.secretRef
610
+ })
611
+ ]
612
+ });
532
613
  }
533
614
  function ConfigTab({ api }) {
534
615
  const [config, setConfig] = (0, react.useState)(null);
616
+ const [secrets, setSecrets] = (0, react.useState)(EMPTY_SECRETS);
535
617
  const [loading, setLoading] = (0, react.useState)(true);
536
618
  const [saving, setSaving] = (0, react.useState)(false);
537
619
  const [testing, setTesting] = (0, react.useState)(false);
538
620
  const [message, setMessage] = (0, react.useState)("");
539
621
  const [error, setError] = (0, react.useState)("");
540
622
  const [results, setResults] = (0, react.useState)([]);
541
- const [argsText, setArgsText] = (0, react.useState)({});
542
- const [envText, setEnvText] = (0, react.useState)({});
543
- const [headerText, setHeaderText] = (0, react.useState)({});
544
- const initTexts = (next) => {
545
- const args = {};
546
- const envs = {};
547
- const headers = {};
548
- for (const [id, server] of Object.entries(next.servers)) {
549
- args[id] = (server.args ?? []).join("\n");
550
- envs[id] = JSON.stringify(server.env ?? {}, null, 2);
551
- headers[id] = JSON.stringify(server.headers ?? {}, null, 2);
552
- }
553
- setArgsText(args);
554
- setEnvText(envs);
555
- setHeaderText(headers);
556
- };
557
623
  const load = async () => {
558
624
  try {
559
- const next = await api.getConfig();
560
- setConfig(next);
561
- initTexts(next);
625
+ setConfig(await api.getConfig());
562
626
  } catch (err) {
563
627
  setError(errorMessage(err));
564
628
  } finally {
@@ -582,13 +646,13 @@ function ConfigTab({ api }) {
582
646
  [key]: value
583
647
  });
584
648
  };
585
- const updateServer = (id, patch) => {
649
+ const patchConnection = (id, patch) => {
586
650
  setConfig((prev) => prev === null ? prev : {
587
651
  ...prev,
588
- servers: {
589
- ...prev.servers,
652
+ connections: {
653
+ ...prev.connections,
590
654
  [id]: {
591
- ...prev.servers[id],
655
+ ...prev.connections[id],
592
656
  ...patch
593
657
  }
594
658
  }
@@ -599,9 +663,32 @@ function ConfigTab({ api }) {
599
663
  setMessage("");
600
664
  setError("");
601
665
  try {
602
- const saved = await api.saveConfig(config);
666
+ const payload = {
667
+ enabled: config.enabled,
668
+ role: config.role,
669
+ billing: config.billing,
670
+ connections: {
671
+ odoo: {
672
+ ...config.connections.odoo,
673
+ secret: secrets.odoo || void 0
674
+ },
675
+ nextcloud: {
676
+ ...config.connections.nextcloud,
677
+ secret: secrets.nextcloud || void 0
678
+ },
679
+ wechat: {
680
+ ...config.connections.wechat,
681
+ secret: secrets.wechat || void 0
682
+ },
683
+ homeassistant: {
684
+ ...config.connections.homeassistant,
685
+ secret: secrets.homeassistant || void 0
686
+ }
687
+ }
688
+ };
689
+ const saved = await api.saveConfig(payload);
603
690
  setConfig(saved);
604
- initTexts(saved);
691
+ setSecrets(EMPTY_SECRETS);
605
692
  setMessage(tt("config.saved"));
606
693
  } catch (err) {
607
694
  setError(errorMessage(err));
@@ -623,28 +710,22 @@ function ConfigTab({ api }) {
623
710
  }
624
711
  };
625
712
  const renderResult = (result) => {
626
- if (result.ok) return tt("config.testOk", {
627
- tools: result.toolCount ?? 0,
628
- latency: result.latencyMs ?? 0
629
- });
630
- return tt("config.testFail", { error: result.error ?? "" });
631
- };
632
- const commitArgs = (id) => {
633
- updateServer(id, { args: argsText[id] === "" ? [] : argsText[id].split("\n").map((line) => line.trim()).filter(Boolean) });
634
- };
635
- const commitRecord = (id, kind) => {
636
- const text = kind === "env" ? envText[id] : headerText[id];
637
- try {
638
- const record = parseRecord(text ?? "{}");
639
- updateServer(id, kind === "env" ? { env: record } : { headers: record });
640
- setError("");
641
- } catch (err) {
642
- setError(tt("config.jsonInvalid", { error: errorMessage(err) }));
713
+ if (result.ok) {
714
+ if (result.serverInfo !== void 0) return result.serverInfo;
715
+ return tt("config.testOk", {
716
+ tools: result.toolCount ?? 0,
717
+ latency: result.latencyMs ?? 0
718
+ });
643
719
  }
720
+ return tt("config.testFail", { error: result.error ?? "" });
644
721
  };
645
722
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
646
723
  className: panel_module_default.tabBody,
647
724
  children: [
725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
726
+ className: panel_module_default.message,
727
+ children: tt("config.mcpHint")
728
+ }),
648
729
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
649
730
  title: tt("config.role"),
650
731
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
@@ -672,110 +753,82 @@ function ConfigTab({ api }) {
672
753
  })
673
754
  })]
674
755
  }),
675
- Object.entries(config.servers).map(([id, server]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
676
- title: id,
677
- children: [
678
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
679
- label: tt("config.enabled"),
680
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
681
- className: panel_module_default.checkbox,
682
- type: "checkbox",
683
- checked: server.enabled,
684
- onChange: (event) => {
685
- updateServer(id, { enabled: event.target.checked });
686
- }
687
- })
688
- }),
689
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
690
- label: tt("config.transport"),
691
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
692
- className: panel_module_default.select,
693
- value: server.transport,
694
- onChange: (event) => {
695
- updateServer(id, { transport: event.target.value });
696
- },
697
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
698
- value: "stdio",
699
- children: "stdio"
700
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
701
- value: "streamable-http",
702
- children: "streamable-http"
703
- })]
704
- })
705
- }),
706
- server.transport === "stdio" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
707
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
708
- label: tt("config.command"),
709
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
710
- className: panel_module_default.input,
711
- value: server.command ?? "",
712
- onChange: (event) => {
713
- updateServer(id, { command: event.target.value });
714
- }
715
- })
716
- }),
717
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
718
- label: tt("config.args"),
719
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
720
- className: panel_module_default.input,
721
- rows: 3,
722
- value: argsText[id] ?? "",
723
- onChange: (event) => {
724
- setArgsText((prev) => ({
725
- ...prev,
726
- [id]: event.target.value
727
- }));
728
- },
729
- onBlur: () => {
730
- commitArgs(id);
731
- }
732
- })
733
- }),
734
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
735
- label: tt("config.env"),
736
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
737
- className: panel_module_default.input,
738
- rows: 4,
739
- value: envText[id] ?? "{}",
740
- onChange: (event) => {
741
- setEnvText((prev) => ({
742
- ...prev,
743
- [id]: event.target.value
744
- }));
745
- },
746
- onBlur: () => {
747
- commitRecord(id, "env");
748
- }
749
- })
750
- })
751
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
752
- label: tt("config.url"),
753
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
754
- className: panel_module_default.input,
755
- value: server.url ?? "",
756
- onChange: (event) => {
757
- updateServer(id, { url: event.target.value });
758
- }
759
- })
760
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
761
- label: tt("config.headers"),
762
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
763
- className: panel_module_default.input,
764
- rows: 4,
765
- value: headerText[id] ?? "{}",
766
- onChange: (event) => {
767
- setHeaderText((prev) => ({
768
- ...prev,
769
- [id]: event.target.value
770
- }));
771
- },
772
- onBlur: () => {
773
- commitRecord(id, "headers");
774
- }
775
- })
776
- })] })
777
- ]
778
- }, id)),
756
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
757
+ title: tt("config.odoo"),
758
+ row: config.connections.odoo,
759
+ secret: secrets.odoo,
760
+ onEnabled: (enabled) => {
761
+ patchConnection("odoo", { enabled });
762
+ },
763
+ onUrl: (url) => {
764
+ patchConnection("odoo", { url });
765
+ },
766
+ onSecret: (value) => {
767
+ setSecrets((prev) => ({
768
+ ...prev,
769
+ odoo: value
770
+ }));
771
+ },
772
+ urlLabel: tt("config.odoo.url"),
773
+ secretLabel: tt("config.odoo.apiKey")
774
+ }),
775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
776
+ title: tt("config.nextcloud"),
777
+ row: config.connections.nextcloud,
778
+ secret: secrets.nextcloud,
779
+ onEnabled: (enabled) => {
780
+ patchConnection("nextcloud", { enabled });
781
+ },
782
+ onUrl: (url) => {
783
+ patchConnection("nextcloud", { url });
784
+ },
785
+ onUsername: (username) => {
786
+ patchConnection("nextcloud", { username });
787
+ },
788
+ onSecret: (value) => {
789
+ setSecrets((prev) => ({
790
+ ...prev,
791
+ nextcloud: value
792
+ }));
793
+ },
794
+ urlLabel: tt("config.nextcloud.url"),
795
+ usernameLabel: tt("config.nextcloud.username"),
796
+ secretLabel: tt("config.nextcloud.appPassword")
797
+ }),
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
799
+ title: tt("config.wecom"),
800
+ row: config.connections.wechat,
801
+ secret: secrets.wechat,
802
+ onEnabled: (enabled) => {
803
+ patchConnection("wechat", { enabled });
804
+ },
805
+ onSecret: (value) => {
806
+ setSecrets((prev) => ({
807
+ ...prev,
808
+ wechat: value
809
+ }));
810
+ },
811
+ secretLabel: tt("config.wecom.webhook")
812
+ }),
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(McpSection, {
814
+ title: tt("config.ha"),
815
+ row: config.connections.homeassistant,
816
+ secret: secrets.homeassistant,
817
+ onEnabled: (enabled) => {
818
+ patchConnection("homeassistant", { enabled });
819
+ },
820
+ onUrl: (url) => {
821
+ patchConnection("homeassistant", { url });
822
+ },
823
+ onSecret: (value) => {
824
+ setSecrets((prev) => ({
825
+ ...prev,
826
+ homeassistant: value
827
+ }));
828
+ },
829
+ urlLabel: tt("config.ha.url"),
830
+ secretLabel: tt("config.ha.token")
831
+ }),
779
832
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
780
833
  title: tt("config.billing"),
781
834
  children: [
@@ -1090,9 +1143,10 @@ function WeixinTab({ api }) {
1090
1143
  });
1091
1144
  }
1092
1145
  //#endregion
1093
- //#region src/client/panel/RezPanel.tsx
1146
+ //#region src/client/settings-card.tsx
1094
1147
  /**
1095
- * Rez Suite panel shell: header + three tabs (config/status/audit).
1148
+ * ReZ settings pages: one accordion card in 设置 → 插件 → 插件配置,
1149
+ * plus a dedicated Plugins tab for the same content.
1096
1150
  */
1097
1151
  const TABS = [
1098
1152
  {
@@ -1112,27 +1166,12 @@ const TABS = [
1112
1166
  label: () => tt("tab.audit")
1113
1167
  }
1114
1168
  ];
1115
- function RezPanel({ controller, api }) {
1169
+ function RezSettingsPage() {
1170
+ const api = (0, react.useMemo)(() => new RezApi(), []);
1116
1171
  const [activeTab, setActiveTab] = (0, react.useState)("config");
1117
1172
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1118
- className: panel_module_default.panel,
1173
+ className: panel_module_default.shell,
1119
1174
  children: [
1120
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1121
- className: panel_module_default.panelHeader,
1122
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1123
- className: panel_module_default.panelTitle,
1124
- children: tt("panel.title")
1125
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1126
- type: "button",
1127
- className: panel_module_default.iconButton,
1128
- title: tt("common.close"),
1129
- "aria-label": tt("common.close"),
1130
- onClick: () => {
1131
- controller.close();
1132
- },
1133
- children: "x"
1134
- })]
1135
- }),
1136
1175
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1137
1176
  className: panel_module_default.tabBar,
1138
1177
  role: "tablist",
@@ -1148,218 +1187,76 @@ function RezPanel({ controller, api }) {
1148
1187
  children: tab.label()
1149
1188
  }, tab.id))
1150
1189
  }),
1151
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1152
- className: panel_module_default.panelContent,
1153
- children: [
1154
- activeTab === "config" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigTab, { api }),
1155
- activeTab === "weixin" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeixinTab, { api }),
1156
- activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
1157
- activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
1158
- ]
1159
- })
1190
+ activeTab === "config" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigTab, { api }),
1191
+ activeTab === "weixin" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeixinTab, { api }),
1192
+ activeTab === "status" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTab, { api }),
1193
+ activeTab === "audit" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuditTab, { api })
1160
1194
  ]
1161
1195
  });
1162
1196
  }
1163
- //#endregion
1164
- //#region src/client/mount.tsx
1165
- /**
1166
- * Rez panel view mounting. Single-occupant center-column takeover, same
1167
- * mechanism as the task board and ssh panels. The conversation subtree stays
1168
- * mounted under the panel.
1169
- */
1170
- const CONVERSATION_COLUMN_SELECTOR = "[data-pane=\"conversation\"]";
1171
- const ACTIVE_ATTR = "data-dsh-rez-active";
1172
- const OTHER_ACTIVE_ATTRS = ["data-dsh-taskboard-active", "data-dsh-ssh-active"];
1173
- const ACTIVATE_EVENT = "dsh-panel-activate";
1174
- const PANEL_NAME = "rez";
1175
- function conversationColumn() {
1176
- return document.querySelector(CONVERSATION_COLUMN_SELECTOR) ?? void 0;
1177
- }
1178
- function mountPanel(controller, api) {
1179
- let root;
1180
- let container;
1181
- const ensure = () => {
1182
- if (container !== void 0) {
1183
- if (container.isConnected) return;
1184
- root?.unmount();
1185
- root = void 0;
1186
- container.remove();
1187
- container = void 0;
1188
- }
1189
- const column = conversationColumn();
1190
- if (column === void 0) return;
1191
- container = document.createElement("div");
1192
- container.dataset.dshRezView = "";
1193
- container.className = panel_module_default.view;
1194
- column.appendChild(container);
1195
- root = (0, react_dom_client.createRoot)(container);
1196
- root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RezPanel, {
1197
- controller,
1198
- api
1199
- }));
1200
- };
1201
- const waitObserver = new MutationObserver(() => {
1202
- ensure();
1203
- });
1204
- waitObserver.observe(document.body, {
1205
- childList: true,
1206
- subtree: true
1207
- });
1208
- const retryTimer = window.setInterval(() => {
1209
- ensure();
1210
- if (container?.isConnected === true) window.clearInterval(retryTimer);
1211
- }, 250);
1212
- const applyActive = () => {
1213
- if (controller.getSnapshot().panelOpen) {
1214
- for (const attr of OTHER_ACTIVE_ATTRS) document.documentElement.removeAttribute(attr);
1215
- document.documentElement.setAttribute(ACTIVE_ATTR, "");
1216
- document.dispatchEvent(new CustomEvent(ACTIVATE_EVENT, { detail: PANEL_NAME }));
1217
- } else document.documentElement.removeAttribute(ACTIVE_ATTR);
1218
- };
1219
- const onOtherActivate = (event) => {
1220
- const detail = event.detail;
1221
- if ((detail === "taskboard" || detail === "ssh") && controller.getSnapshot().panelOpen) controller.close();
1222
- };
1223
- const SIDEBAR_ROW_SELECTOR = "[class*=\"sessionRow\"], [class*=\"projectRow\"], [class*=\"searchResultRow\"], [class*=\"searchResultWorkspace\"], [class*=\"newSession\"]";
1224
- const onClickSidebarRow = (event) => {
1225
- if (!controller.getSnapshot().panelOpen) return;
1226
- const target = event.target;
1227
- if (target === null) return;
1228
- if (target.closest(SIDEBAR_ROW_SELECTOR) !== null) controller.close();
1229
- };
1230
- document.addEventListener("click", onClickSidebarRow, true);
1231
- document.addEventListener(ACTIVATE_EVENT, onOtherActivate);
1232
- const unsubscribe = controller.subscribe(applyActive);
1233
- applyActive();
1234
- ensure();
1235
- return () => {
1236
- window.clearInterval(retryTimer);
1237
- document.removeEventListener("click", onClickSidebarRow, true);
1238
- document.removeEventListener(ACTIVATE_EVENT, onOtherActivate);
1239
- waitObserver.disconnect();
1240
- unsubscribe();
1241
- document.documentElement.removeAttribute(ACTIVE_ATTR);
1242
- root?.unmount();
1243
- root = void 0;
1244
- container?.remove();
1245
- container = void 0;
1246
- };
1247
- }
1248
- //#endregion
1249
- //#region src/client/panel/controller.ts
1250
- var PanelController = class {
1251
- panelOpen = false;
1252
- listeners = /* @__PURE__ */ new Set();
1253
- getSnapshot() {
1254
- return { panelOpen: this.panelOpen };
1255
- }
1256
- subscribe(fn) {
1257
- this.listeners.add(fn);
1258
- return () => {
1259
- this.listeners.delete(fn);
1260
- };
1261
- }
1262
- open() {
1263
- if (this.panelOpen) return;
1264
- this.panelOpen = true;
1265
- this.notify();
1266
- }
1267
- close() {
1268
- if (!this.panelOpen) return;
1269
- this.panelOpen = false;
1270
- this.notify();
1271
- }
1272
- toggle() {
1273
- if (this.panelOpen) this.close();
1274
- else this.open();
1275
- }
1276
- notify() {
1277
- for (const fn of [...this.listeners]) fn();
1278
- }
1279
- };
1280
- //#endregion
1281
- //#region src/client/sidebar-entry.ts
1282
- function sidebarRoot() {
1283
- const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
1284
- if (column === null) return void 0;
1285
- return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
1286
- }
1287
- function newSessionButton(root) {
1288
- const nested = root.querySelector("button[class*=\"newSession\"]");
1289
- if (nested !== null) return nested;
1290
- for (const child of root.children) if (child.tagName === "BUTTON") return child;
1291
- }
1292
- function createEntry(controller) {
1293
- const entry = document.createElement("button");
1294
- entry.type = "button";
1295
- entry.dataset.dshRezEntry = "";
1296
- entry.className = panel_module_default.entry;
1297
- entry.setAttribute("aria-label", tt("entry.label"));
1298
- entry.setAttribute("title", tt("entry.tooltip"));
1299
- entry.innerHTML = "<span class=\"" + panel_module_default.entryIcon + "\"><svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"2.5\" y=\"2.5\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 8l2 2 4-4\"/></svg></span><span class=\"" + panel_module_default.entryLabel + "\">" + tt("entry.label") + "</span>";
1300
- entry.addEventListener("click", () => {
1301
- controller.toggle();
1302
- });
1303
- return entry;
1304
- }
1305
- function placeEntry(root, entry) {
1306
- const button = newSessionButton(root);
1307
- if (button === void 0) return false;
1308
- if (entry.parentElement !== root) {
1309
- const row = button.closest("[class*=\"logoRow\"]");
1310
- const base = row !== null && row.parentElement === root ? row : button;
1311
- const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches("[data-dsh-taskboard-entry], [data-dsh-ssh-entry], [data-dsh-rez-entry]"));
1312
- const anchor = family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
1313
- root.insertBefore(entry, anchor);
1314
- }
1315
- return true;
1316
- }
1317
- function mountSidebarEntry(controller) {
1318
- const entry = createEntry(controller);
1319
- let root;
1320
- let placed = false;
1321
- const place = () => {
1322
- const next = sidebarRoot();
1323
- if (next === void 0) return;
1324
- if (root !== void 0 && root !== next) {
1325
- if (entry.parentElement === root) entry.remove();
1326
- placed = false;
1327
- }
1328
- root = next;
1329
- if (!placed) placed = placeEntry(root, entry);
1330
- };
1331
- const observer = new MutationObserver(place);
1332
- observer.observe(document.body, {
1333
- childList: true,
1334
- subtree: true
1197
+ function RezPluginCard() {
1198
+ const [open, setOpen] = (0, react.useState)(false);
1199
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1200
+ className: panel_module_default.card + (open ? " " + panel_module_default.cardOpen : ""),
1201
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1202
+ type: "button",
1203
+ className: panel_module_default.header,
1204
+ onClick: () => {
1205
+ setOpen(!open);
1206
+ },
1207
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1208
+ className: panel_module_default.headText,
1209
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1210
+ className: panel_module_default.name,
1211
+ children: tt("settings.title")
1212
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1213
+ className: panel_module_default.description,
1214
+ children: tt("settings.description")
1215
+ })]
1216
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1217
+ className: panel_module_default.chevron + (open ? " " + panel_module_default.chevronOpen : ""),
1218
+ viewBox: "0 0 14 14",
1219
+ width: "14",
1220
+ height: "14",
1221
+ "aria-hidden": "true",
1222
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1223
+ d: "M3 5l4 4 4-4",
1224
+ fill: "none",
1225
+ stroke: "currentColor",
1226
+ strokeWidth: "1.4",
1227
+ strokeLinecap: "round",
1228
+ strokeLinejoin: "round"
1229
+ })
1230
+ })]
1231
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1232
+ className: panel_module_default.body,
1233
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RezSettingsPage, {})
1234
+ }) : null]
1335
1235
  });
1336
- place();
1337
- return () => {
1338
- observer.disconnect();
1339
- entry.remove();
1340
- };
1341
1236
  }
1342
1237
  //#endregion
1343
1238
  //#region src/client/index.ts
1344
1239
  const NS = "dsh-rez-suite";
1345
1240
  const inject = ["slots", "locale"];
1346
1241
  function apply(ctx) {
1242
+ const t = ctx.locale.bind(NS);
1347
1243
  ctx.effect(() => ctx.locale.register(NS, {
1348
1244
  zh,
1349
1245
  en
1350
1246
  }), "dsh-rez-suite: dictionaries");
1351
- const controller = new PanelController();
1352
- const api = new RezApi();
1353
- const disposers = [];
1354
- try {
1355
- disposers.push(mountSidebarEntry(controller));
1356
- disposers.push(mountPanel(controller, api));
1357
- } catch (error) {
1358
- console.warn("[dsh-rez-suite] mount failed:", error);
1359
- }
1360
- ctx.effect(() => () => {
1361
- for (const dispose of disposers.splice(0)) dispose();
1362
- }, "dsh-rez-suite: ui mounts");
1247
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1248
+ name: "settings.plugin.item",
1249
+ id: "rez-suite",
1250
+ order: 40,
1251
+ locale: NS
1252
+ }, RezPluginCard));
1253
+ ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
1254
+ name: "settings.plugins.tab",
1255
+ id: "rez",
1256
+ order: 20,
1257
+ label: () => t("settings.title"),
1258
+ locale: NS
1259
+ }, RezSettingsPage));
1363
1260
  }
1364
1261
  //#endregion
1365
1262
  exports.apply = apply;