dsh-llm-codebuddy 1.3.4 → 1.3.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/client.js ADDED
@@ -0,0 +1,547 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-llm-codebuddy",
3
+ factory: () => {
4
+ const ROUTE = "/dsh-llm-codebuddy/auth";
5
+ const MARKER = "data-codebuddy-auth-switch";
6
+
7
+ function button(text) {
8
+ const element = document.createElement("button");
9
+ element.type = "button";
10
+ element.textContent = text;
11
+ Object.assign(element.style, {
12
+ minHeight: "44px",
13
+ padding: "0 12px",
14
+ border: "1px solid var(--dsw-border-subtle, #d0d5dd)",
15
+ borderRadius: "8px",
16
+ background: "var(--dsw-surface-subtle, transparent)",
17
+ color: "inherit",
18
+ cursor: "pointer",
19
+ whiteSpace: "nowrap",
20
+ boxSizing: "border-box",
21
+ font: "inherit",
22
+ fontSize: "13px",
23
+ fontWeight: "500",
24
+ transition: "background-color 120ms ease, border-color 120ms ease, opacity 120ms ease",
25
+ });
26
+ return element;
27
+ }
28
+
29
+ function accountPicker() {
30
+ const element = document.createElement("select");
31
+ element.setAttribute("aria-label", "CodeBuddy 令牌账号");
32
+ Object.assign(element.style, {
33
+ minHeight: "44px",
34
+ minWidth: "180px",
35
+ maxWidth: "260px",
36
+ flex: "1 1 220px",
37
+ boxSizing: "border-box",
38
+ padding: "0 10px",
39
+ border: "1px solid var(--dsw-border-subtle, #d0d5dd)",
40
+ borderRadius: "8px",
41
+ background: "var(--dsw-surface-subtle, transparent)",
42
+ color: "inherit",
43
+ font: "inherit",
44
+ fontSize: "13px",
45
+ });
46
+ return element;
47
+ }
48
+
49
+ function textInput(type, placeholder, ariaLabel) {
50
+ const element = document.createElement("input");
51
+ element.type = type;
52
+ element.placeholder = placeholder;
53
+ element.setAttribute("aria-label", ariaLabel);
54
+ element.autocomplete = type === "password" ? "new-password" : "off";
55
+ Object.assign(element.style, {
56
+ minHeight: "44px",
57
+ minWidth: "0",
58
+ flex: "1 1 200px",
59
+ boxSizing: "border-box",
60
+ padding: "0 12px",
61
+ border: "1px solid var(--dsw-border-subtle, #d0d5dd)",
62
+ borderRadius: "8px",
63
+ background: "var(--dsw-surface-subtle, transparent)",
64
+ color: "inherit",
65
+ font: "inherit",
66
+ fontSize: "13px",
67
+ });
68
+ return element;
69
+ }
70
+
71
+ function fieldLabel(text) {
72
+ const element = document.createElement("span");
73
+ element.textContent = text;
74
+ Object.assign(element.style, {
75
+ flex: "0 0 auto",
76
+ minWidth: "92px",
77
+ fontSize: "13px",
78
+ lineHeight: "20px",
79
+ color: "var(--dsw-text-secondary, #667085)",
80
+ });
81
+ return element;
82
+ }
83
+
84
+ function row() {
85
+ const element = document.createElement("div");
86
+ Object.assign(element.style, {
87
+ display: "flex",
88
+ alignItems: "center",
89
+ gap: "8px",
90
+ flexWrap: "wrap",
91
+ width: "100%",
92
+ });
93
+ return element;
94
+ }
95
+
96
+ function section() {
97
+ const element = document.createElement("div");
98
+ Object.assign(element.style, {
99
+ display: "flex",
100
+ flexDirection: "column",
101
+ gap: "8px",
102
+ width: "100%",
103
+ boxSizing: "border-box",
104
+ padding: "10px 12px",
105
+ border: "1px solid var(--dsw-border-subtle, #d0d5dd)",
106
+ borderRadius: "10px",
107
+ background: "var(--dsw-surface-subtle, rgba(0, 0, 0, 0.02))",
108
+ });
109
+ return element;
110
+ }
111
+
112
+ function setVisible(element, visible, display = "flex") {
113
+ element.hidden = !visible;
114
+ element.style.setProperty("display", visible ? display : "none", "important");
115
+ }
116
+
117
+ function isCodeBuddy(input) {
118
+ const editor = input.parentElement?.parentElement;
119
+ if (!editor) return false;
120
+ if (editor.textContent?.includes("codebuddy-cn")) return true;
121
+ return editor.parentElement?.querySelector('select[aria-label="提供方"]')?.value === "codebuddy-cn";
122
+ }
123
+
124
+ function accountText(account) {
125
+ const label = account?.accountName || account?.label || account?.account?.displayName || account?.account?.email || account?.account?.userId || "未命名账号";
126
+ const userId = account?.userId || account?.account?.userId;
127
+ return userId && userId !== label ? `${label} · ${String(userId).slice(0, 8)}` : label;
128
+ }
129
+
130
+ function accountLabel(account) {
131
+ return account?.accountName || account?.label || account?.account?.displayName || account?.account?.email || account?.account?.userId || "未命名账号";
132
+ }
133
+
134
+ function apiKeyText(key) {
135
+ const label = key?.label || (key?.kind === "environment" ? `环境变量 ${key.ref}` : "DSH 保存的 API Key");
136
+ const suffix = key?.masked ? ` · ${key.masked}` : "";
137
+ return key?.configured === false ? `${label}(不可用)` : `${label}${suffix}`;
138
+ }
139
+
140
+ function apiKeyLabel(key) {
141
+ return key?.label || (key?.kind === "environment" ? `环境变量 ${key.ref}` : "DSH 保存的 API Key");
142
+ }
143
+
144
+ function formatCredits(value) {
145
+ const number = Number(value);
146
+ return Number.isFinite(number) ? number.toLocaleString("zh-CN", { maximumFractionDigits: 2 }) : "—";
147
+ }
148
+
149
+ function applyCreditStatus(stats, status, activeAccount) {
150
+ const visible = status.mode === "token" && Boolean(activeAccount);
151
+ setVisible(stats, visible);
152
+ if (!visible) return;
153
+ const credit = stats.querySelector('[data-codebuddy-stat="credits"]');
154
+ const usage = stats.querySelector('[data-codebuddy-stat="usage"]');
155
+ credit.title = status.creditError || "";
156
+ usage.title = status.todayUsageError || "";
157
+ if (status.creditLoading) credit.textContent = "剩余积分:读取中…";
158
+ else if (status.unlimited) credit.textContent = "剩余积分:不限量";
159
+ else if (typeof status.credits === "number" && Number.isFinite(status.credits)) credit.textContent = `剩余积分:${formatCredits(status.credits)}`;
160
+ else credit.textContent = status.creditError ? "剩余积分:暂不可用" : "剩余积分:—";
161
+ const today = status.todayUsage;
162
+ if (today && today.synced === true) {
163
+ usage.textContent = `今日请求:${Number.isFinite(Number(today.count)) ? Number(today.count) : 0} 次 · 用量 ${formatCredits(today.used)} 积分`;
164
+ } else {
165
+ usage.textContent = status.creditLoading ? "今日请求:读取中…" : status.todayUsageError ? "今日请求:暂不可用" : "今日请求:—";
166
+ }
167
+ }
168
+
169
+ function applyMode(input, keyButton, tokenButton, keySection, keySourceRow, keySelect, keyHint, newKeyInput, newKeyLabelInput, saveKeyButton, removeKeyButton, tokenSection, accountRow, accountSelect, accountName, addButton, removeButton, accountStats, message, status) {
170
+ const token = status.mode === "token";
171
+ const apiKeys = Array.isArray(status.apiKeys) ? status.apiKeys : [];
172
+ const activeApiKeyId = status.activeApiKeyId ?? apiKeys[0]?.id;
173
+ const activeApiKey = apiKeys.find((key) => key.id === activeApiKeyId);
174
+ const accounts = Array.isArray(status.accounts) ? status.accounts : [];
175
+ const activeAccountId = status.activeAccountId ?? accounts[0]?.id;
176
+ input.disabled = token;
177
+ input.placeholder = token ? "当前使用 CodeBuddy 账号令牌" : "输入新的 API Key(保存到 DSH)";
178
+ newKeyInput.disabled = token;
179
+ newKeyLabelInput.disabled = token;
180
+ keyButton.setAttribute("aria-pressed", String(!token));
181
+ tokenButton.setAttribute("aria-pressed", String(token));
182
+ keyButton.style.background = !token ? "var(--dsw-accent-subtle, #eef4ff)" : "var(--dsw-surface-subtle, transparent)";
183
+ tokenButton.style.background = token ? "var(--dsw-accent-subtle, #eef4ff)" : "var(--dsw-surface-subtle, transparent)";
184
+ setVisible(keySection, !token);
185
+ setVisible(keySourceRow, apiKeys.length > 0);
186
+ keySelect.replaceChildren(...apiKeys.map((key) => {
187
+ const option = document.createElement("option");
188
+ option.value = key.id;
189
+ option.textContent = apiKeyText(key);
190
+ option.title = apiKeyLabel(key);
191
+ return option;
192
+ }));
193
+ if (activeApiKeyId) keySelect.value = activeApiKeyId;
194
+ keyHint.textContent = token
195
+ ? ""
196
+ : activeApiKey ? `${status.apiKeyConfigured ? "当前来源" : "可选来源"}:${apiKeyText(activeApiKey)}` : "未检测到可用 API Key,可输入新 Key 保存";
197
+ keyHint.title = activeApiKey ? apiKeyLabel(activeApiKey) : "";
198
+ saveKeyButton.textContent = "添加并使用";
199
+ removeKeyButton.hidden = !activeApiKey || activeApiKey.kind !== "dsh";
200
+ setVisible(tokenSection, token);
201
+ tokenButton.textContent = token ? "令牌模式" : "令牌登录";
202
+ addButton.textContent = accounts.length ? "添加账号" : "登录 CodeBuddy";
203
+ setVisible(accountRow, token && accounts.length > 0);
204
+ accountSelect.replaceChildren(...accounts.map((account) => {
205
+ const option = document.createElement("option");
206
+ option.value = account.id;
207
+ option.textContent = accountText(account);
208
+ option.title = accountLabel(account);
209
+ return option;
210
+ }));
211
+ if (activeAccountId) accountSelect.value = activeAccountId;
212
+ const activeAccount = accounts.find((account) => account.id === activeAccountId);
213
+ accountName.textContent = activeAccount ? `当前账号:${accountText(activeAccount)}` : "";
214
+ accountName.title = activeAccount ? accountLabel(activeAccount) : "";
215
+ removeButton.hidden = !token || !activeAccount;
216
+ setVisible(accountStats, token && Boolean(activeAccount));
217
+ applyCreditStatus(accountStats, status, activeAccount);
218
+ message.textContent = token
219
+ ? status.authenticated ? `令牌已登录:${activeAccount ? accountText(activeAccount) : "当前账号"}` : "令牌缺失,请重新登录"
220
+ : status.apiKeyConfigured ? `API Key 已就绪:${activeApiKey ? apiKeyText(activeApiKey) : "当前来源"}` : "未配置 API Key,请输入后保存";
221
+ message.style.color = token
222
+ ? status.authenticated ? "var(--dsw-text-success, #2e7d32)" : "var(--dsw-text-danger, #c62828)"
223
+ : status.apiKeyConfigured ? "var(--dsw-text-success, #2e7d32)" : "var(--dsw-text-danger, #c62828)";
224
+ }
225
+
226
+ async function request(path, body) {
227
+ const options = { method: "POST" };
228
+ if (body !== undefined) {
229
+ options.headers = { "content-type": "application/json" };
230
+ options.body = JSON.stringify(body);
231
+ }
232
+ const response = await fetch(`${ROUTE}/${path}`, options);
233
+ const result = await response.json();
234
+ if (!response.ok || !result.ok) {
235
+ const error = new Error(result.message || `请求失败(${response.status})`);
236
+ error.status = response.status;
237
+ throw error;
238
+ }
239
+ return result;
240
+ }
241
+
242
+ function mount(input) {
243
+ const field = input.parentElement;
244
+ if (!field || field.querySelector(`[${MARKER}]`)) return;
245
+ field.setAttribute("data-codebuddy-auth-field", "");
246
+ input.dataset.codebuddyPlaceholder = input.placeholder;
247
+ for (const nativeNode of field.children) nativeNode.hidden = true;
248
+ Object.assign(field.style, { display: "block", width: "100%", boxSizing: "border-box" });
249
+ const controls = document.createElement("div");
250
+ controls.setAttribute(MARKER, "");
251
+ controls.setAttribute("role", "group");
252
+ controls.setAttribute("aria-label", "CodeBuddy 认证方式");
253
+ Object.assign(controls.style, {
254
+ display: "flex",
255
+ flexDirection: "column",
256
+ alignItems: "stretch",
257
+ gap: "8px",
258
+ width: "100%",
259
+ maxWidth: "100%",
260
+ paddingTop: "4px",
261
+ boxSizing: "border-box",
262
+ color: "var(--dsw-text-primary, inherit)",
263
+ });
264
+ const modeRow = row();
265
+ const keySection = section();
266
+ const keySourceRow = row();
267
+ const keyAddRow = row();
268
+ const tokenSection = section();
269
+ const accountRow = row();
270
+ const tokenActionRow = row();
271
+ const keyButton = button("API Key");
272
+ const tokenButton = button("令牌登录");
273
+ const keySelect = accountPicker();
274
+ keySelect.setAttribute("aria-label", "CodeBuddy API Key 来源");
275
+ const keySourceLabel = fieldLabel("当前 API Key");
276
+ const newKeyLabel = fieldLabel("新增 API Key");
277
+ const newKeyInput = textInput("password", "粘贴新的 API Key", "新增 CodeBuddy API Key");
278
+ const newKeyLabelInput = textInput("text", "名称(可选)", "API Key 名称");
279
+ const keyHint = document.createElement("span");
280
+ Object.assign(keyHint.style, {
281
+ display: "block",
282
+ width: "100%",
283
+ minWidth: "0",
284
+ overflowWrap: "anywhere",
285
+ whiteSpace: "normal",
286
+ fontSize: "12px",
287
+ lineHeight: "18px",
288
+ color: "var(--dsw-text-secondary, #667085)",
289
+ });
290
+ const saveKeyButton = button("添加并使用");
291
+ const removeKeyButton = button("删除");
292
+ const accountSelect = accountPicker();
293
+ const accountName = document.createElement("span");
294
+ Object.assign(accountName.style, {
295
+ flex: "1 1 220px",
296
+ minWidth: "0",
297
+ overflowWrap: "anywhere",
298
+ whiteSpace: "normal",
299
+ fontSize: "13px",
300
+ color: "var(--dsw-text-secondary, #667085)",
301
+ });
302
+ const addButton = button("令牌登录");
303
+ const removeButton = button("删除账号");
304
+ const accountLabel = fieldLabel("令牌账号");
305
+ const accountStats = document.createElement("div");
306
+ accountStats.setAttribute("role", "status");
307
+ accountStats.setAttribute("aria-live", "polite");
308
+ Object.assign(accountStats.style, {
309
+ display: "flex",
310
+ alignItems: "center",
311
+ gap: "8px",
312
+ flexWrap: "wrap",
313
+ width: "100%",
314
+ minHeight: "24px",
315
+ fontSize: "13px",
316
+ color: "var(--dsw-text-secondary, #667085)",
317
+ });
318
+ const tokenHint = document.createElement("span");
319
+ tokenHint.textContent = "令牌登录后可切换账号,并查看积分与今日请求量。";
320
+ Object.assign(tokenHint.style, {
321
+ display: "block",
322
+ width: "100%",
323
+ fontSize: "12px",
324
+ lineHeight: "18px",
325
+ color: "var(--dsw-text-secondary, #667085)",
326
+ });
327
+ const creditStat = document.createElement("span");
328
+ creditStat.dataset.codebuddyStat = "credits";
329
+ const usageStat = document.createElement("span");
330
+ usageStat.dataset.codebuddyStat = "usage";
331
+ accountStats.append(creditStat, usageStat);
332
+ const message = document.createElement("span");
333
+ message.setAttribute("role", "status");
334
+ message.setAttribute("aria-live", "polite");
335
+ Object.assign(message.style, { fontSize: "12px", minHeight: "18px", lineHeight: "18px" });
336
+ modeRow.append(keyButton, tokenButton);
337
+ keySourceRow.append(keySourceLabel, keySelect, removeKeyButton);
338
+ keyAddRow.append(newKeyLabel, newKeyInput, newKeyLabelInput, saveKeyButton);
339
+ keySection.append(keySourceRow, keyAddRow, keyHint);
340
+ accountRow.append(accountLabel, accountSelect, accountName, removeButton);
341
+ tokenActionRow.append(addButton);
342
+ tokenSection.append(tokenHint, accountRow, accountStats, tokenActionRow);
343
+ controls.append(modeRow, keySection, tokenSection, message);
344
+ field.append(controls);
345
+ let current = { mode: "api-key", authenticated: false, accounts: [], apiKeys: [], credits: undefined, creditLoading: false, creditError: null, todayUsage: null, todayUsageError: null };
346
+ const render = (status) => {
347
+ current = { ...current, ...status };
348
+ applyMode(input, keyButton, tokenButton, keySection, keySourceRow, keySelect, keyHint, newKeyInput, newKeyLabelInput, saveKeyButton, removeKeyButton, tokenSection, accountRow, accountSelect, accountName, addButton, removeButton, accountStats, message, current);
349
+ };
350
+ render(current);
351
+ let creditRequestId = 0;
352
+ const loadCredits = async (accountId) => {
353
+ const requestId = ++creditRequestId;
354
+ if (current.mode !== "token" || !accountId) {
355
+ render({ credits: undefined, creditLoading: false, creditError: null, todayUsage: null, todayUsageError: null });
356
+ return;
357
+ }
358
+ render({ credits: undefined, creditLoading: true, creditError: null, todayUsage: null, todayUsageError: null });
359
+ try {
360
+ const result = await request("credits", { accountId });
361
+ if (requestId !== creditRequestId || current.activeAccountId !== accountId) return;
362
+ render({ ...result, creditLoading: false });
363
+ } catch (error) {
364
+ if (requestId !== creditRequestId || current.activeAccountId !== accountId) return;
365
+ render({ credits: null, creditLoading: false, creditError: error instanceof Error ? error.message : "查询 CodeBuddy 积分失败", todayUsage: null, todayUsageError: "查询 CodeBuddy 今日请求量失败" });
366
+ }
367
+ };
368
+
369
+ const setBusy = (busy) => {
370
+ keyButton.disabled = busy;
371
+ tokenButton.disabled = busy;
372
+ keySelect.disabled = busy;
373
+ newKeyInput.disabled = busy || current.mode === "token";
374
+ newKeyLabelInput.disabled = busy || current.mode === "token";
375
+ saveKeyButton.disabled = busy;
376
+ removeKeyButton.disabled = busy;
377
+ accountSelect.disabled = busy;
378
+ addButton.disabled = busy;
379
+ removeButton.disabled = busy;
380
+ keyButton.style.cursor = busy ? "progress" : "pointer";
381
+ tokenButton.style.cursor = busy ? "progress" : "pointer";
382
+ saveKeyButton.style.cursor = busy ? "progress" : "pointer";
383
+ removeKeyButton.style.cursor = busy ? "progress" : "pointer";
384
+ addButton.style.cursor = busy ? "progress" : "pointer";
385
+ removeButton.style.cursor = busy ? "progress" : "pointer";
386
+ };
387
+ keyButton.addEventListener("click", async () => {
388
+ setBusy(true);
389
+ message.textContent = "正在切换…";
390
+ try {
391
+ render(await request("api-key", current.activeApiKeyId ? { keyId: current.activeApiKeyId } : undefined));
392
+ newKeyInput.focus();
393
+ } catch (error) {
394
+ message.textContent = error instanceof Error ? error.message : "切换失败";
395
+ message.style.color = "var(--dsw-text-danger, #c62828)";
396
+ } finally {
397
+ setBusy(false);
398
+ }
399
+ });
400
+ keySelect.addEventListener("change", async () => {
401
+ setBusy(true);
402
+ message.textContent = "正在切换 API Key…";
403
+ try {
404
+ render(await request("api-key", { keyId: keySelect.value }));
405
+ } catch (error) {
406
+ message.textContent = error instanceof Error ? error.message : "切换失败";
407
+ message.style.color = "var(--dsw-text-danger, #c62828)";
408
+ } finally {
409
+ setBusy(false);
410
+ }
411
+ });
412
+ saveKeyButton.addEventListener("click", async () => {
413
+ const value = newKeyInput.value.trim();
414
+ if (!value) {
415
+ message.textContent = "请输入新的 API Key";
416
+ message.style.color = "var(--dsw-text-danger, #c62828)";
417
+ newKeyInput.focus();
418
+ return;
419
+ }
420
+ setBusy(true);
421
+ saveKeyButton.textContent = "添加中…";
422
+ message.textContent = "正在保存 API Key…";
423
+ try {
424
+ const label = newKeyLabelInput.value.trim();
425
+ const next = await request("api-key/add", { key: value, ...(label ? { label } : {}) });
426
+ newKeyInput.value = "";
427
+ newKeyLabelInput.value = "";
428
+ render(next);
429
+ } catch (error) {
430
+ message.textContent = error instanceof Error ? error.message : "保存失败";
431
+ message.style.color = "var(--dsw-text-danger, #c62828)";
432
+ } finally {
433
+ setBusy(false);
434
+ }
435
+ });
436
+ newKeyInput.addEventListener("keydown", (event) => {
437
+ if (event.key === "Enter" && !saveKeyButton.disabled) {
438
+ event.preventDefault();
439
+ saveKeyButton.click();
440
+ }
441
+ });
442
+ removeKeyButton.addEventListener("click", async () => {
443
+ if (!current.activeApiKeyId || !window.confirm("确定删除当前 DSH 保存的 API Key 吗?")) return;
444
+ setBusy(true);
445
+ message.textContent = "正在删除 API Key…";
446
+ try {
447
+ render(await request("api-key/remove", { keyId: current.activeApiKeyId }));
448
+ } catch (error) {
449
+ message.textContent = error instanceof Error ? error.message : "删除失败";
450
+ message.style.color = "var(--dsw-text-danger, #c62828)";
451
+ } finally {
452
+ setBusy(false);
453
+ }
454
+ });
455
+ tokenButton.addEventListener("click", async () => {
456
+ setBusy(true);
457
+ tokenButton.textContent = "切换中…";
458
+ message.textContent = current.accounts?.length ? "正在切换令牌账号…" : "请在浏览器中完成 CodeBuddy 中国站登录";
459
+ try {
460
+ const next = await request(current.accounts?.length ? "token" : "login");
461
+ render(next);
462
+ await loadCredits(next.activeAccountId);
463
+ } catch (error) {
464
+ message.textContent = error instanceof Error ? error.message : "登录失败";
465
+ message.style.color = "var(--dsw-text-danger, #c62828)";
466
+ } finally {
467
+ setBusy(false);
468
+ }
469
+ });
470
+ addButton.addEventListener("click", async () => {
471
+ setBusy(true);
472
+ addButton.textContent = "等待浏览器登录…";
473
+ message.textContent = "请在浏览器中完成 CodeBuddy 中国站登录";
474
+ try {
475
+ const next = await request("login");
476
+ render(next);
477
+ await loadCredits(next.activeAccountId);
478
+ } catch (error) {
479
+ message.textContent = error instanceof Error ? error.message : "登录失败";
480
+ message.style.color = "var(--dsw-text-danger, #c62828)";
481
+ } finally {
482
+ setBusy(false);
483
+ }
484
+ });
485
+ accountSelect.addEventListener("change", async () => {
486
+ setBusy(true);
487
+ message.textContent = "正在切换令牌账号…";
488
+ try {
489
+ const next = await request("token", { accountId: accountSelect.value });
490
+ render(next);
491
+ await loadCredits(next.activeAccountId);
492
+ } catch (error) {
493
+ message.textContent = error instanceof Error ? error.message : "切换失败";
494
+ message.style.color = "var(--dsw-text-danger, #c62828)";
495
+ } finally {
496
+ setBusy(false);
497
+ }
498
+ });
499
+ removeButton.addEventListener("click", async () => {
500
+ if (!current.activeAccountId || !window.confirm("确定删除这个 CodeBuddy 登录账号吗?令牌将从 DSH 凭据中移除。")) return;
501
+ setBusy(true);
502
+ message.textContent = "正在删除账号…";
503
+ try {
504
+ const next = await request("remove", { accountId: current.activeAccountId });
505
+ render(next);
506
+ await loadCredits(next.activeAccountId);
507
+ } catch (error) {
508
+ message.textContent = error instanceof Error ? error.message : "删除失败";
509
+ message.style.color = "var(--dsw-text-danger, #c62828)";
510
+ } finally {
511
+ setBusy(false);
512
+ }
513
+ });
514
+ fetch(`${ROUTE}/status`, { cache: "no-store" })
515
+ .then((response) => response.json())
516
+ .then((status) => {
517
+ render(status);
518
+ return loadCredits(status.activeAccountId);
519
+ })
520
+ .catch(() => {
521
+ message.textContent = "认证状态读取失败";
522
+ message.style.color = "var(--dsw-text-danger, #c62828)";
523
+ });
524
+ }
525
+
526
+ function enhance() {
527
+ for (const input of document.querySelectorAll('input[aria-label="API 密钥"]')) {
528
+ if (isCodeBuddy(input)) mount(input);
529
+ }
530
+ }
531
+
532
+ function apply(ctx) {
533
+ ctx.effect(() => {
534
+ const observer = new MutationObserver(enhance);
535
+ observer.observe(document.body, { childList: true, subtree: true });
536
+ document.addEventListener("change", enhance, true);
537
+ enhance();
538
+ return () => {
539
+ observer.disconnect();
540
+ document.removeEventListener("change", enhance, true);
541
+ };
542
+ }, "llm-codebuddy: auth switch");
543
+ }
544
+
545
+ return { name: "dsh-llm-codebuddy-client", inject: [], apply };
546
+ },
547
+ });