dsh-llm-codebuddy 1.3.5 → 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/README.md +115 -145
- package/cli.js +21 -2
- package/client.js +427 -32
- package/codebuddy-auth.js +204 -7
- package/codebuddy-credits.js +441 -0
- package/codebuddy-web.js +340 -18
- package/index.js +40 -15
- package/package.json +3 -2
package/client.js
CHANGED
|
@@ -17,10 +17,103 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
color: "inherit",
|
|
18
18
|
cursor: "pointer",
|
|
19
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",
|
|
20
25
|
});
|
|
21
26
|
return element;
|
|
22
27
|
}
|
|
23
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
|
+
|
|
24
117
|
function isCodeBuddy(input) {
|
|
25
118
|
const editor = input.parentElement?.parentElement;
|
|
26
119
|
if (!editor) return false;
|
|
@@ -28,27 +121,122 @@ window.__ModuleLoader__.load({
|
|
|
28
121
|
return editor.parentElement?.querySelector('select[aria-label="提供方"]')?.value === "codebuddy-cn";
|
|
29
122
|
}
|
|
30
123
|
|
|
31
|
-
function
|
|
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) {
|
|
32
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;
|
|
33
176
|
input.disabled = token;
|
|
34
|
-
input.placeholder = token ? "当前使用 CodeBuddy 账号令牌" :
|
|
177
|
+
input.placeholder = token ? "当前使用 CodeBuddy 账号令牌" : "输入新的 API Key(保存到 DSH)";
|
|
178
|
+
newKeyInput.disabled = token;
|
|
179
|
+
newKeyLabelInput.disabled = token;
|
|
35
180
|
keyButton.setAttribute("aria-pressed", String(!token));
|
|
36
181
|
tokenButton.setAttribute("aria-pressed", String(token));
|
|
37
182
|
keyButton.style.background = !token ? "var(--dsw-accent-subtle, #eef4ff)" : "var(--dsw-surface-subtle, transparent)";
|
|
38
183
|
tokenButton.style.background = token ? "var(--dsw-accent-subtle, #eef4ff)" : "var(--dsw-surface-subtle, transparent)";
|
|
39
|
-
|
|
40
|
-
|
|
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)";
|
|
41
224
|
}
|
|
42
225
|
|
|
43
|
-
async function request(path) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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})`);
|
|
48
236
|
error.status = response.status;
|
|
49
237
|
throw error;
|
|
50
238
|
}
|
|
51
|
-
return
|
|
239
|
+
return result;
|
|
52
240
|
}
|
|
53
241
|
|
|
54
242
|
function mount(input) {
|
|
@@ -56,37 +244,152 @@ window.__ModuleLoader__.load({
|
|
|
56
244
|
if (!field || field.querySelector(`[${MARKER}]`)) return;
|
|
57
245
|
field.setAttribute("data-codebuddy-auth-field", "");
|
|
58
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" });
|
|
59
249
|
const controls = document.createElement("div");
|
|
60
250
|
controls.setAttribute(MARKER, "");
|
|
61
251
|
controls.setAttribute("role", "group");
|
|
62
252
|
controls.setAttribute("aria-label", "CodeBuddy 认证方式");
|
|
63
|
-
Object.assign(controls.style, {
|
|
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();
|
|
64
271
|
const keyButton = button("API Key");
|
|
65
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);
|
|
66
332
|
const message = document.createElement("span");
|
|
67
333
|
message.setAttribute("role", "status");
|
|
68
334
|
message.setAttribute("aria-live", "polite");
|
|
69
|
-
Object.assign(message.style, { fontSize: "12px", minHeight: "18px" });
|
|
70
|
-
|
|
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);
|
|
71
344
|
field.append(controls);
|
|
72
|
-
let current = { mode: "api-key", authenticated: false };
|
|
345
|
+
let current = { mode: "api-key", authenticated: false, accounts: [], apiKeys: [], credits: undefined, creditLoading: false, creditError: null, todayUsage: null, todayUsageError: null };
|
|
73
346
|
const render = (status) => {
|
|
74
347
|
current = { ...current, ...status };
|
|
75
|
-
applyMode(input, keyButton, tokenButton, message, current);
|
|
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
|
+
}
|
|
76
367
|
};
|
|
77
368
|
|
|
78
369
|
const setBusy = (busy) => {
|
|
79
370
|
keyButton.disabled = busy;
|
|
80
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;
|
|
81
380
|
keyButton.style.cursor = busy ? "progress" : "pointer";
|
|
82
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";
|
|
83
386
|
};
|
|
84
387
|
keyButton.addEventListener("click", async () => {
|
|
85
388
|
setBusy(true);
|
|
86
389
|
message.textContent = "正在切换…";
|
|
87
390
|
try {
|
|
88
|
-
render(await request("api-key"));
|
|
89
|
-
|
|
391
|
+
render(await request("api-key", current.activeApiKeyId ? { keyId: current.activeApiKeyId } : undefined));
|
|
392
|
+
newKeyInput.focus();
|
|
90
393
|
} catch (error) {
|
|
91
394
|
message.textContent = error instanceof Error ? error.message : "切换失败";
|
|
92
395
|
message.style.color = "var(--dsw-text-danger, #c62828)";
|
|
@@ -94,24 +397,126 @@ window.__ModuleLoader__.load({
|
|
|
94
397
|
setBusy(false);
|
|
95
398
|
}
|
|
96
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
|
+
});
|
|
97
455
|
tokenButton.addEventListener("click", async () => {
|
|
98
456
|
setBusy(true);
|
|
99
|
-
tokenButton.textContent = "
|
|
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 = "等待浏览器登录…";
|
|
100
473
|
message.textContent = "请在浏览器中完成 CodeBuddy 中国站登录";
|
|
101
474
|
try {
|
|
102
|
-
const
|
|
103
|
-
render(
|
|
475
|
+
const next = await request("login");
|
|
476
|
+
render(next);
|
|
477
|
+
await loadCredits(next.activeAccountId);
|
|
104
478
|
} catch (error) {
|
|
105
479
|
message.textContent = error instanceof Error ? error.message : "登录失败";
|
|
106
480
|
message.style.color = "var(--dsw-text-danger, #c62828)";
|
|
107
481
|
} finally {
|
|
108
|
-
|
|
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 {
|
|
109
511
|
setBusy(false);
|
|
110
512
|
}
|
|
111
513
|
});
|
|
112
514
|
fetch(`${ROUTE}/status`, { cache: "no-store" })
|
|
113
515
|
.then((response) => response.json())
|
|
114
|
-
.then(
|
|
516
|
+
.then((status) => {
|
|
517
|
+
render(status);
|
|
518
|
+
return loadCredits(status.activeAccountId);
|
|
519
|
+
})
|
|
115
520
|
.catch(() => {
|
|
116
521
|
message.textContent = "认证状态读取失败";
|
|
117
522
|
message.style.color = "var(--dsw-text-danger, #c62828)";
|
|
@@ -126,21 +531,11 @@ window.__ModuleLoader__.load({
|
|
|
126
531
|
|
|
127
532
|
function apply(ctx) {
|
|
128
533
|
ctx.effect(() => {
|
|
129
|
-
const style = document.createElement("style");
|
|
130
|
-
style.textContent = `
|
|
131
|
-
[data-codebuddy-auth-field] { display: grid !important; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; }
|
|
132
|
-
[data-codebuddy-auth-field] > span:first-child { grid-column: 1 / -1; }
|
|
133
|
-
[data-codebuddy-auth-field] > input[aria-label="API 密钥"] { grid-column: 1; min-width: 0; }
|
|
134
|
-
[data-codebuddy-auth-field] > [data-codebuddy-auth-switch] { grid-column: 2; }
|
|
135
|
-
@media (max-width: 640px) { [data-codebuddy-auth-field] > [data-codebuddy-auth-switch] { grid-column: 1 / -1; } }
|
|
136
|
-
`;
|
|
137
|
-
document.head.append(style);
|
|
138
534
|
const observer = new MutationObserver(enhance);
|
|
139
535
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
140
536
|
document.addEventListener("change", enhance, true);
|
|
141
537
|
enhance();
|
|
142
538
|
return () => {
|
|
143
|
-
style.remove();
|
|
144
539
|
observer.disconnect();
|
|
145
540
|
document.removeEventListener("change", enhance, true);
|
|
146
541
|
};
|