@cxyhhhhh/dsh-qqbot 0.5.0-dev.202610101632 → 0.5.0-dev.202610101921
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/dist/commands/help.d.ts +1 -1
- package/dist/commands/help.js +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -8
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -21
- package/dist/index.js.map +1 -1
- package/dist/setup/bind-routes.d.ts +52 -9
- package/dist/setup/bind-routes.d.ts.map +1 -1
- package/dist/setup/bind-routes.js +237 -12
- package/dist/setup/bind-routes.js.map +1 -1
- package/dist/setup/index.d.ts +23 -4
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js +48 -4
- package/dist/setup/index.js.map +1 -1
- package/dist/setup/persist.d.ts +24 -0
- package/dist/setup/persist.d.ts.map +1 -1
- package/dist/setup/persist.js +84 -1
- package/dist/setup/persist.js.map +1 -1
- package/dist/setup/presenters.d.ts +1 -1
- package/dist/setup/presenters.js +1 -1
- package/dist/shared/bind-protocol.d.ts +42 -0
- package/dist/shared/bind-protocol.d.ts.map +1 -0
- package/dist/shared/bind-protocol.js +19 -0
- package/dist/shared/bind-protocol.js.map +1 -0
- package/dist/transport/reply-limiter.d.ts +1 -1
- package/dist/transport/reply-limiter.js +1 -1
- package/lib/client.js +732 -108
- package/lib/client.js.map +1 -1
- package/package.json +8 -1
- package/tsconfig.client.json +1 -1
package/lib/client.js
CHANGED
|
@@ -5,81 +5,148 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
+
//#region src/shared/bind-protocol.ts
|
|
9
|
+
/**
|
|
10
|
+
* 端内绑定协议 —— 宿主路由与客户端面板的共享契约
|
|
11
|
+
*
|
|
12
|
+
* 类型与路径常量的唯一事实源:宿主(setup/bind-routes.ts)与浏览器
|
|
13
|
+
* (client/bind-page.ts)各自 import,杜绝两端协议漂移。
|
|
14
|
+
* 本模块必须保持纯类型 + 纯常量(零 node 依赖、零副作用):
|
|
15
|
+
* 客户端 tsdown 构建会将其内联进 bundle。
|
|
16
|
+
*/
|
|
17
|
+
/** 状态查询(绑定页轮询) */
|
|
18
|
+
const BIND_STATE_PATH = "/qqbot/im-qqbot/bind";
|
|
19
|
+
/** 刷新二维码(绑定页按钮) */
|
|
20
|
+
const BIND_REFRESH_PATH = "/qqbot/im-qqbot/bind/refresh";
|
|
21
|
+
/** 手动填写/编辑凭据(绑定页表单) */
|
|
22
|
+
const BIND_MANUAL_PATH = "/qqbot/im-qqbot/bind/manual";
|
|
23
|
+
/** 解绑:移除已保存凭据,回到绑定流程 */
|
|
24
|
+
const BIND_UNBIND_PATH = "/qqbot/im-qqbot/bind/unbind";
|
|
25
|
+
/** 配置读写(设置面板表单) */
|
|
26
|
+
const BIND_CONFIG_PATH = "/qqbot/im-qqbot/config";
|
|
27
|
+
//#endregion
|
|
8
28
|
//#region src/client/bind-page.ts
|
|
9
29
|
/**
|
|
10
|
-
* 端内绑定面板 —
|
|
30
|
+
* 端内绑定面板 — 扫码 / 手动填写 / 已绑定编辑
|
|
31
|
+
*
|
|
32
|
+
* 宿主插件注册 /qqbot/im-qqbot/bind 路由(待绑定与已绑定 fiber 均常驻):
|
|
33
|
+
* - 未绑定:waiting(二维码)/ starting / saving / failed 相位 + manual 提交
|
|
34
|
+
* - 已绑定:bound 相位(回显脱敏 appId,secret 不回传)+ manual 更换 + 解绑
|
|
35
|
+
* 扫码成功或手动保存均写入 profile patch 并经 HMR 重载生效。
|
|
11
36
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
37
|
+
* 样式为注入式 CSS 类(imqb-* 命名空间,随面板挂载注入一次):
|
|
38
|
+
* hover/过渡/focus 样式无法用 inline style 表达,统一走类;
|
|
39
|
+
* 颜色尽量取 DSW 主题变量(--dsw-alias-*),缺省值兜底。
|
|
15
40
|
*
|
|
16
41
|
* 同一组件复用于两种展示位(侧栏绑定页 / 设置页 QQ Bot 栏目)。
|
|
17
42
|
* 运行时仅依赖 react(dsh 客户端平台种子表提供);宿主通信走同源 fetch,
|
|
18
43
|
* Desktop 下由壳的 IPC 桥自动附加 Host 认证转发。
|
|
19
44
|
*/
|
|
20
|
-
/** 状态查询路由(宿主 bind-routes 注册) */
|
|
21
|
-
const BIND_STATE_URL = "/qqbot/im-qqbot/bind";
|
|
22
|
-
/** 刷新路由 */
|
|
23
|
-
const BIND_REFRESH_URL = "/qqbot/im-qqbot/bind/refresh";
|
|
24
45
|
/** 轮询间隔(毫秒):活跃轮询 / 失败退避 */
|
|
25
46
|
const ACTIVE_POLL_MS = 2e3;
|
|
26
47
|
const RETRY_POLL_MS = 3e3;
|
|
27
|
-
/**
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
/** 样式注入标记(幂等) */
|
|
49
|
+
const STYLE_ID$1 = "im-qqbot-bind-panel-styles";
|
|
50
|
+
/**
|
|
51
|
+
* 注入面板样式(一次)。交互态(hover/active/focus)无法用 inline style
|
|
52
|
+
* 表达,统一收敛到类;DSW 主题变量缺失时用中性值兜底。
|
|
53
|
+
*/
|
|
54
|
+
function usePanelStyles() {
|
|
55
|
+
(0, react.useEffect)(() => {
|
|
56
|
+
if (typeof document === "undefined") return;
|
|
57
|
+
if (document.getElementById(STYLE_ID$1) !== null) return;
|
|
58
|
+
const style = document.createElement("style");
|
|
59
|
+
style.id = STYLE_ID$1;
|
|
60
|
+
style.textContent = `
|
|
61
|
+
.imqb-panel {
|
|
62
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
63
|
+
gap: 14px; padding: 24px; min-height: 320px; text-align: center; font-size: 14px;
|
|
64
|
+
}
|
|
65
|
+
.imqb-title { margin: 0; font-size: 16px; font-weight: 600; }
|
|
66
|
+
.imqb-hint { margin: 0; opacity: 0.75; line-height: 1.6; }
|
|
67
|
+
.imqb-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
|
|
68
|
+
.imqb-link { font-size: 13px; opacity: 0.85; color: var(--dsw-alias-brand, #4d6bfe); text-decoration: none; }
|
|
69
|
+
.imqb-link:hover { text-decoration: underline; }
|
|
70
|
+
.imqb-icon { font-size: 32px; line-height: 1; }
|
|
71
|
+
.imqb-qr { border-radius: 10px; background: #fff; padding: 10px; max-width: 100%; }
|
|
72
|
+
.imqb-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
|
|
73
|
+
.imqb-btn, .imqb-pill {
|
|
74
|
+
font: inherit; cursor: pointer; color: inherit;
|
|
75
|
+
transition: background-color .15s ease, border-color .15s ease, color .15s ease, opacity .15s ease;
|
|
76
|
+
}
|
|
77
|
+
.imqb-btn:disabled, .imqb-pill:disabled { opacity: 0.5; cursor: default; }
|
|
78
|
+
.imqb-btn {
|
|
79
|
+
padding: 7px 18px; border-radius: 8px; font-size: 13px;
|
|
80
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));
|
|
81
|
+
background: transparent;
|
|
82
|
+
}
|
|
83
|
+
.imqb-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.12)); }
|
|
84
|
+
.imqb-btn--primary {
|
|
85
|
+
border-color: transparent;
|
|
86
|
+
background: var(--dsw-alias-brand, #4d6bfe); color: #fff; font-weight: 500;
|
|
87
|
+
}
|
|
88
|
+
.imqb-btn--primary:hover:not(:disabled) { background: var(--dsw-alias-brand-strong, #3b5cf0); }
|
|
89
|
+
.imqb-btn--danger { color: var(--dsw-alias-danger, #f26d6d); border-color: var(--dsw-alias-danger, rgba(242, 109, 109, 0.5)); }
|
|
90
|
+
.imqb-btn--danger:hover:not(:disabled) { background: var(--dsw-alias-danger, rgba(242, 109, 109, 0.12)); }
|
|
91
|
+
.imqb-ghost {
|
|
92
|
+
border: none; background: transparent; opacity: 0.65; padding: 4px 8px;
|
|
93
|
+
font: inherit; font-size: 13px; cursor: pointer; color: inherit;
|
|
94
|
+
}
|
|
95
|
+
.imqb-ghost:hover { opacity: 1; text-decoration: underline; }
|
|
96
|
+
.imqb-pill {
|
|
97
|
+
padding: 5px 16px; border-radius: 999px; font-size: 13px;
|
|
98
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4)); background: transparent;
|
|
99
|
+
}
|
|
100
|
+
.imqb-pill:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.12)); }
|
|
101
|
+
.imqb-btn:focus-visible, .imqb-pill:focus-visible, .imqb-input:focus-visible, .imqb-ghost:focus-visible {
|
|
102
|
+
outline: 2px solid var(--dsw-alias-brand, #4d6bfe); outline-offset: 1px;
|
|
103
|
+
}
|
|
104
|
+
.imqb-form { display: flex; flex-direction: column; gap: 12px; width: min(320px, 100%); box-sizing: border-box; }
|
|
105
|
+
.imqb-field { display: flex; flex-direction: column; gap: 5px; text-align: left; font-size: 13px; }
|
|
106
|
+
.imqb-input {
|
|
107
|
+
padding: 8px 10px; border-radius: 8px; box-sizing: border-box; width: 100%;
|
|
108
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));
|
|
109
|
+
background: var(--dsw-alias-bg-input, rgba(128, 128, 128, 0.08));
|
|
110
|
+
color: inherit; font: inherit; font-size: 13px; outline: none;
|
|
111
|
+
}
|
|
112
|
+
.imqb-input:focus { border-color: var(--dsw-alias-brand, #4d6bfe); }
|
|
113
|
+
.imqb-error { margin: 0; font-size: 12px; color: var(--dsw-alias-danger, #f26d6d); }
|
|
114
|
+
/* 紧凑状态视图(已绑定 / 过渡态):设置行风格,无居中大卡留白 */
|
|
115
|
+
.imqb-status { display: flex; flex-direction: column; text-align: left; font-size: 14px; }
|
|
116
|
+
.imqb-status-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; }
|
|
117
|
+
.imqb-status-icon { font-size: 24px; line-height: 1; flex: none; }
|
|
118
|
+
.imqb-qqicon {
|
|
119
|
+
flex: none; display: block; border-radius: 8px;
|
|
120
|
+
background: #fff; /* 深色企鹅 logo 需浅底,暗色主题下保持可见 */
|
|
121
|
+
}
|
|
122
|
+
.imqb-status-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
|
|
123
|
+
.imqb-status-title { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.5; }
|
|
124
|
+
.imqb-status-sub { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary, inherit); opacity: .85; }
|
|
125
|
+
.imqb-status-actions { display: flex; align-items: center; gap: 8px; padding: 4px 0 12px; }
|
|
126
|
+
`;
|
|
127
|
+
document.head.appendChild(style);
|
|
128
|
+
return () => {
|
|
129
|
+
style.remove();
|
|
130
|
+
};
|
|
131
|
+
}, []);
|
|
132
|
+
}
|
|
72
133
|
/**
|
|
73
134
|
* 绑定状态轮询 hook。
|
|
74
135
|
*
|
|
136
|
+
* 404 语义分两种:从未拿到过路由 = 插件未启用(终态);
|
|
137
|
+
* 曾经拿到过 = HMR 重载窗口期(manual/unbind 保存后路由短暂消失),
|
|
138
|
+
* 低频等待恢复而不是判定终态——否则保存成功提示会被
|
|
139
|
+
* 「插件未启用」覆盖且无法自愈。
|
|
140
|
+
*
|
|
75
141
|
* @returns 轮询快照:state 为宿主相位(null = 加载中),gone 表示
|
|
76
|
-
*
|
|
142
|
+
* 插件未启用(从未注册过路由)。
|
|
77
143
|
*/
|
|
78
144
|
function useBindState() {
|
|
79
145
|
const [state, setState] = (0, react.useState)(null);
|
|
80
146
|
const [gone, setGone] = (0, react.useState)(false);
|
|
81
147
|
(0, react.useEffect)(() => {
|
|
82
148
|
let stopped = false;
|
|
149
|
+
let sawRoute = false;
|
|
83
150
|
let timer;
|
|
84
151
|
const schedule = (ms) => {
|
|
85
152
|
timer = setTimeout(() => {
|
|
@@ -89,19 +156,21 @@ window.__ModuleLoader__.load({
|
|
|
89
156
|
const tick = async () => {
|
|
90
157
|
if (stopped) return;
|
|
91
158
|
try {
|
|
92
|
-
const res = await fetch(
|
|
159
|
+
const res = await fetch(BIND_STATE_PATH, { cache: "no-store" });
|
|
93
160
|
if (res.status === 404) {
|
|
94
|
-
if (!stopped) setGone(true);
|
|
161
|
+
if (!stopped && !sawRoute) setGone(true);
|
|
162
|
+
if (!stopped && sawRoute) schedule(1e4);
|
|
95
163
|
return;
|
|
96
164
|
}
|
|
97
165
|
if (!res.ok) {
|
|
98
166
|
schedule(RETRY_POLL_MS);
|
|
99
167
|
return;
|
|
100
168
|
}
|
|
169
|
+
sawRoute = true;
|
|
101
170
|
const body = await res.json();
|
|
102
171
|
if (stopped) return;
|
|
103
172
|
setState(body);
|
|
104
|
-
schedule(body.phase === "saving" ? 500 : ACTIVE_POLL_MS);
|
|
173
|
+
schedule(body.phase === "saving" ? 500 : body.phase === "bound" ? 3e4 : ACTIVE_POLL_MS);
|
|
105
174
|
return;
|
|
106
175
|
} catch {}
|
|
107
176
|
schedule(RETRY_POLL_MS);
|
|
@@ -117,42 +186,610 @@ window.__ModuleLoader__.load({
|
|
|
117
186
|
gone
|
|
118
187
|
};
|
|
119
188
|
}
|
|
120
|
-
/**
|
|
121
|
-
async function
|
|
189
|
+
/** 手动提交凭据(填写 / 更换共用) */
|
|
190
|
+
async function submitManualCredentials(appId, appSecret) {
|
|
122
191
|
try {
|
|
123
|
-
await fetch(
|
|
192
|
+
const res = await fetch(BIND_MANUAL_PATH, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "content-type": "application/json" },
|
|
195
|
+
body: JSON.stringify({
|
|
196
|
+
appId,
|
|
197
|
+
appSecret
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
const body = await res.json().catch(() => ({}));
|
|
201
|
+
if (res.ok && body.ok) return {
|
|
202
|
+
ok: true,
|
|
203
|
+
message: body.message
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
error: body.error ?? `保存失败(HTTP ${res.status})`
|
|
208
|
+
};
|
|
124
209
|
} catch (err) {
|
|
125
|
-
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
error: err instanceof Error ? err.message : String(err)
|
|
213
|
+
};
|
|
126
214
|
}
|
|
127
215
|
}
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
216
|
+
/**
|
|
217
|
+
* 手动凭据表单(填写 / 更换共用)。
|
|
218
|
+
*
|
|
219
|
+
* appSecret 永不回显(占位符提示),提交成功后展示保存结果;
|
|
220
|
+
* 宿主写入 profile patch 并经 HMR 重载,状态轮询随后收敛为 bound。
|
|
221
|
+
*/
|
|
222
|
+
function ManualForm({ initialAppId }) {
|
|
223
|
+
const [appId, setAppId] = (0, react.useState)(initialAppId ?? "");
|
|
224
|
+
const [appSecret, setAppSecret] = (0, react.useState)("");
|
|
225
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
226
|
+
const [result, setResult] = (0, react.useState)(null);
|
|
227
|
+
const submit = (0, react.useCallback)((event) => {
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
if (busy) return;
|
|
230
|
+
const trimmedId = appId.trim();
|
|
231
|
+
const trimmedSecret = appSecret.trim();
|
|
232
|
+
if (trimmedId.length === 0 || trimmedSecret.length === 0) {
|
|
233
|
+
setResult({
|
|
234
|
+
ok: false,
|
|
235
|
+
error: "appId 与 appSecret 均为必填"
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setBusy(true);
|
|
240
|
+
submitManualCredentials(trimmedId, trimmedSecret).then((outcome) => {
|
|
241
|
+
setResult(outcome);
|
|
242
|
+
setBusy(false);
|
|
243
|
+
});
|
|
244
|
+
}, [
|
|
245
|
+
appId,
|
|
246
|
+
appSecret,
|
|
247
|
+
busy
|
|
248
|
+
]);
|
|
249
|
+
if (result !== null && result.ok) return (0, react.createElement)("div", { className: "imqb-status" }, (0, react.createElement)("div", { className: "imqb-status-row" }, (0, react.createElement)("div", { className: "imqb-status-icon" }, "✅"), (0, react.createElement)("div", { className: "imqb-status-meta" }, (0, react.createElement)("p", { className: "imqb-status-title" }, result.message ?? "凭据已保存"), (0, react.createElement)("p", { className: "imqb-status-sub" }, "插件将自动重载;稍后即可开始对话"))));
|
|
250
|
+
return (0, react.createElement)("form", {
|
|
251
|
+
className: "imqb-form",
|
|
252
|
+
onSubmit: submit
|
|
253
|
+
}, (0, react.createElement)("label", { className: "imqb-field" }, "AppID", (0, react.createElement)("input", {
|
|
254
|
+
className: "imqb-input",
|
|
255
|
+
value: appId,
|
|
256
|
+
placeholder: "QQ 机器人 AppID",
|
|
257
|
+
onChange: (event) => {
|
|
258
|
+
setAppId(event.currentTarget.value);
|
|
259
|
+
},
|
|
260
|
+
autoComplete: "off",
|
|
261
|
+
spellCheck: false
|
|
262
|
+
})), (0, react.createElement)("label", { className: "imqb-field" }, "AppSecret", (0, react.createElement)("input", {
|
|
263
|
+
className: "imqb-input",
|
|
264
|
+
type: "password",
|
|
265
|
+
value: appSecret,
|
|
266
|
+
placeholder: initialAppId === void 0 ? "QQ 机器人 AppSecret" : "输入新的 AppSecret(不回显)",
|
|
267
|
+
onChange: (event) => {
|
|
268
|
+
setAppSecret(event.currentTarget.value);
|
|
269
|
+
},
|
|
270
|
+
autoComplete: "new-password"
|
|
271
|
+
})), result !== null && !result.ok ? (0, react.createElement)("p", { className: "imqb-error" }, result.error) : null, (0, react.createElement)("button", {
|
|
272
|
+
className: "imqb-btn imqb-btn--primary",
|
|
273
|
+
type: "submit",
|
|
274
|
+
disabled: busy
|
|
275
|
+
}, busy ? "正在保存…" : "保存"));
|
|
276
|
+
}
|
|
277
|
+
/** QQ 企鹅 logo(256×256 PNG data URI;深色图案配白底圆角,明暗主题下均可见) */
|
|
278
|
+
const QQ_BOT_ICON_PNG = "data:image/png;base64,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";
|
|
279
|
+
/** QQ 机器人图标(logo 图片,白底圆角容器内呈现) */
|
|
280
|
+
function QqBotIcon({ size = 28 }) {
|
|
281
|
+
return (0, react.createElement)("img", {
|
|
282
|
+
src: QQ_BOT_ICON_PNG,
|
|
283
|
+
alt: "QQ",
|
|
284
|
+
width: size,
|
|
285
|
+
height: size,
|
|
286
|
+
className: "imqb-qqicon"
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/** 已绑定视图:回显 appId + 更换凭据 / 解绑入口 */
|
|
290
|
+
function BoundView({ appId }) {
|
|
291
|
+
const [editing, setEditing] = (0, react.useState)(false);
|
|
292
|
+
const [unbindResult, setUnbindResult] = (0, react.useState)(null);
|
|
293
|
+
const unbind = (0, react.useCallback)(() => {
|
|
294
|
+
if (!window.confirm("确定解绑吗?\n解绑后机器人立即下线,插件将重新进入扫码绑定流程。")) return;
|
|
295
|
+
fetch(BIND_UNBIND_PATH, { method: "POST" }).then(async (res) => {
|
|
296
|
+
const body = await res.json().catch(() => ({}));
|
|
297
|
+
if (res.ok && body.ok === true) {
|
|
298
|
+
setUnbindResult({
|
|
299
|
+
ok: true,
|
|
300
|
+
message: body.message ?? "已解绑,插件将重载并重新进入绑定流程"
|
|
301
|
+
});
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
setUnbindResult({
|
|
305
|
+
ok: false,
|
|
306
|
+
message: body.error ?? `解绑失败(HTTP ${res.status})`
|
|
307
|
+
});
|
|
308
|
+
}).catch((err) => {
|
|
309
|
+
setUnbindResult({
|
|
310
|
+
ok: false,
|
|
311
|
+
message: err instanceof Error ? err.message : String(err)
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
}, []);
|
|
315
|
+
if (unbindResult !== null) return (0, react.createElement)("div", { className: "imqb-status" }, (0, react.createElement)("div", { className: "imqb-status-row" }, (0, react.createElement)("div", { className: "imqb-status-icon" }, unbindResult.ok ? "👋" : "⚠️"), (0, react.createElement)("div", { className: "imqb-status-meta" }, (0, react.createElement)("p", { className: "imqb-status-title" }, unbindResult.message))));
|
|
316
|
+
if (editing) return (0, react.createElement)("div", { className: "imqb-status" }, (0, react.createElement)("div", { className: "imqb-status-row" }, (0, react.createElement)("div", { className: "imqb-status-meta" }, (0, react.createElement)("p", { className: "imqb-status-title" }, "更换凭据")), (0, react.createElement)("button", {
|
|
317
|
+
className: "imqb-ghost",
|
|
318
|
+
onClick: () => {
|
|
319
|
+
setEditing(false);
|
|
320
|
+
}
|
|
321
|
+
}, "返回")), (0, react.createElement)(ManualForm, { initialAppId: appId }));
|
|
322
|
+
return (0, react.createElement)("div", { className: "imqb-status" }, (0, react.createElement)("div", { className: "imqb-status-row" }, (0, react.createElement)(QqBotIcon, { size: 28 }), (0, react.createElement)("div", { className: "imqb-status-meta" }, (0, react.createElement)("p", { className: "imqb-status-title" }, "已绑定"), (0, react.createElement)("p", { className: "imqb-status-sub imqb-mono" }, `AppID ${appId} · 私聊与群聊已接入`))), (0, react.createElement)("div", { className: "imqb-status-actions" }, (0, react.createElement)("button", {
|
|
323
|
+
className: "imqb-btn imqb-btn--primary",
|
|
324
|
+
onClick: () => {
|
|
325
|
+
setEditing(true);
|
|
326
|
+
}
|
|
327
|
+
}, "更换凭据"), (0, react.createElement)("button", {
|
|
328
|
+
className: "imqb-btn imqb-btn--danger",
|
|
329
|
+
onClick: unbind
|
|
330
|
+
}, "解绑")));
|
|
331
|
+
}
|
|
332
|
+
/** 未绑定视图:扫码 / 手动填写切换 */
|
|
333
|
+
function UnboundView({ state }) {
|
|
334
|
+
const [mode, setMode] = (0, react.useState)("qr");
|
|
131
335
|
const refresh = (0, react.useCallback)(() => {
|
|
132
|
-
|
|
336
|
+
fetch(BIND_REFRESH_PATH, { method: "POST" }).catch(() => {});
|
|
133
337
|
}, []);
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}, "
|
|
141
|
-
return (0, react.createElement)("div", { style: CONTAINER }, state.qrSvg === void 0 ? null : (0, react.createElement)("img", {
|
|
338
|
+
if (mode === "manual") return (0, react.createElement)("div", { className: "imqb-panel" }, (0, react.createElement)("p", { className: "imqb-title" }, "手动填写凭据"), (0, react.createElement)(ManualForm, {}), (0, react.createElement)("button", {
|
|
339
|
+
className: "imqb-ghost",
|
|
340
|
+
onClick: () => {
|
|
341
|
+
setMode("qr");
|
|
342
|
+
}
|
|
343
|
+
}, "改用扫码绑定"));
|
|
344
|
+
return (0, react.createElement)("div", { className: "imqb-panel" }, state.qrSvg === void 0 ? null : (0, react.createElement)("img", {
|
|
142
345
|
src: state.qrSvg,
|
|
143
346
|
alt: "QQ 扫码二维码",
|
|
144
347
|
width: 220,
|
|
145
348
|
height: 220,
|
|
146
|
-
|
|
147
|
-
}), (0, react.createElement)("p", {
|
|
349
|
+
className: "imqb-qr"
|
|
350
|
+
}), (0, react.createElement)("p", { className: "imqb-title" }, "扫码绑定 QQ 机器人"), (0, react.createElement)("p", { className: "imqb-hint" }, "使用手机 QQ 扫描二维码完成绑定"), (0, react.createElement)("a", {
|
|
148
351
|
href: state.url,
|
|
149
352
|
target: "_blank",
|
|
150
353
|
rel: "noreferrer noopener",
|
|
151
|
-
|
|
152
|
-
}, "
|
|
153
|
-
|
|
354
|
+
className: "imqb-link"
|
|
355
|
+
}, "二维码显示异常?在浏览器中打开"), (0, react.createElement)("div", { className: "imqb-row" }, (0, react.createElement)("button", {
|
|
356
|
+
className: "imqb-pill",
|
|
154
357
|
onClick: refresh
|
|
155
|
-
}, "刷新二维码"))
|
|
358
|
+
}, "刷新二维码"), (0, react.createElement)("button", {
|
|
359
|
+
className: "imqb-pill",
|
|
360
|
+
onClick: () => {
|
|
361
|
+
setMode("manual");
|
|
362
|
+
}
|
|
363
|
+
}, "手动填写")));
|
|
364
|
+
}
|
|
365
|
+
/** 依据宿主相位渲染面板主体(各展示位共用) */
|
|
366
|
+
function BindPanelBody() {
|
|
367
|
+
usePanelStyles();
|
|
368
|
+
const { state, gone } = useBindState();
|
|
369
|
+
if (gone) return (0, react.createElement)("div", { className: "imqb-panel" }, (0, react.createElement)("div", { className: "imqb-icon" }, "❔"), (0, react.createElement)("p", { className: "imqb-hint" }, "插件未启用,或当前版本不支持端内配置"));
|
|
370
|
+
if (state === null || state.phase === "starting") return (0, react.createElement)("div", { className: "imqb-panel" }, (0, react.createElement)("p", { className: "imqb-hint" }, "正在获取绑定状态…"));
|
|
371
|
+
if (state.phase === "saving") return (0, react.createElement)("div", { className: "imqb-status" }, (0, react.createElement)("div", { className: "imqb-status-row" }, (0, react.createElement)("div", { className: "imqb-status-icon" }, "🎉"), (0, react.createElement)("div", { className: "imqb-status-meta" }, (0, react.createElement)("p", { className: "imqb-status-title" }, "扫码成功"), (0, react.createElement)("p", { className: "imqb-status-sub" }, "凭据已保存,插件正在重载,稍后即可开始对话"))));
|
|
372
|
+
if (state.phase === "failed") return (0, react.createElement)("div", { className: "imqb-panel" }, (0, react.createElement)("div", { className: "imqb-icon" }, "⚠️"), (0, react.createElement)("p", { className: "imqb-title" }, "绑定失败"), (0, react.createElement)("p", { className: "imqb-hint" }, state.reason), (0, react.createElement)("div", { className: "imqb-row" }, (0, react.createElement)("button", {
|
|
373
|
+
className: "imqb-btn imqb-btn--primary",
|
|
374
|
+
onClick: () => {
|
|
375
|
+
window.location.reload();
|
|
376
|
+
}
|
|
377
|
+
}, "重试")));
|
|
378
|
+
if (state.phase === "bound") return (0, react.createElement)(BoundView, { appId: state.appId });
|
|
379
|
+
return (0, react.createElement)(UnboundView, { state });
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/client/config-panel.ts
|
|
383
|
+
/**
|
|
384
|
+
* QQ 机器人配置面板 — 必要配置的 UI 编辑
|
|
385
|
+
*
|
|
386
|
+
* UI 复刻 DSW 通用设置(settings-form 原语)的视觉风格:
|
|
387
|
+
* 左对齐设置列、0.5px 行分隔、label 13px/500、控件右置;
|
|
388
|
+
* Switch 为 36×20 aria-checked 结构(thumb translateX 动画);
|
|
389
|
+
* 三值选择用分段控件(滑动指示器按段数/索引的 CSS 变量定位);
|
|
390
|
+
* 保存按钮为 DSW 反色主按钮(label-primary 底 + bg-layer-3 字)。
|
|
391
|
+
*
|
|
392
|
+
* 数据流:GET /qqbot/im-qqbot/config 回显当前生效配置(appSecret
|
|
393
|
+
* 已由宿主剔除)→ 表单编辑 → dirty diff → POST 仅提交变更键 →
|
|
394
|
+
* 宿主白名单 + Schema 校验 → 最小写入 profile patch → HMR 重载生效。
|
|
395
|
+
*
|
|
396
|
+
* 字段策略:只开放用户必要的配置;cwd 只读展示;超时与性能调优
|
|
397
|
+
* 类字段走 profile patch / 环境变量。
|
|
398
|
+
*/
|
|
399
|
+
/** 样式注入标记(幂等) */
|
|
400
|
+
const STYLE_ID = "im-qqbot-config-panel-styles";
|
|
401
|
+
/** DSW 通用设置风格(settings-form / Switch / SegmentedControl 原语复刻) */
|
|
402
|
+
function useConfigPanelStyles() {
|
|
403
|
+
(0, react.useEffect)(() => {
|
|
404
|
+
if (typeof document === "undefined") return;
|
|
405
|
+
if (document.getElementById(STYLE_ID) !== null) return;
|
|
406
|
+
const style = document.createElement("style");
|
|
407
|
+
style.id = STYLE_ID;
|
|
408
|
+
style.textContent = `
|
|
409
|
+
.imqb2-form { display: flex; flex-direction: column; width: 100%; }
|
|
410
|
+
.imqb2-group-title {
|
|
411
|
+
padding: 14px 0 2px; font-size: 12px; font-weight: 600; line-height: 18px;
|
|
412
|
+
color: var(--dsw-alias-label-tertiary); text-transform: uppercase; letter-spacing: .04em;
|
|
413
|
+
}
|
|
414
|
+
.imqb2-field { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; }
|
|
415
|
+
.imqb2-field + .imqb2-field { border-top: 0.5px solid var(--dsw-alias-border-l2); }
|
|
416
|
+
.imqb2-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }
|
|
417
|
+
.imqb2-label {
|
|
418
|
+
flex: 1; min-width: 0; font-size: 13px; font-weight: 500; line-height: 1.5;
|
|
419
|
+
color: var(--dsw-alias-label-primary);
|
|
420
|
+
}
|
|
421
|
+
.imqb2-help { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
422
|
+
.imqb2-hint { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
|
|
423
|
+
/* Switch(aria-checked 驱动,视觉态与无障碍态同源) */
|
|
424
|
+
.imqb2-switch {
|
|
425
|
+
box-sizing: border-box; position: relative; flex: 0 0 auto;
|
|
426
|
+
width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 999px;
|
|
427
|
+
background: var(--dsw-alias-border-l3); cursor: pointer; font: inherit;
|
|
428
|
+
}
|
|
429
|
+
.imqb2-switch[aria-checked='true'] { background: var(--dsw-alias-brand-primary); }
|
|
430
|
+
.imqb2-switch:disabled { cursor: default; opacity: 0.5; }
|
|
431
|
+
.imqb2-switch:focus-visible {
|
|
432
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
433
|
+
outline-offset: 2px;
|
|
434
|
+
}
|
|
435
|
+
.imqb2-switch-thumb {
|
|
436
|
+
display: block; width: 16px; height: 16px; border-radius: 50%;
|
|
437
|
+
background: var(--dsw-alias-switch-thumb, #ffffff);
|
|
438
|
+
transition: transform 120ms ease;
|
|
439
|
+
}
|
|
440
|
+
.imqb2-switch[aria-checked='true'] .imqb2-switch-thumb {
|
|
441
|
+
background: var(--dsw-alias-label-primary-foreground, #ffffff);
|
|
442
|
+
transform: translateX(16px);
|
|
443
|
+
}
|
|
444
|
+
/* 输入(官方 fields.input 复刻) */
|
|
445
|
+
.imqb2-input {
|
|
446
|
+
box-sizing: border-box; width: 100%; height: 34px; padding: 0 12px;
|
|
447
|
+
border: 0.5px solid var(--dsw-alias-border-l4); border-radius: var(--dsw-radius-md, 8px);
|
|
448
|
+
background: var(--dsw-alias-bg-layer-3); font: inherit; font-size: 13px; line-height: 1.5;
|
|
449
|
+
color: var(--dsw-alias-label-primary); outline: none;
|
|
450
|
+
}
|
|
451
|
+
.imqb2-input:focus { outline: none; border-color: var(--dsw-alias-state-business-primary); }
|
|
452
|
+
.imqb2-input:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
453
|
+
.imqb2-textarea { height: auto; min-height: 68px; padding: 8px 12px; resize: vertical; line-height: 1.5; }
|
|
454
|
+
/* 分段控件(官方 SegmentedControl 复刻:指示器按段数/索引定位) */
|
|
455
|
+
.imqb2-seg {
|
|
456
|
+
position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;
|
|
457
|
+
gap: 2px; padding: 4px; border-radius: var(--dsw-radius-md, 8px);
|
|
458
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
459
|
+
}
|
|
460
|
+
.imqb2-seg-indicator {
|
|
461
|
+
position: absolute; top: 4px; left: 4px;
|
|
462
|
+
width: calc((100% - 8px - 2px * (var(--imqb2-seg-count) - 1)) / var(--imqb2-seg-count));
|
|
463
|
+
height: calc(100% - 8px);
|
|
464
|
+
border: 0; border-radius: var(--dsw-radius-sm, 6px);
|
|
465
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
466
|
+
box-shadow: var(--dsw-elevation-soft, 0 1px 3px rgba(0,0,0,.08));
|
|
467
|
+
transform: translateX(calc(var(--imqb2-seg-index) * (100% + 2px)));
|
|
468
|
+
transition: transform 160ms ease;
|
|
469
|
+
pointer-events: none;
|
|
470
|
+
}
|
|
471
|
+
.imqb2-seg-tab {
|
|
472
|
+
box-sizing: border-box; position: relative; z-index: 1; height: 28px; padding: 0 16px;
|
|
473
|
+
border: 0; border-radius: var(--dsw-radius-sm, 6px); background: transparent;
|
|
474
|
+
color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px;
|
|
475
|
+
font-weight: 500; white-space: nowrap; cursor: pointer; transition: color 120ms ease;
|
|
476
|
+
}
|
|
477
|
+
.imqb2-seg-tab:hover, .imqb2-seg-tab[aria-selected='true'] { color: var(--dsw-alias-label-primary); }
|
|
478
|
+
.imqb2-seg-tab:focus-visible {
|
|
479
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
480
|
+
outline-offset: -2px;
|
|
481
|
+
}
|
|
482
|
+
/* 底栏(官方 SettingsForm.footer 复刻) */
|
|
483
|
+
.imqb2-footer { display: flex; align-items: center; gap: 8px; padding-top: 16px; }
|
|
484
|
+
.imqb2-note { flex: 1; min-width: 0; margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }
|
|
485
|
+
.imqb2-note--error { color: var(--dsw-alias-state-error-primary, var(--dsw-alias-label-error)); }
|
|
486
|
+
.imqb2-note--ok { color: var(--dsw-alias-label-secondary); }
|
|
487
|
+
.imqb2-save {
|
|
488
|
+
appearance: none; flex: none; border: 1px solid transparent;
|
|
489
|
+
border-radius: var(--dsw-radius-md, 8px); padding: 5px 14px;
|
|
490
|
+
font: inherit; font-size: 13px; line-height: 1.5; cursor: pointer;
|
|
491
|
+
background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3);
|
|
492
|
+
}
|
|
493
|
+
.imqb2-save:disabled { opacity: 0.4; cursor: default; }
|
|
494
|
+
.imqb2-save:focus-visible {
|
|
495
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
496
|
+
outline-offset: 1px;
|
|
497
|
+
}
|
|
498
|
+
/* cwd 只读行 */
|
|
499
|
+
.imqb2-readonly { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--dsw-alias-label-secondary); word-break: break-all; margin: 0; }
|
|
500
|
+
`;
|
|
501
|
+
document.head.appendChild(style);
|
|
502
|
+
return () => {
|
|
503
|
+
style.remove();
|
|
504
|
+
};
|
|
505
|
+
}, []);
|
|
506
|
+
}
|
|
507
|
+
/** 开关(官方 Switch 原语结构:aria-checked 驱动视觉态) */
|
|
508
|
+
function Switch({ checked, label, onChange }) {
|
|
509
|
+
return (0, react.createElement)("button", {
|
|
510
|
+
type: "button",
|
|
511
|
+
role: "switch",
|
|
512
|
+
"aria-checked": checked,
|
|
513
|
+
"aria-label": label,
|
|
514
|
+
className: "imqb2-switch",
|
|
515
|
+
onClick: () => {
|
|
516
|
+
onChange(!checked);
|
|
517
|
+
}
|
|
518
|
+
}, (0, react.createElement)("span", { className: "imqb2-switch-thumb" }));
|
|
519
|
+
}
|
|
520
|
+
/** 访问模式分段选择(官方 SegmentedControl 结构:CSS 变量定位滑动指示器) */
|
|
521
|
+
function Segmented({ value, options, onChange, label }) {
|
|
522
|
+
const index = Math.max(0, options.findIndex((o) => o.value === value));
|
|
523
|
+
const trackStyle = {
|
|
524
|
+
"--imqb2-seg-count": options.length,
|
|
525
|
+
"--imqb2-seg-index": index
|
|
526
|
+
};
|
|
527
|
+
return (0, react.createElement)("div", {
|
|
528
|
+
className: "imqb2-seg",
|
|
529
|
+
role: "tablist",
|
|
530
|
+
"aria-label": label,
|
|
531
|
+
style: trackStyle
|
|
532
|
+
}, (0, react.createElement)("span", { className: "imqb2-seg-indicator" }), options.map((o) => (0, react.createElement)("button", {
|
|
533
|
+
key: o.value,
|
|
534
|
+
type: "button",
|
|
535
|
+
role: "tab",
|
|
536
|
+
"aria-selected": o.value === value,
|
|
537
|
+
className: "imqb2-seg-tab",
|
|
538
|
+
onClick: () => {
|
|
539
|
+
onChange(o.value);
|
|
540
|
+
}
|
|
541
|
+
}, o.label)));
|
|
542
|
+
}
|
|
543
|
+
/** 开关行(label 左 + Switch 右 + 可选 help) */
|
|
544
|
+
function SwitchRow({ label, help, checked, onChange }) {
|
|
545
|
+
return (0, react.createElement)("div", { className: "imqb2-field" }, (0, react.createElement)("div", { className: "imqb2-head" }, (0, react.createElement)("span", { className: "imqb2-label" }, label), (0, react.createElement)(Switch, {
|
|
546
|
+
checked,
|
|
547
|
+
label,
|
|
548
|
+
onChange
|
|
549
|
+
})), help === void 0 ? null : (0, react.createElement)("p", { className: "imqb2-help" }, help));
|
|
550
|
+
}
|
|
551
|
+
/** 文本行(label 上 + 输入下,与官方 fields 布局一致) */
|
|
552
|
+
function FieldRow({ label, help, value, placeholder, onChange }) {
|
|
553
|
+
return (0, react.createElement)("div", { className: "imqb2-field" }, (0, react.createElement)("span", { className: "imqb2-label" }, label), (0, react.createElement)("input", {
|
|
554
|
+
className: "imqb2-input",
|
|
555
|
+
value,
|
|
556
|
+
placeholder,
|
|
557
|
+
spellCheck: false,
|
|
558
|
+
onChange: (event) => {
|
|
559
|
+
onChange(event.currentTarget.value);
|
|
560
|
+
}
|
|
561
|
+
}), help === void 0 ? null : (0, react.createElement)("p", { className: "imqb2-hint" }, help));
|
|
562
|
+
}
|
|
563
|
+
/** 多行文本行 */
|
|
564
|
+
function AreaRow({ label, help, value, rows, onChange }) {
|
|
565
|
+
return (0, react.createElement)("div", { className: "imqb2-field" }, (0, react.createElement)("span", { className: "imqb2-label" }, label), (0, react.createElement)("textarea", {
|
|
566
|
+
className: "imqb2-input imqb2-textarea",
|
|
567
|
+
value,
|
|
568
|
+
rows: rows ?? 3,
|
|
569
|
+
onChange: (event) => {
|
|
570
|
+
onChange(event.currentTarget.value);
|
|
571
|
+
}
|
|
572
|
+
}), help === void 0 ? null : (0, react.createElement)("p", { className: "imqb2-hint" }, help));
|
|
573
|
+
}
|
|
574
|
+
/** 访问模式分段选择行 */
|
|
575
|
+
function SegmentedRow({ label, help, value, options, onChange }) {
|
|
576
|
+
return (0, react.createElement)("div", { className: "imqb2-field" }, (0, react.createElement)("div", { className: "imqb2-head" }, (0, react.createElement)("span", { className: "imqb2-label" }, label), (0, react.createElement)(Segmented, {
|
|
577
|
+
value,
|
|
578
|
+
options,
|
|
579
|
+
onChange,
|
|
580
|
+
label
|
|
581
|
+
})), help === void 0 ? null : (0, react.createElement)("p", { className: "imqb2-hint" }, help));
|
|
582
|
+
}
|
|
583
|
+
const MODE_OPTIONS = [
|
|
584
|
+
{
|
|
585
|
+
value: "open",
|
|
586
|
+
label: "开放"
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
value: "allowlist",
|
|
590
|
+
label: "白名单"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
value: "disabled",
|
|
594
|
+
label: "停用"
|
|
595
|
+
}
|
|
596
|
+
];
|
|
597
|
+
/** 深比较(表单值与初始回显值的 dirty 判断) */
|
|
598
|
+
function shallowEqual(a, b) {
|
|
599
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* 配置面板主体。
|
|
603
|
+
*
|
|
604
|
+
* 保存仅提交变更键(服务端按白名单合并),保存成功后提示
|
|
605
|
+
* HMR 重载语义(进行中的机器人会话将重置)。
|
|
606
|
+
*/
|
|
607
|
+
function ConfigPanel() {
|
|
608
|
+
useConfigPanelStyles();
|
|
609
|
+
const [initial, setInitial] = (0, react.useState)(null);
|
|
610
|
+
const [form, setForm] = (0, react.useState)(null);
|
|
611
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
612
|
+
const [result, setResult] = (0, react.useState)(null);
|
|
613
|
+
const [loadError, setLoadError] = (0, react.useState)(null);
|
|
614
|
+
(0, react.useEffect)(() => {
|
|
615
|
+
let stopped = false;
|
|
616
|
+
fetch(BIND_CONFIG_PATH, { cache: "no-store" }).then(async (res) => {
|
|
617
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
618
|
+
const body = await res.json();
|
|
619
|
+
if (stopped) return;
|
|
620
|
+
setInitial(body.config);
|
|
621
|
+
setForm(body.config);
|
|
622
|
+
}).catch((err) => {
|
|
623
|
+
if (!stopped) setLoadError(err instanceof Error ? err.message : String(err));
|
|
624
|
+
});
|
|
625
|
+
return () => {
|
|
626
|
+
stopped = true;
|
|
627
|
+
};
|
|
628
|
+
}, []);
|
|
629
|
+
const patch = (0, react.useCallback)((key, value) => {
|
|
630
|
+
setForm((current) => current === null ? current : {
|
|
631
|
+
...current,
|
|
632
|
+
[key]: value
|
|
633
|
+
});
|
|
634
|
+
setResult(null);
|
|
635
|
+
}, []);
|
|
636
|
+
const submit = (0, react.useCallback)((event) => {
|
|
637
|
+
event.preventDefault();
|
|
638
|
+
if (busy || initial === null || form === null) return;
|
|
639
|
+
const changed = {};
|
|
640
|
+
for (const key of Object.keys(form)) if (!shallowEqual(form[key], initial[key])) changed[key] = form[key];
|
|
641
|
+
if (Object.keys(changed).length === 0) return;
|
|
642
|
+
setBusy(true);
|
|
643
|
+
fetch(BIND_CONFIG_PATH, {
|
|
644
|
+
method: "POST",
|
|
645
|
+
headers: { "content-type": "application/json" },
|
|
646
|
+
body: JSON.stringify(changed)
|
|
647
|
+
}).then(async (res) => {
|
|
648
|
+
const body = await res.json().catch(() => ({}));
|
|
649
|
+
if (res.ok && body.ok === true) {
|
|
650
|
+
setResult({
|
|
651
|
+
ok: true,
|
|
652
|
+
message: body.message ?? "配置已保存,插件将自动重载"
|
|
653
|
+
});
|
|
654
|
+
setInitial(form);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
setResult({
|
|
658
|
+
ok: false,
|
|
659
|
+
message: body.error ?? `保存失败(HTTP ${res.status})`
|
|
660
|
+
});
|
|
661
|
+
}).catch((err) => {
|
|
662
|
+
setResult({
|
|
663
|
+
ok: false,
|
|
664
|
+
message: err instanceof Error ? err.message : String(err)
|
|
665
|
+
});
|
|
666
|
+
}).finally(() => {
|
|
667
|
+
setBusy(false);
|
|
668
|
+
});
|
|
669
|
+
}, [
|
|
670
|
+
busy,
|
|
671
|
+
form,
|
|
672
|
+
initial
|
|
673
|
+
]);
|
|
674
|
+
if (loadError !== null) return (0, react.createElement)("p", {
|
|
675
|
+
className: "imqb2-hint",
|
|
676
|
+
style: { padding: "12px 0" }
|
|
677
|
+
}, `配置加载失败:${loadError}`);
|
|
678
|
+
if (initial === null || form === null) return (0, react.createElement)("p", {
|
|
679
|
+
className: "imqb2-hint",
|
|
680
|
+
style: { padding: "12px 0" }
|
|
681
|
+
}, "正在加载配置…");
|
|
682
|
+
const dirty = Object.keys(form).some((key) => !shallowEqual(form[key], initial[key]));
|
|
683
|
+
const access = form.access ?? {
|
|
684
|
+
c2cMode: "open",
|
|
685
|
+
c2cAllow: [],
|
|
686
|
+
groupMode: "open",
|
|
687
|
+
groupAllow: []
|
|
688
|
+
};
|
|
689
|
+
return (0, react.createElement)("form", {
|
|
690
|
+
className: "imqb2-form",
|
|
691
|
+
onSubmit: submit
|
|
692
|
+
}, (0, react.createElement)("div", { className: "imqb2-group-title" }, "访问控制"), (0, react.createElement)(SegmentedRow, {
|
|
693
|
+
label: "私聊",
|
|
694
|
+
value: access.c2cMode,
|
|
695
|
+
options: MODE_OPTIONS,
|
|
696
|
+
onChange: (v) => {
|
|
697
|
+
patch("access", {
|
|
698
|
+
...access,
|
|
699
|
+
c2cMode: v
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}), access.c2cMode === "allowlist" ? (0, react.createElement)(AreaRow, {
|
|
703
|
+
label: "私聊白名单",
|
|
704
|
+
help: "每行一个 openid",
|
|
705
|
+
rows: 3,
|
|
706
|
+
value: access.c2cAllow.join("\n"),
|
|
707
|
+
onChange: (v) => {
|
|
708
|
+
patch("access", {
|
|
709
|
+
...access,
|
|
710
|
+
c2cAllow: v.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}) : null, (0, react.createElement)(SegmentedRow, {
|
|
714
|
+
label: "群聊",
|
|
715
|
+
value: access.groupMode,
|
|
716
|
+
options: MODE_OPTIONS,
|
|
717
|
+
onChange: (v) => {
|
|
718
|
+
patch("access", {
|
|
719
|
+
...access,
|
|
720
|
+
groupMode: v
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}), access.groupMode === "allowlist" ? (0, react.createElement)(AreaRow, {
|
|
724
|
+
label: "群聊白名单",
|
|
725
|
+
help: "每行一个群 openid",
|
|
726
|
+
rows: 3,
|
|
727
|
+
value: access.groupAllow.join("\n"),
|
|
728
|
+
onChange: (v) => {
|
|
729
|
+
patch("access", {
|
|
730
|
+
...access,
|
|
731
|
+
groupAllow: v.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
}) : null, (0, react.createElement)("div", { className: "imqb2-group-title" }, "基本"), (0, react.createElement)(SwitchRow, {
|
|
735
|
+
label: "群聊需要 @机器人",
|
|
736
|
+
checked: form.requireMention ?? true,
|
|
737
|
+
help: "开启后群聊中只有 @机器人 的消息会触发回复",
|
|
738
|
+
onChange: (v) => {
|
|
739
|
+
patch("requireMention", v);
|
|
740
|
+
}
|
|
741
|
+
}), (0, react.createElement)(SwitchRow, {
|
|
742
|
+
label: "流式输出",
|
|
743
|
+
checked: form.streaming ?? true,
|
|
744
|
+
help: "回复内容分段实时推送到聊天",
|
|
745
|
+
onChange: (v) => {
|
|
746
|
+
patch("streaming", v);
|
|
747
|
+
}
|
|
748
|
+
}), (0, react.createElement)(SwitchRow, {
|
|
749
|
+
label: "显示工具调用结果",
|
|
750
|
+
checked: form.showToolResults ?? false,
|
|
751
|
+
help: "在聊天中展示 agent 工具调用的结构化结果",
|
|
752
|
+
onChange: (v) => {
|
|
753
|
+
patch("showToolResults", v);
|
|
754
|
+
}
|
|
755
|
+
}), (0, react.createElement)("div", { className: "imqb2-group-title" }, "模型"), (0, react.createElement)(FieldRow, {
|
|
756
|
+
label: "Provider",
|
|
757
|
+
value: form.provider ?? "",
|
|
758
|
+
placeholder: "留空继承宿主默认",
|
|
759
|
+
onChange: (v) => {
|
|
760
|
+
patch("provider", v);
|
|
761
|
+
}
|
|
762
|
+
}), (0, react.createElement)(FieldRow, {
|
|
763
|
+
label: "Model",
|
|
764
|
+
value: form.model ?? "",
|
|
765
|
+
placeholder: "留空继承宿主默认",
|
|
766
|
+
onChange: (v) => {
|
|
767
|
+
patch("model", v);
|
|
768
|
+
}
|
|
769
|
+
}), (0, react.createElement)(FieldRow, {
|
|
770
|
+
label: "Preset",
|
|
771
|
+
value: form.preset ?? "",
|
|
772
|
+
placeholder: "Agent preset id",
|
|
773
|
+
onChange: (v) => {
|
|
774
|
+
patch("preset", v);
|
|
775
|
+
}
|
|
776
|
+
}), (0, react.createElement)("div", { className: "imqb2-group-title" }, "补充指令"), (0, react.createElement)(AreaRow, {
|
|
777
|
+
label: "群聊额外 system prompt",
|
|
778
|
+
value: form.groupPrompt ?? "",
|
|
779
|
+
onChange: (v) => {
|
|
780
|
+
patch("groupPrompt", v);
|
|
781
|
+
}
|
|
782
|
+
}), (0, react.createElement)(AreaRow, {
|
|
783
|
+
label: "私聊额外 system prompt",
|
|
784
|
+
value: form.directPrompt ?? "",
|
|
785
|
+
onChange: (v) => {
|
|
786
|
+
patch("directPrompt", v);
|
|
787
|
+
}
|
|
788
|
+
}), form.cwd !== void 0 ? (0, react.createElement)("div", { className: "imqb2-field" }, (0, react.createElement)("div", { className: "imqb2-head" }, (0, react.createElement)("span", { className: "imqb2-label" }, "Agent 工作目录"), (0, react.createElement)("span", { className: "imqb2-hint" }, "只读")), (0, react.createElement)("p", { className: "imqb2-readonly" }, form.cwd)) : null, (0, react.createElement)("div", { className: "imqb2-footer" }, result !== null ? (0, react.createElement)("p", { className: `imqb2-note${result.ok ? " imqb2-note--ok" : " imqb2-note--error"}` }, (result.ok ? "" : "⚠️ ") + result.message) : dirty ? (0, react.createElement)("p", { className: "imqb2-note" }, "保存后插件将重载,进行中的机器人会话将重置") : (0, react.createElement)("p", { className: "imqb2-note" }), (0, react.createElement)("button", {
|
|
789
|
+
className: "imqb2-save",
|
|
790
|
+
type: "submit",
|
|
791
|
+
disabled: busy || !dirty
|
|
792
|
+
}, busy ? "正在保存…" : "保存")));
|
|
156
793
|
}
|
|
157
794
|
//#endregion
|
|
158
795
|
//#region src/client/index.ts
|
|
@@ -166,7 +803,7 @@ window.__ModuleLoader__.load({
|
|
|
166
803
|
const TAB_KIND = "qqbot-bind";
|
|
167
804
|
/** 席位注册 key(Tab 类型 id,席位按其分发;只需全局唯一,与包名解耦) */
|
|
168
805
|
const TAB_ID = "im-qqbot/bind";
|
|
169
|
-
/**
|
|
806
|
+
/** 绑定入口图标(QQ 机器人,跟随主题图标色变量) */
|
|
170
807
|
function BindGuideIcon() {
|
|
171
808
|
return (0, react.createElement)("svg", {
|
|
172
809
|
width: 24,
|
|
@@ -175,34 +812,21 @@ window.__ModuleLoader__.load({
|
|
|
175
812
|
fill: "none",
|
|
176
813
|
"aria-hidden": true
|
|
177
814
|
}, (0, react.createElement)("path", {
|
|
178
|
-
d: "
|
|
179
|
-
fill: "
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
cy: 12,
|
|
184
|
-
r: 1.4,
|
|
185
|
-
fill: "#fff"
|
|
186
|
-
}), (0, react.createElement)("circle", {
|
|
187
|
-
cx: 14.5,
|
|
188
|
-
cy: 12,
|
|
189
|
-
r: 1.4,
|
|
190
|
-
fill: "#fff"
|
|
815
|
+
d: "M9.50039 11.1504C9.96983 11.1504 10.3504 11.5309 10.3504 12.0004V14.0004C10.3504 14.4698 9.96983 14.8504 9.50039 14.8504C9.03095 14.8504 8.65039 14.4698 8.65039 14.0004V12.0004C8.65039 11.5309 9.03095 11.1504 9.50039 11.1504Z",
|
|
816
|
+
fill: "var(------icon_primary, #1A1A1A)"
|
|
817
|
+
}), (0, react.createElement)("path", {
|
|
818
|
+
d: "M14.5004 11.1504C14.9698 11.1504 15.3504 11.5309 15.3504 12.0004V14.0004C15.3504 14.4698 14.9698 14.8504 14.5004 14.8504C14.0309 14.8504 13.6504 14.4698 13.6504 14.0004V12.0004C13.6504 11.5309 14.0309 11.1504 14.5004 11.1504Z",
|
|
819
|
+
fill: "var(------icon_primary, #1A1A1A)"
|
|
191
820
|
}), (0, react.createElement)("path", {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
821
|
+
fillRule: "evenodd",
|
|
822
|
+
clipRule: "evenodd",
|
|
823
|
+
d: "M12.8504 5.3109C13.5299 4.99138 14.0004 4.30063 14.0004 3.5C14.0004 2.39543 13.1049 1.5 12.0004 1.5C10.8958 1.5 10.0004 2.39543 10.0004 3.5C10.0004 4.30065 10.4708 4.99141 11.1504 5.31092V6.18851C6.38487 6.61795 2.65039 10.6231 2.65039 15.5004V17.0004C2.65039 19.679 4.82181 21.8504 7.50039 21.8504H16.5004C19.179 21.8504 21.3504 19.679 21.3504 17.0004V15.5004C21.3504 10.6231 17.6159 6.61795 12.8504 6.18851V5.3109ZM12.0004 7.85039C7.77541 7.85039 4.35039 11.2754 4.35039 15.5004V17.0004C4.35039 18.7401 5.76069 20.1504 7.50039 20.1504H16.5004C18.2401 20.1504 19.6504 18.7401 19.6504 17.0004V15.5004C19.6504 11.2754 16.2254 7.85039 12.0004 7.85039Z",
|
|
824
|
+
fill: "var(------icon_primary, #1A1A1A)"
|
|
196
825
|
}));
|
|
197
826
|
}
|
|
198
827
|
/** 设置页「QQ Bot」栏目:面板常驻展示绑定状态 */
|
|
199
828
|
function QQBotSettingsSection() {
|
|
200
|
-
return (0, react.createElement)("div", null, (0, react.createElement)(
|
|
201
|
-
margin: "0 0 8px",
|
|
202
|
-
opacity: .75,
|
|
203
|
-
lineHeight: 1.6,
|
|
204
|
-
fontSize: "13px"
|
|
205
|
-
} }, "扫码将 QQ 机器人绑定到当前配置;绑定完成后机器人接入私聊与群聊。"), (0, react.createElement)(BindPanelBody));
|
|
829
|
+
return (0, react.createElement)("div", null, (0, react.createElement)(BindPanelBody), (0, react.createElement)(ConfigPanel));
|
|
206
830
|
}
|
|
207
831
|
function apply(ctx) {
|
|
208
832
|
const tabs = ctx.sidebarRightTabs;
|
|
@@ -213,18 +837,18 @@ window.__ModuleLoader__.load({
|
|
|
213
837
|
name: "settings.section",
|
|
214
838
|
id: "qqbot",
|
|
215
839
|
order: 7,
|
|
216
|
-
label: "QQ
|
|
840
|
+
label: "QQ 机器人"
|
|
217
841
|
}, QQBotSettingsSection)));
|
|
218
842
|
if (tabs !== void 0 && slots !== void 0) {
|
|
219
843
|
disposers.push(tabs.register({
|
|
220
844
|
id: TAB_ID,
|
|
221
845
|
kind: TAB_KIND,
|
|
222
|
-
title: () => "QQ
|
|
846
|
+
title: () => "QQ 机器人绑定",
|
|
223
847
|
guide: [{
|
|
224
848
|
id: "bind",
|
|
225
849
|
order: 90,
|
|
226
|
-
title: () => "QQ
|
|
227
|
-
description: () => "
|
|
850
|
+
title: () => "QQ 机器人绑定",
|
|
851
|
+
description: () => "扫码或手动填写,绑定 QQ 机器人",
|
|
228
852
|
icon: BindGuideIcon
|
|
229
853
|
}]
|
|
230
854
|
}));
|
|
@@ -263,7 +887,7 @@ window.__ModuleLoader__.load({
|
|
|
263
887
|
return;
|
|
264
888
|
}
|
|
265
889
|
try {
|
|
266
|
-
const res = await fetch(
|
|
890
|
+
const res = await fetch(BIND_STATE_PATH, { cache: "no-store" });
|
|
267
891
|
if (res.ok) {
|
|
268
892
|
if ((await res.json()).phase === "waiting" && !opened && right !== void 0) try {
|
|
269
893
|
right.openTab(TAB_KIND);
|