@mhfire/dsh-im-bridge 0.1.3 → 0.2.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/lib/client.js CHANGED
@@ -1,153 +1,789 @@
1
- window.__ModuleLoader__.load({ id: '@mhfire/dsh-im-bridge', factory: (require) => {
2
- var module = { exports: {} }; var exports = module.exports;
3
- /**
4
- * im-bridge 配置卡片(浏览器端)。
5
- * tsdown 生成的工厂格式手写, 免构建: react / runtime-client 从模块表 require,
6
- * 其余逻辑全部内联。在 Settings 插件配置(Plugins)页渲染 im-bridge 命名空间卡片。
7
- */
8
- var React = require('react');
9
- var createElement = React.createElement;
10
- var useState = React.useState, useEffect = React.useEffect;
11
- var runtimeClient = require('@deepseek-ai/dsh-client-runtime/client');
12
- var createSnapshotStore = runtimeClient.createSnapshotStore;
13
-
14
- var NS = 'im-bridge';
15
-
16
- /** 卡片编辑的字段(命名空间内的可编辑项; botId/secret 属敏感项, 保留在 profile patch) */
17
- var FIELDS = [
18
- { field: 'allowFrom', label: '允许的发送者 userid(逗号分隔,空 = 所有人)', kind: 'text' },
19
- { field: 'agentTimeoutSec', label: '单任务超时(秒)', kind: 'number' },
20
- { field: 'startHint', label: '开始处理时的占位提示', kind: 'text' },
21
- { field: 'deniedMessage', label: '非白名单拒绝文案', kind: 'text' },
22
- { field: 'welcomeMessage', label: '进入会话欢迎语', kind: 'text' },
23
- ];
24
-
25
- function fmt(f, v) {
26
- if (f.field === 'allowFrom') return Array.isArray(v) ? v.join(',') : '';
27
- if (f.kind === 'number') return typeof v === 'number' ? String(v) : '';
28
- return typeof v === 'string' ? v : '';
29
- }
30
-
31
- /** 返回写入值; null = 清除; undefined = 非法(阻止保存) */
32
- function parse(f, text) {
33
- if (f.field === 'allowFrom') {
34
- var t = text.trim();
35
- return t === '' ? [] : t.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
36
- }
37
- if (f.kind === 'number') {
38
- var t2 = text.trim();
39
- if (t2 === '') return null;
40
- var n = Number(t2);
41
- return Number.isFinite(n) ? n : undefined;
42
- }
43
- return text;
44
- }
45
-
46
- function WecomCard(props) {
47
- var state = props.useWecomCard ? props.useWecomCard(function (s) { return s; }) : { available: false, writable: false, value: {} };
48
- var value = state.value || {};
49
- var draftsState = useState({});
50
- var drafts = draftsState[0], setDrafts = draftsState[1];
51
- var savingState = useState(false);
52
- var saving = savingState[0], setSaving = savingState[1];
53
-
54
- useEffect(function () {
55
- var next = {};
56
- FIELDS.forEach(function (f) { next[f.field] = fmt(f, value[f.field]); });
57
- setDrafts(next);
58
- }, [state.value]);
59
-
60
- if (!state.available) {
61
- return createElement('p', { style: { color: '#888' } }, 'im-bridge 命名空间不可用');
62
- }
63
-
64
- var invalid = false;
65
- FIELDS.forEach(function (f) {
66
- var p = parse(f, drafts[f.field] === undefined ? '' : drafts[f.field]);
67
- if (p === undefined) invalid = true;
68
- });
69
-
70
- function onEdit(field, text) {
71
- setDrafts(function (d) {
72
- var n = {};
73
- Object.keys(d).forEach(function (k) { n[k] = d[k]; });
74
- n[field] = text;
75
- return n;
76
- });
77
- }
78
-
79
- function onSave() {
80
- if (invalid || saving) return;
81
- setSaving(true);
82
- var writes = [];
83
- FIELDS.forEach(function (f) {
84
- var p = parse(f, drafts[f.field] === undefined ? '' : drafts[f.field]);
85
- if (p === null) writes.push({ field: f.field, clear: true });
86
- else writes.push({ field: f.field, clear: false, value: p });
87
- });
88
- Promise.all(writes.map(function (w) {
89
- return w.clear ? props.scope.unset(w.field) : props.scope.set(w.field, w.value);
90
- })).then(function () { setSaving(false); }, function () { setSaving(false); });
91
- }
92
-
93
- return createElement('div', { style: { display: 'grid', gap: '10px' } },
94
- FIELDS.map(function (f) {
95
- return createElement('label', { key: f.field, style: { display: 'grid', gap: '2px', fontSize: '13px' } },
96
- createElement('span', {}, f.label),
97
- createElement('input', {
98
- type: f.kind === 'number' ? 'number' : 'text',
99
- value: drafts[f.field] === undefined ? '' : drafts[f.field],
100
- disabled: !state.writable,
101
- style: { width: '100%', boxSizing: 'border-box', padding: '4px 6px', fontSize: '13px' },
102
- onChange: function (e) { onEdit(f.field, e.target.value); },
103
- }),
104
- );
105
- }),
106
- createElement('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', marginTop: '2px' } },
107
- createElement('button', {
108
- disabled: invalid || saving || !state.writable,
109
- onClick: onSave,
110
- style: { padding: '4px 14px', cursor: 'pointer' },
111
- }, saving ? '保存中…' : '保存'),
112
- createElement('span', { style: { fontSize: '12px', color: invalid ? '#c00' : '#888' } },
113
- invalid ? '存在无效输入' : (state.writable ? '' : '当前不可写')),
114
- ),
115
- );
116
- }
117
-
118
- function apply(ctx) {
119
- var scope = ctx.settingsScope.bind({ namespace: NS });
120
-
121
- function project() {
122
- var snap = scope.getSnapshot();
123
- return { available: snap.status === 'ready', writable: snap.writable, value: snap.value };
124
- }
125
- var store = createSnapshotStore(project());
126
- scope.subscribe(function () { store.set(project()); });
127
-
128
- ctx.effect(function () {
129
- return ctx.locale.register(NS, {
130
- zh: { title: '企业微信桥接' },
131
- en: { title: 'WeCom Bridge' },
132
- });
133
- }, 'im-bridge: locale dicts');
134
-
135
- var face = {
136
- hooks: { wecomCard: store },
137
- scope: scope,
138
- };
139
-
140
- ctx.slots.inject('settings.plugin.item', function* () {
141
- yield ctx.slots.register({
142
- name: 'settings.plugin.item',
143
- id: 'im-bridge',
144
- order: 30,
145
- locale: NS,
146
- inject: function () { return face; },
147
- }, WecomCard);
148
- });
149
- }
150
-
151
- exports.inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope'];
152
- exports.apply = apply;
153
- return module.exports; } });
1
+ window.__ModuleLoader__.load({
2
+ id: "@mhfire/dsh-im-bridge",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
+ //#region \0dsh-css:C:\Users\user\Desktop\dsh-im-bridge\plugin\src\client\PluginCard.module.css.mjs
12
+ const css$1 = ".yj1zEa_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.yj1zEa_card:hover{border-color:var(--dsw-alias-label-dimmed)}.yj1zEa_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.yj1zEa_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.yj1zEa_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.yj1zEa_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.yj1zEa_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.yj1zEa_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.yj1zEa_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.yj1zEa_chevronOpen{transform:rotate(180deg)}.yj1zEa_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.yj1zEa_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.yj1zEa_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.yj1zEa_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.yj1zEa_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.yj1zEa_discard,.yj1zEa_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.yj1zEa_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.yj1zEa_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.yj1zEa_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.yj1zEa_discard:disabled,.yj1zEa_save:disabled{opacity:.4;cursor:default}.yj1zEa_discard:focus-visible,.yj1zEa_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}";
13
+ const tagId$1 = "@mhfire/dsh-im-bridge/PluginCard.module.css";
14
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
15
+ const tag = document.createElement("style");
16
+ tag.dataset.plugin = "@mhfire/dsh-im-bridge";
17
+ tag.dataset.pluginCss = tagId$1;
18
+ tag.textContent = css$1;
19
+ document.head.appendChild(tag);
20
+ }
21
+ var PluginCard_module_css_default = {
22
+ "body": "yj1zEa_body",
23
+ "card": "yj1zEa_card",
24
+ "cardOpen": "yj1zEa_cardOpen",
25
+ "chevron": "yj1zEa_chevron",
26
+ "chevronOpen": "yj1zEa_chevronOpen",
27
+ "description": "yj1zEa_description",
28
+ "discard": "yj1zEa_discard",
29
+ "failed": "yj1zEa_failed",
30
+ "footer": "yj1zEa_footer",
31
+ "headText": "yj1zEa_headText",
32
+ "header": "yj1zEa_header",
33
+ "name": "yj1zEa_name",
34
+ "pending": "yj1zEa_pending",
35
+ "readOnly": "yj1zEa_readOnly",
36
+ "save": "yj1zEa_save"
37
+ };
38
+ //#endregion
39
+ //#region src/client/PluginCard.tsx
40
+ /** Expandable plugin card chrome matching the Host Plugins section. */
41
+ /**
42
+ * Render one plugin card.
43
+ * @param props - locale copy, form state, and controls.
44
+ * @returns the card, or nothing when the namespace is unavailable.
45
+ */
46
+ function PluginCard(props) {
47
+ const [open, setOpen] = (0, react.useState)(false);
48
+ const { state } = props;
49
+ if (!state.available) return null;
50
+ const title = props.t(props.titleKey);
51
+ const blocked = !state.dirty || state.invalid || state.saving;
52
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
53
+ className: open ? `${PluginCard_module_css_default.card} ${PluginCard_module_css_default.cardOpen}` : PluginCard_module_css_default.card,
54
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
55
+ type: "button",
56
+ className: PluginCard_module_css_default.header,
57
+ "aria-expanded": open,
58
+ "aria-label": `${props.t(open ? "collapse" : "expand")}: ${title}`,
59
+ onClick: () => {
60
+ setOpen((current) => !current);
61
+ },
62
+ children: [
63
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
64
+ className: PluginCard_module_css_default.headText,
65
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
66
+ className: PluginCard_module_css_default.name,
67
+ children: title
68
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
69
+ className: PluginCard_module_css_default.description,
70
+ children: props.t(props.descriptionKey)
71
+ })]
72
+ }),
73
+ state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
74
+ className: PluginCard_module_css_default.pending,
75
+ children: props.t("unsaved")
76
+ }) : null,
77
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: open ? `${PluginCard_module_css_default.chevron} ${PluginCard_module_css_default.chevronOpen}` : PluginCard_module_css_default.chevron })
78
+ ]
79
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
80
+ className: PluginCard_module_css_default.body,
81
+ children: [
82
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
83
+ className: PluginCard_module_css_default.readOnly,
84
+ role: "status",
85
+ children: props.t("readOnly")
86
+ }) : null,
87
+ props.children,
88
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
89
+ className: PluginCard_module_css_default.footer,
90
+ children: [
91
+ state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
92
+ className: PluginCard_module_css_default.failed,
93
+ role: "status",
94
+ children: props.t("saveFailed")
95
+ }) : null,
96
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
97
+ type: "button",
98
+ className: PluginCard_module_css_default.discard,
99
+ disabled: !state.dirty || state.saving,
100
+ onClick: props.onDiscard,
101
+ children: props.t("discard")
102
+ }),
103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
104
+ type: "button",
105
+ className: PluginCard_module_css_default.save,
106
+ disabled: blocked,
107
+ onClick: props.onSave,
108
+ children: props.t(state.saving ? "saving" : "save")
109
+ })
110
+ ]
111
+ })
112
+ ]
113
+ }) : null]
114
+ });
115
+ }
116
+ //#endregion
117
+ //#region \0dsh-css:C:\Users\user\Desktop\dsh-im-bridge\plugin\src\client\fields.module.css.mjs
118
+ const css = ".OMD7cq_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.OMD7cq_field+.OMD7cq_field{border-top:1px solid var(--dsw-alias-border-l2)}.OMD7cq_head{align-items:center;gap:8px;display:flex}.OMD7cq_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.OMD7cq_badges{align-items:center;gap:8px;display:inline-flex}.OMD7cq_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.OMD7cq_badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}.OMD7cq_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.OMD7cq_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.OMD7cq_reset:disabled{cursor:default}.OMD7cq_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.OMD7cq_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.OMD7cq_input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.OMD7cq_inputInvalid{border:1px solid var(--dsw-alias-label-error);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.OMD7cq_inputInvalid:focus-visible{outline:none}.OMD7cq_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.OMD7cq_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}";
119
+ const tagId = "@mhfire/dsh-im-bridge/fields.module.css";
120
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
121
+ const tag = document.createElement("style");
122
+ tag.dataset.plugin = "@mhfire/dsh-im-bridge";
123
+ tag.dataset.pluginCss = tagId;
124
+ tag.textContent = css;
125
+ document.head.appendChild(tag);
126
+ }
127
+ var fields_module_css_default = {
128
+ "badge": "OMD7cq_badge",
129
+ "badgeMuted": "OMD7cq_badgeMuted",
130
+ "badges": "OMD7cq_badges",
131
+ "field": "OMD7cq_field",
132
+ "head": "OMD7cq_head",
133
+ "hint": "OMD7cq_hint",
134
+ "input": "OMD7cq_input",
135
+ "inputInvalid": "OMD7cq_inputInvalid",
136
+ "invalid": "OMD7cq_invalid",
137
+ "label": "OMD7cq_label",
138
+ "reset": "OMD7cq_reset"
139
+ };
140
+ //#endregion
141
+ //#region src/client/fields.tsx
142
+ /**
143
+ * A staged value field.
144
+ * @param props - the field's copy, staged text, and edit actions.
145
+ * @returns the labelled control.
146
+ */
147
+ function ValueField(props) {
148
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
149
+ className: fields_module_css_default.field,
150
+ children: [
151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
152
+ className: fields_module_css_default.head,
153
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
154
+ className: fields_module_css_default.label,
155
+ htmlFor: props.id,
156
+ children: props.label
157
+ }), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
158
+ className: fields_module_css_default.badges,
159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
160
+ className: fields_module_css_default.badge,
161
+ children: props.overriddenLabel
162
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
163
+ type: "button",
164
+ className: fields_module_css_default.reset,
165
+ disabled: props.disabled,
166
+ onClick: props.onReset,
167
+ children: props.resetLabel
168
+ })]
169
+ }) : null]
170
+ }),
171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
172
+ id: props.id,
173
+ className: props.invalid ? fields_module_css_default.inputInvalid : fields_module_css_default.input,
174
+ type: "text",
175
+ ...props.numeric === true ? { inputMode: "numeric" } : {},
176
+ ...props.invalid ? { "aria-invalid": true } : {},
177
+ value: props.text,
178
+ disabled: props.disabled,
179
+ onChange: (event) => {
180
+ props.onEdit(event.target.value);
181
+ }
182
+ }),
183
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
184
+ className: props.invalid ? fields_module_css_default.invalid : fields_module_css_default.hint,
185
+ children: props.invalid ? props.invalidLabel : props.hint
186
+ })
187
+ ]
188
+ });
189
+ }
190
+ /**
191
+ * Write-only credential control. The literal never rides a response, so the
192
+ * control starts blank and reports only whether one is configured.
193
+ * @param props - the field's copy, staged text, and configured state.
194
+ * @returns the labelled control.
195
+ */
196
+ function SecretField(props) {
197
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
198
+ className: fields_module_css_default.field,
199
+ children: [
200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
201
+ className: fields_module_css_default.head,
202
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
203
+ className: fields_module_css_default.label,
204
+ htmlFor: props.id,
205
+ children: props.label
206
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
207
+ className: fields_module_css_default.badges,
208
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
209
+ className: props.configured ? fields_module_css_default.badge : fields_module_css_default.badgeMuted,
210
+ children: props.stateLabel
211
+ })
212
+ })]
213
+ }),
214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
215
+ id: props.id,
216
+ className: fields_module_css_default.input,
217
+ type: "password",
218
+ autoComplete: "off",
219
+ value: props.text,
220
+ disabled: props.disabled,
221
+ onChange: (event) => {
222
+ props.onEdit(event.target.value);
223
+ }
224
+ }),
225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
226
+ className: fields_module_css_default.hint,
227
+ children: props.hint
228
+ })
229
+ ]
230
+ });
231
+ }
232
+ //#endregion
233
+ //#region src/client/WecomCard.tsx
234
+ /**
235
+ * Render the WeCom settings card.
236
+ * @param props - locale copy, snapshot hook, and form actions.
237
+ * @returns the card, or nothing when the namespace is unavailable.
238
+ */
239
+ function WecomCard(props) {
240
+ const { t } = props;
241
+ const state = props.useWecomCard((snapshot) => snapshot);
242
+ const disabled = !state.writable;
243
+ const field = {
244
+ overriddenLabel: t("overridden"),
245
+ resetLabel: t("reset"),
246
+ invalidLabel: t("invalidNumber"),
247
+ disabled
248
+ };
249
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PluginCard, {
250
+ t,
251
+ titleKey: "title",
252
+ descriptionKey: "description",
253
+ state,
254
+ onSave: props.save,
255
+ onDiscard: props.discard,
256
+ children: [
257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
258
+ id: "im-bridge-botId",
259
+ label: t("botId"),
260
+ hint: t("secretHint"),
261
+ disabled,
262
+ text: state.botId.text,
263
+ configured: state.botIdConfigured,
264
+ stateLabel: state.botIdConfigured ? t("secretConfigured") : t("secretUnset"),
265
+ onEdit: (text) => {
266
+ props.edit("botId", text);
267
+ }
268
+ }),
269
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretField, {
270
+ id: "im-bridge-secret",
271
+ label: t("secret"),
272
+ hint: t("secretHint"),
273
+ disabled,
274
+ text: state.secret.text,
275
+ configured: state.secretConfigured,
276
+ stateLabel: state.secretConfigured ? t("secretConfigured") : t("secretUnset"),
277
+ onEdit: (text) => {
278
+ props.edit("secret", text);
279
+ }
280
+ }),
281
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
282
+ id: "im-bridge-allowFrom",
283
+ label: t("allowFrom"),
284
+ hint: t("allowFromHint"),
285
+ ...field,
286
+ ...state.allowFrom,
287
+ onEdit: (text) => {
288
+ props.edit("allowFrom", text);
289
+ },
290
+ onReset: () => {
291
+ props.resetField("allowFrom");
292
+ }
293
+ }),
294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
295
+ id: "im-bridge-agentTimeoutSec",
296
+ label: t("agentTimeoutSec"),
297
+ hint: t("agentTimeoutSecHint"),
298
+ numeric: true,
299
+ ...field,
300
+ ...state.agentTimeoutSec,
301
+ onEdit: (text) => {
302
+ props.edit("agentTimeoutSec", text);
303
+ },
304
+ onReset: () => {
305
+ props.resetField("agentTimeoutSec");
306
+ }
307
+ }),
308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
309
+ id: "im-bridge-startHint",
310
+ label: t("startHint"),
311
+ hint: t("startHintHint"),
312
+ ...field,
313
+ ...state.startHint,
314
+ onEdit: (text) => {
315
+ props.edit("startHint", text);
316
+ },
317
+ onReset: () => {
318
+ props.resetField("startHint");
319
+ }
320
+ }),
321
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
322
+ id: "im-bridge-deniedMessage",
323
+ label: t("deniedMessage"),
324
+ hint: t("deniedMessageHint"),
325
+ ...field,
326
+ ...state.deniedMessage,
327
+ onEdit: (text) => {
328
+ props.edit("deniedMessage", text);
329
+ },
330
+ onReset: () => {
331
+ props.resetField("deniedMessage");
332
+ }
333
+ }),
334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
335
+ id: "im-bridge-welcomeMessage",
336
+ label: t("welcomeMessage"),
337
+ hint: t("welcomeMessageHint"),
338
+ ...field,
339
+ ...state.welcomeMessage,
340
+ onEdit: (text) => {
341
+ props.edit("welcomeMessage", text);
342
+ },
343
+ onReset: () => {
344
+ props.resetField("welcomeMessage");
345
+ }
346
+ }),
347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
348
+ id: "im-bridge-provider",
349
+ label: t("provider"),
350
+ hint: t("providerHint"),
351
+ ...field,
352
+ ...state.provider,
353
+ onEdit: (text) => {
354
+ props.edit("provider", text);
355
+ },
356
+ onReset: () => {
357
+ props.resetField("provider");
358
+ }
359
+ }),
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ValueField, {
361
+ id: "im-bridge-model",
362
+ label: t("model"),
363
+ hint: t("modelHint"),
364
+ ...field,
365
+ ...state.model,
366
+ onEdit: (text) => {
367
+ props.edit("model", text);
368
+ },
369
+ onReset: () => {
370
+ props.resetField("model");
371
+ }
372
+ })
373
+ ]
374
+ });
375
+ }
376
+ //#endregion
377
+ //#region src/client/card-form.ts
378
+ /**
379
+ * Staged settings form owned by this plugin.
380
+ * Mirrors the Host Plugins section model without importing its chrome.
381
+ */
382
+ /** Whole-number field. Empty draft clears. */
383
+ function numberField(field) {
384
+ return {
385
+ field,
386
+ format: (value) => typeof value === "number" ? String(value) : "",
387
+ parse: (text) => {
388
+ const trimmed = text.trim();
389
+ if (trimmed === "") return { kind: "clear" };
390
+ const parsed = Number(trimmed);
391
+ return Number.isFinite(parsed) ? {
392
+ kind: "set",
393
+ value: parsed
394
+ } : void 0;
395
+ }
396
+ };
397
+ }
398
+ /** Free-text field. Empty draft clears. */
399
+ function textField(field) {
400
+ return {
401
+ field,
402
+ format: (value) => typeof value === "string" ? value : "",
403
+ parse: (text) => {
404
+ const trimmed = text.trim();
405
+ return trimmed === "" ? { kind: "clear" } : {
406
+ kind: "set",
407
+ value: trimmed
408
+ };
409
+ }
410
+ };
411
+ }
412
+ /** Comma-separated string list. Empty draft stores []. */
413
+ function csvField(field) {
414
+ return {
415
+ field,
416
+ format: (value) => Array.isArray(value) ? value.join(",") : "",
417
+ parse: (text) => {
418
+ return {
419
+ kind: "set",
420
+ value: text.split(",").map((item) => item.trim()).filter(Boolean)
421
+ };
422
+ }
423
+ };
424
+ }
425
+ /** Stages edits over one settings namespace and writes them on save. */
426
+ var CardForm = class {
427
+ scope;
428
+ specs;
429
+ secretSpecs;
430
+ staged = /* @__PURE__ */ new Map();
431
+ listeners = /* @__PURE__ */ new Set();
432
+ saving = false;
433
+ failed = false;
434
+ /**
435
+ * @param scope - bound settings scope for this card's namespace.
436
+ * @param specs - section fields this card edits.
437
+ * @param secrets - write-only controls; a blank draft is a no-op.
438
+ */
439
+ constructor(scope, specs, secrets = []) {
440
+ this.scope = scope;
441
+ this.specs = new Map(specs.map((spec) => [spec.field, spec]));
442
+ this.secretSpecs = new Map(secrets.map((spec) => [spec.field, spec]));
443
+ scope.subscribe(() => {
444
+ this.publish();
445
+ });
446
+ }
447
+ /** Publish a projection rebuilt whenever the scope or a draft changes. */
448
+ bind(project) {
449
+ const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
450
+ this.listeners.add(() => {
451
+ store.set(project());
452
+ });
453
+ return store;
454
+ }
455
+ /** Card-level state: what the Host serves and what a save would do. */
456
+ shell() {
457
+ const snapshot = this.scope.getSnapshot();
458
+ const plan = this.plan();
459
+ return {
460
+ available: snapshot.status === "ready",
461
+ writable: snapshot.writable,
462
+ dirty: plan.length > 0,
463
+ invalid: plan.some((item) => item.run === void 0),
464
+ saving: this.saving,
465
+ failed: this.failed
466
+ };
467
+ }
468
+ /** One control's staged text, override badge, and validity. */
469
+ field(field) {
470
+ const staged = this.staged.get(field);
471
+ if (this.secretSpecs.has(field)) return {
472
+ text: staged?.text ?? "",
473
+ overridden: false,
474
+ invalid: false
475
+ };
476
+ const spec = this.spec(field);
477
+ if (staged === void 0) return {
478
+ text: spec.format(this.sectionValue(field)),
479
+ overridden: this.stored(field),
480
+ invalid: false
481
+ };
482
+ const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
483
+ return {
484
+ text: staged.text,
485
+ overridden: write?.kind === "set",
486
+ invalid: write === void 0
487
+ };
488
+ }
489
+ /** Edit, reset, save, and discard actions bound to this form. */
490
+ actions() {
491
+ return {
492
+ edit: (field, text) => {
493
+ this.stage(field, {
494
+ text,
495
+ clear: false
496
+ });
497
+ },
498
+ resetField: (field) => {
499
+ this.stage(field, {
500
+ text: this.spec(field).format(this.baseValue(field)),
501
+ clear: true
502
+ });
503
+ },
504
+ save: () => {
505
+ this.save();
506
+ },
507
+ discard: () => {
508
+ if (this.staged.size === 0 && !this.failed) return;
509
+ this.staged.clear();
510
+ this.failed = false;
511
+ this.publish();
512
+ }
513
+ };
514
+ }
515
+ /** Write every staged edit, then re-seed from what the Host accepted. */
516
+ async save() {
517
+ const plan = this.plan();
518
+ const writes = plan.flatMap((item) => item.run === void 0 ? [] : [item.run]);
519
+ if (plan.length === 0 || this.saving || writes.length !== plan.length) return;
520
+ this.saving = true;
521
+ this.failed = false;
522
+ this.publish();
523
+ let landed = true;
524
+ for (const write of writes) landed = await write() && landed;
525
+ if (landed) this.staged.clear();
526
+ this.saving = false;
527
+ this.failed = !landed;
528
+ this.publish();
529
+ }
530
+ plan() {
531
+ const plan = [];
532
+ for (const [field, staged] of this.staged) {
533
+ const secret = this.secretSpecs.get(field);
534
+ if (secret !== void 0) {
535
+ const value = staged.text.trim();
536
+ if (value !== "") plan.push({
537
+ field,
538
+ run: () => secret.write(value)
539
+ });
540
+ continue;
541
+ }
542
+ const spec = this.spec(field);
543
+ if (staged.clear) {
544
+ if (this.stored(field)) plan.push({
545
+ field,
546
+ run: () => this.clear(field)
547
+ });
548
+ continue;
549
+ }
550
+ if (staged.text === spec.format(this.sectionValue(field))) continue;
551
+ const write = spec.parse(staged.text);
552
+ if (write === void 0) plan.push({
553
+ field,
554
+ run: void 0
555
+ });
556
+ else if (write.kind === "clear") plan.push({
557
+ field,
558
+ run: () => this.clear(field)
559
+ });
560
+ else plan.push({
561
+ field,
562
+ run: () => this.store(field, write.value)
563
+ });
564
+ }
565
+ return plan;
566
+ }
567
+ async clear(field) {
568
+ await this.scope.unset(field);
569
+ return !this.stored(field);
570
+ }
571
+ async store(field, value) {
572
+ await this.scope.set(field, value);
573
+ return this.userLayer()?.[field] === value || Array.isArray(value) && JSON.stringify(this.userLayer()?.[field]) === JSON.stringify(value);
574
+ }
575
+ stage(field, edit) {
576
+ this.staged.set(field, edit);
577
+ this.failed = false;
578
+ this.publish();
579
+ }
580
+ spec(field) {
581
+ const spec = this.specs.get(field);
582
+ if (spec === void 0) throw new Error(`im-bridge card has no field ${field}`);
583
+ return spec;
584
+ }
585
+ snapshotOf() {
586
+ return this.scope.getSnapshot();
587
+ }
588
+ sectionValue(field) {
589
+ return this.snapshotOf().value?.[field];
590
+ }
591
+ baseValue(field) {
592
+ return this.snapshotOf().base?.[field];
593
+ }
594
+ userLayer() {
595
+ return this.snapshotOf().user;
596
+ }
597
+ stored(field) {
598
+ const user = this.userLayer();
599
+ return user !== void 0 && Object.hasOwn(user, field);
600
+ }
601
+ publish() {
602
+ for (const listener of this.listeners) listener();
603
+ }
604
+ };
605
+ //#endregion
606
+ //#region src/client/card-controller.ts
607
+ /** Settings namespace paired with this card. */
608
+ const NS$1 = "im-bridge";
609
+ /** Bridges the `im-bridge` scope onto the staged card form. */
610
+ var WecomCardController = class {
611
+ scope;
612
+ describe;
613
+ form;
614
+ store;
615
+ /**
616
+ * @param scope - bound settings scope for the `im-bridge` namespace.
617
+ * @param describe - Host describe face; secret literals never ride it.
618
+ */
619
+ constructor(scope, describe) {
620
+ this.scope = scope;
621
+ this.describe = describe;
622
+ this.form = new CardForm(scope, [
623
+ csvField("allowFrom"),
624
+ numberField("agentTimeoutSec"),
625
+ textField("startHint"),
626
+ textField("deniedMessage"),
627
+ textField("welcomeMessage"),
628
+ textField("provider"),
629
+ textField("model")
630
+ ], [{
631
+ field: "botId",
632
+ write: (text) => this.writeSecret("botId", text)
633
+ }, {
634
+ field: "secret",
635
+ write: (text) => this.writeSecret("secret", text)
636
+ }]);
637
+ this.store = this.form.bind(() => this.projection());
638
+ }
639
+ projection() {
640
+ return {
641
+ ...this.form.shell(),
642
+ botId: this.form.field("botId"),
643
+ secret: this.form.field("secret"),
644
+ botIdConfigured: this.secretConfigured("botId"),
645
+ secretConfigured: this.secretConfigured("secret"),
646
+ allowFrom: this.form.field("allowFrom"),
647
+ agentTimeoutSec: this.form.field("agentTimeoutSec"),
648
+ startHint: this.form.field("startHint"),
649
+ deniedMessage: this.form.field("deniedMessage"),
650
+ welcomeMessage: this.form.field("welcomeMessage"),
651
+ provider: this.form.field("provider"),
652
+ model: this.form.field("model")
653
+ };
654
+ }
655
+ /**
656
+ * Whether the Host reports a configured value for one secret slot.
657
+ * @param field - `botId` or `secret`.
658
+ * @returns true when describe lists that slot as set.
659
+ */
660
+ secretConfigured(field) {
661
+ return (this.describe.getSnapshot().view?.namespaces.find((candidate) => candidate.ns === NS$1))?.secrets.some((slot) => slot.path[0] === field && slot.set) === true;
662
+ }
663
+ /**
664
+ * Write one credential into the user layer, then read configured state back.
665
+ * @param field - `botId` or `secret`.
666
+ * @param text - the staged literal.
667
+ * @returns whether describe reports the slot set afterwards.
668
+ */
669
+ async writeSecret(field, text) {
670
+ await this.scope.set(field, text);
671
+ return this.secretConfigured(field);
672
+ }
673
+ /** Face the slot registration injects. */
674
+ inject() {
675
+ return {
676
+ hooks: { wecomCard: this.store },
677
+ ...this.form.actions()
678
+ };
679
+ }
680
+ };
681
+ //#endregion
682
+ //#region src/client/locales.ts
683
+ /** English copy for the im-bridge card. */
684
+ const en = {
685
+ title: "WeCom Bridge",
686
+ description: "Credentials, allow-list, timeouts, and WeCom-only model overrides.",
687
+ unsaved: "Unsaved",
688
+ readOnly: "This document is read-only.",
689
+ saveFailed: "Save did not land. Correct the fields and try again.",
690
+ save: "Save",
691
+ saving: "Saving…",
692
+ discard: "Discard",
693
+ expand: "Show settings",
694
+ collapse: "Hide settings",
695
+ overridden: "Overridden",
696
+ reset: "Reset",
697
+ invalidNumber: "Enter a finite number.",
698
+ botId: "Bot ID",
699
+ secret: "Secret",
700
+ secretHint: "Leave blank to keep the stored value. Save, then restart the process to open the WebSocket.",
701
+ secretConfigured: "Configured",
702
+ secretUnset: "Not configured",
703
+ allowFrom: "Allowed sender userids",
704
+ allowFromHint: "Comma-separated. Empty allows everyone.",
705
+ agentTimeoutSec: "Task timeout (seconds)",
706
+ agentTimeoutSecHint: "Progress bar and remaining-time estimate.",
707
+ startHint: "Placeholder while thinking",
708
+ startHintHint: "First stream line after a message arrives.",
709
+ deniedMessage: "Denied-sender reply",
710
+ deniedMessageHint: "Sent when the userid is outside the allow-list.",
711
+ welcomeMessage: "Welcome message",
712
+ welcomeMessageHint: "Sent when a user opens the WeCom chat.",
713
+ provider: "WeCom-only provider",
714
+ providerHint: "Empty follows the GUI default model. Both provider and model must be set to override.",
715
+ model: "WeCom-only model",
716
+ modelHint: "Takes effect only together with provider."
717
+ };
718
+ /** Chinese copy for the im-bridge card. */
719
+ const zh = {
720
+ title: "企业微信桥接",
721
+ description: "凭证、白名单、超时和企微专用模型覆盖。",
722
+ unsaved: "未保存",
723
+ readOnly: "当前文档不可写。",
724
+ saveFailed: "保存未生效,请修正后重试。",
725
+ save: "保存",
726
+ saving: "保存中…",
727
+ discard: "放弃",
728
+ expand: "展开设置",
729
+ collapse: "收起设置",
730
+ overridden: "已覆盖",
731
+ reset: "重置",
732
+ invalidNumber: "请输入有效数字。",
733
+ botId: "Bot ID",
734
+ secret: "Secret",
735
+ secretHint: "留空保留已存值。保存后需重启进程才会连 WebSocket。",
736
+ secretConfigured: "已配置",
737
+ secretUnset: "未配置",
738
+ allowFrom: "允许的发送者 userid",
739
+ allowFromHint: "逗号分隔;空 = 允许所有人。",
740
+ agentTimeoutSec: "单任务超时(秒)",
741
+ agentTimeoutSecHint: "动画进度条和剩余估算的基准。",
742
+ startHint: "开始处理时的占位提示",
743
+ startHintHint: "收到消息后推送的第一条流式文案。",
744
+ deniedMessage: "非白名单拒绝文案",
745
+ deniedMessageHint: "发送者不在白名单时回复。",
746
+ welcomeMessage: "进入会话欢迎语",
747
+ welcomeMessageHint: "用户打开企微会话时发送。",
748
+ provider: "企微专用 provider",
749
+ providerHint: "空 = 跟随 GUI 默认模型。须与 model 同时填写才覆盖。",
750
+ model: "企微专用 model",
751
+ modelHint: "仅在同时填写 provider 时生效。"
752
+ };
753
+ //#endregion
754
+ //#region src/client/index.ts
755
+ /** Settings namespace shared with the Host half. */
756
+ const NS = "im-bridge";
757
+ /** Required browser services. */
758
+ const inject = [
759
+ "slots",
760
+ "locale",
761
+ "connection",
762
+ "remote",
763
+ "settingsScope"
764
+ ];
765
+ /**
766
+ * Register locale copy and the Plugins-tab card.
767
+ * @param ctx - browser plugin context.
768
+ */
769
+ function apply(ctx) {
770
+ const card = new WecomCardController(ctx.settingsScope.bind({ namespace: NS }), ctx.settingsScope.describe());
771
+ ctx.effect(() => ctx.locale.register(NS, {
772
+ zh,
773
+ en
774
+ }), "im-bridge: locale dicts");
775
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
776
+ name: "settings.plugin.item",
777
+ key: NS,
778
+ locale: NS,
779
+ inject: () => card.inject()
780
+ }, WecomCard));
781
+ }
782
+ //#endregion
783
+ exports.apply = apply;
784
+ exports.inject = inject;
785
+ return module.exports;
786
+ }
787
+ });
788
+
789
+ //# sourceMappingURL=client.js.map