dsh-codearts 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/lib/bin.mjs +1 -1
- package/lib/client/index.d.cts +16 -0
- package/lib/client/index.js +500 -87
- package/lib/index.d.mts +5 -1
- package/lib/index.mjs +107 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
本文件记录本插件的重要改动。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## [1.2.0] - 2026-10-27
|
|
7
|
+
|
|
8
|
+
### 新增
|
|
9
|
+
|
|
10
|
+
- **对话底部那行积分可以点开看明细了**:以前只有一行聚合数字(多账号把所有号加在
|
|
11
|
+
一起),看不出是哪个号还剩多少。现在那一行是个按钮,点开是逐号明细——账号名、各自
|
|
12
|
+
剩余积分、哪个号有积分将在本周期结束时作废、最近刷新时间,右上角一个「刷新积分」。
|
|
13
|
+
它是唯一绕开 60 秒积分缓存的出口(`GET /api/codearts/status?force=1`):点了必定打
|
|
14
|
+
上游,不会拿旧数字糊弄。弹层用 fixed 定位挂在按钮上方(放不下就翻到下方),不撑宽
|
|
15
|
+
宿主那条共享的 composer dock 行。
|
|
16
|
+
- **两个显隐开关**:设置卡片新增「显示」页,分别管输入框下方那一行的**剩余积分**与
|
|
17
|
+
**上游排队位置**(`showCreditDock` / `showQueueDock`)。默认都开,老配置行为不变。
|
|
18
|
+
开关值随 `/status` 一起下发,底部条每 60 秒读一次,所以关掉最多要等一分钟才见效——
|
|
19
|
+
这句写在设置页上,不让人自己猜。
|
|
20
|
+
|
|
6
21
|
## [1.1.4] - 2026-10-26
|
|
7
22
|
|
|
8
23
|
### 修复
|
package/lib/bin.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { I as codeartsAccountsPath, M as isBenefitModel, N as CodeArtsAccountStore, d as fetchCallerIdentity, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker } from "./usage-B0NfG8vZ.mjs";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
//#region src/version.ts
|
|
5
|
-
const CODEARTS_VERSION = "1.
|
|
5
|
+
const CODEARTS_VERSION = "1.2.0";
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
/**
|
package/lib/client/index.d.cts
CHANGED
|
@@ -85,6 +85,22 @@ interface CodeArtsSettingsKey {
|
|
|
85
85
|
creditsNeedSignIn: string;
|
|
86
86
|
creditsDock: string;
|
|
87
87
|
creditsDockTitle: string;
|
|
88
|
+
creditsDockExpand: string;
|
|
89
|
+
creditsDockPanelTitle: string;
|
|
90
|
+
creditsDockRefresh: string;
|
|
91
|
+
creditsDockRefreshing: string;
|
|
92
|
+
creditsDockColAccount: string;
|
|
93
|
+
creditsDockColBalance: string;
|
|
94
|
+
creditsDockEmpty: string;
|
|
95
|
+
creditsDockExpiring: string;
|
|
96
|
+
creditsDockUpdated: string;
|
|
97
|
+
tabDisplay: string;
|
|
98
|
+
displayTitle: string;
|
|
99
|
+
displayHint: string;
|
|
100
|
+
displayCreditsToggle: string;
|
|
101
|
+
displayQueueToggle: string;
|
|
102
|
+
displayNotWritable: string;
|
|
103
|
+
displayFailed: string;
|
|
88
104
|
queueDock: string;
|
|
89
105
|
queueDockTitle: string;
|
|
90
106
|
claimButton: string;
|
package/lib/client/index.js
CHANGED
|
@@ -33,70 +33,70 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
|
|
|
33
33
|
document.head.appendChild(tag);
|
|
34
34
|
}
|
|
35
35
|
var CodeArtsPluginCard_module_css_default = {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
36
|
+
"accountName": "xoziZG_accountName",
|
|
37
|
+
"modelFill": "xoziZG_modelFill",
|
|
38
|
+
"hint": "xoziZG_hint",
|
|
39
|
+
"section": "xoziZG_section",
|
|
40
|
+
"area": "xoziZG_area",
|
|
41
|
+
"updated": "xoziZG_updated",
|
|
42
|
+
"error": "xoziZG_error",
|
|
43
|
+
"headText": "xoziZG_headText",
|
|
44
|
+
"accountCard": "xoziZG_accountCard",
|
|
45
|
+
"barFill": "xoziZG_barFill",
|
|
40
46
|
"choiceRow": "xoziZG_choiceRow",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"danger": "xoziZG_danger",
|
|
47
|
+
"field": "xoziZG_field",
|
|
48
|
+
"body": "xoziZG_body",
|
|
49
|
+
"accountEntry": "xoziZG_accountEntry",
|
|
50
|
+
"modelChart": "xoziZG_modelChart",
|
|
46
51
|
"modelRequests": "xoziZG_modelRequests",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
52
|
+
"link": "xoziZG_link",
|
|
53
|
+
"actions": "xoziZG_actions",
|
|
54
|
+
"creditRows": "xoziZG_creditRows",
|
|
55
|
+
"desc": "xoziZG_desc",
|
|
56
|
+
"balanceRow": "xoziZG_balanceRow",
|
|
57
|
+
"creditLink": "xoziZG_creditLink",
|
|
50
58
|
"modelName": "xoziZG_modelName",
|
|
59
|
+
"card": "xoziZG_card",
|
|
60
|
+
"bar": "xoziZG_bar",
|
|
61
|
+
"meta": "xoziZG_meta",
|
|
62
|
+
"sectionHead": "xoziZG_sectionHead",
|
|
63
|
+
"balanceMeta": "xoziZG_balanceMeta",
|
|
64
|
+
"modelTrack": "xoziZG_modelTrack",
|
|
65
|
+
"usageFill": "xoziZG_usageFill",
|
|
66
|
+
"choices": "xoziZG_choices",
|
|
67
|
+
"accountRowActions": "xoziZG_accountRowActions",
|
|
68
|
+
"tabPanel": "xoziZG_tabPanel",
|
|
69
|
+
"expiryRow": "xoziZG_expiryRow",
|
|
70
|
+
"tabs": "xoziZG_tabs",
|
|
71
|
+
"fieldLabel": "xoziZG_fieldLabel",
|
|
72
|
+
"manualSummary": "xoziZG_manualSummary",
|
|
73
|
+
"creditRow": "xoziZG_creditRow",
|
|
74
|
+
"creditHint": "xoziZG_creditHint",
|
|
75
|
+
"statusLine": "xoziZG_statusLine",
|
|
76
|
+
"danger": "xoziZG_danger",
|
|
77
|
+
"modelRow": "xoziZG_modelRow",
|
|
78
|
+
"claimAllRow": "xoziZG_claimAllRow",
|
|
79
|
+
"accountToolbar": "xoziZG_accountToolbar",
|
|
80
|
+
"accountHint": "xoziZG_accountHint",
|
|
51
81
|
"expiryLabel": "xoziZG_expiryLabel",
|
|
52
82
|
"name": "xoziZG_name",
|
|
53
|
-
"
|
|
83
|
+
"header": "xoziZG_header",
|
|
84
|
+
"accountRow": "xoziZG_accountRow",
|
|
85
|
+
"sectionLabel": "xoziZG_sectionLabel",
|
|
86
|
+
"count": "xoziZG_count",
|
|
54
87
|
"balanceUnit": "xoziZG_balanceUnit",
|
|
55
|
-
"section": "xoziZG_section",
|
|
56
|
-
"usageBar": "xoziZG_usageBar",
|
|
57
|
-
"accountHint": "xoziZG_accountHint",
|
|
58
|
-
"modelTrack": "xoziZG_modelTrack",
|
|
59
|
-
"accountInfo": "xoziZG_accountInfo",
|
|
60
|
-
"balanceRow": "xoziZG_balanceRow",
|
|
61
88
|
"modelTokens": "xoziZG_modelTokens",
|
|
62
89
|
"accountNameRow": "xoziZG_accountNameRow",
|
|
63
|
-
"
|
|
64
|
-
"modelRow": "xoziZG_modelRow",
|
|
65
|
-
"manualSummary": "xoziZG_manualSummary",
|
|
66
|
-
"accountName": "xoziZG_accountName",
|
|
67
|
-
"avatar": "xoziZG_avatar",
|
|
68
|
-
"choices": "xoziZG_choices",
|
|
69
|
-
"creditHint": "xoziZG_creditHint",
|
|
70
|
-
"creditRow": "xoziZG_creditRow",
|
|
71
|
-
"field": "xoziZG_field",
|
|
72
|
-
"usageFill": "xoziZG_usageFill",
|
|
73
|
-
"updated": "xoziZG_updated",
|
|
74
|
-
"expiryRow": "xoziZG_expiryRow",
|
|
75
|
-
"accountRow": "xoziZG_accountRow",
|
|
76
|
-
"error": "xoziZG_error",
|
|
77
|
-
"bar": "xoziZG_bar",
|
|
78
|
-
"header": "xoziZG_header",
|
|
79
|
-
"tabPanel": "xoziZG_tabPanel",
|
|
80
|
-
"meta": "xoziZG_meta",
|
|
81
|
-
"desc": "xoziZG_desc",
|
|
82
|
-
"balance": "xoziZG_balance",
|
|
83
|
-
"modelFill": "xoziZG_modelFill",
|
|
84
|
-
"creditRows": "xoziZG_creditRows",
|
|
85
|
-
"modelChart": "xoziZG_modelChart",
|
|
86
|
-
"headText": "xoziZG_headText",
|
|
87
|
-
"accountToolbar": "xoziZG_accountToolbar",
|
|
90
|
+
"accountInfo": "xoziZG_accountInfo",
|
|
88
91
|
"creditBlock": "xoziZG_creditBlock",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
92
|
+
"balance": "xoziZG_balance",
|
|
93
|
+
"avatar": "xoziZG_avatar",
|
|
94
|
+
"manual": "xoziZG_manual",
|
|
95
|
+
"usageBar": "xoziZG_usageBar",
|
|
96
|
+
"creditFooter": "xoziZG_creditFooter",
|
|
94
97
|
"check": "xoziZG_check",
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"accountEntry": "xoziZG_accountEntry",
|
|
98
|
-
"body": "xoziZG_body",
|
|
99
|
-
"hint": "xoziZG_hint"
|
|
98
|
+
"manualForm": "xoziZG_manualForm",
|
|
99
|
+
"modelShare": "xoziZG_modelShare"
|
|
100
100
|
};
|
|
101
101
|
//#endregion
|
|
102
102
|
//#region src/client/CodeArtsPluginCard.tsx
|
|
@@ -142,6 +142,11 @@ function claimMessagesOf(data, accountIds, t) {
|
|
|
142
142
|
for (const id of accountIds) out[id] = claimText(data, t);
|
|
143
143
|
return out;
|
|
144
144
|
}
|
|
145
|
+
/** 开关的兜底值:后端没这条路由(老版本)时按「都显示」渲染,与原有行为一致。 */
|
|
146
|
+
const DOCK_DEFAULT = {
|
|
147
|
+
credits: true,
|
|
148
|
+
queue: true
|
|
149
|
+
};
|
|
145
150
|
/** 一个 Tab 面板;未选中的用 hidden 留在 DOM 里,草稿和滚动位置都别丢。 */
|
|
146
151
|
function TabPanel({ id, labelledBy, active, children }) {
|
|
147
152
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -166,7 +171,7 @@ function claimText(data, t) {
|
|
|
166
171
|
if (data?.state === "unavailable") return t("claimUnavailable");
|
|
167
172
|
return t("claimFailed", { message: typeof data?.message === "string" && data.message !== "" ? data.message : "request failed" });
|
|
168
173
|
}
|
|
169
|
-
function formatClock(ts) {
|
|
174
|
+
function formatClock$1(ts) {
|
|
170
175
|
if (ts === void 0 || !Number.isFinite(ts)) return "";
|
|
171
176
|
try {
|
|
172
177
|
return new Date(ts).toLocaleTimeString([], {
|
|
@@ -204,7 +209,7 @@ function CreditBlock({ credits, error, t, onClaim, claiming, claimResult, justCl
|
|
|
204
209
|
used: formatCredits(c.total.used),
|
|
205
210
|
total: formatCredits(c.total.total)
|
|
206
211
|
});
|
|
207
|
-
const updated = formatClock(c?.fetchedAt);
|
|
212
|
+
const updated = formatClock$1(c?.fetchedAt);
|
|
208
213
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
209
214
|
className: CodeArtsPluginCard_module_css_default.creditBlock,
|
|
210
215
|
children: [
|
|
@@ -552,6 +557,56 @@ function CodeArtsPluginCard({ t }) {
|
|
|
552
557
|
const [manualOpen, setManualOpen] = (0, react.useState)(false);
|
|
553
558
|
const [tab, setTab] = (0, react.useState)("accounts");
|
|
554
559
|
const tabIdBase = (0, react.useId)();
|
|
560
|
+
/** 对话底部那一行的两个显隐开关(后端 = 插件 settings 里的两个布尔)。 */
|
|
561
|
+
const [dock, setDock] = (0, react.useState)(DOCK_DEFAULT);
|
|
562
|
+
const [dockWritable, setDockWritable] = (0, react.useState)(false);
|
|
563
|
+
const [dockSaving, setDockSaving] = (0, react.useState)(false);
|
|
564
|
+
const [dockError, setDockError] = (0, react.useState)(null);
|
|
565
|
+
async function refreshDock() {
|
|
566
|
+
try {
|
|
567
|
+
const res = await fetch("/api/codearts/dock", { headers: { Accept: "application/json" } });
|
|
568
|
+
if (!res.ok) return;
|
|
569
|
+
const data = await res.json();
|
|
570
|
+
if (data.dock !== void 0) setDock(data.dock);
|
|
571
|
+
setDockWritable(data.writable === true);
|
|
572
|
+
} catch {}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* 改一个开关:先按新值渲染,写失败再退回去。
|
|
576
|
+
*
|
|
577
|
+
* 这里不像模型勾选那样攒草稿再点保存 —— 只有两个布尔,攒草稿反而让用户猜
|
|
578
|
+
* 「我刚才那一下到底生效没有」。
|
|
579
|
+
*/
|
|
580
|
+
async function toggleDock(key, on) {
|
|
581
|
+
const previous = dock;
|
|
582
|
+
setDock({
|
|
583
|
+
...dock,
|
|
584
|
+
[key]: on
|
|
585
|
+
});
|
|
586
|
+
setDockSaving(true);
|
|
587
|
+
setDockError(null);
|
|
588
|
+
try {
|
|
589
|
+
const res = await fetch("/api/codearts/dock", {
|
|
590
|
+
method: "POST",
|
|
591
|
+
headers: {
|
|
592
|
+
"Content-Type": "application/json",
|
|
593
|
+
Accept: "application/json"
|
|
594
|
+
},
|
|
595
|
+
body: JSON.stringify({ [key]: on })
|
|
596
|
+
});
|
|
597
|
+
if (!res.ok) {
|
|
598
|
+
const data = await res.json().catch(() => ({}));
|
|
599
|
+
throw new Error(typeof data.error === "string" ? data.error : `HTTP ${res.status}`);
|
|
600
|
+
}
|
|
601
|
+
const data = await res.json();
|
|
602
|
+
if (data.dock !== void 0) setDock(data.dock);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
setDock(previous);
|
|
605
|
+
setDockError(error instanceof Error ? error.message : String(error));
|
|
606
|
+
} finally {
|
|
607
|
+
setDockSaving(false);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
555
610
|
/**
|
|
556
611
|
* 领每日签到积分。给 id 就只领那个号,不给就逐个号都领一遍。
|
|
557
612
|
*
|
|
@@ -620,6 +675,9 @@ function CodeArtsPluginCard({ t }) {
|
|
|
620
675
|
(0, react.useEffect)(() => {
|
|
621
676
|
refreshSelection();
|
|
622
677
|
}, []);
|
|
678
|
+
(0, react.useEffect)(() => {
|
|
679
|
+
refreshDock();
|
|
680
|
+
}, []);
|
|
623
681
|
(0, react.useEffect)(() => {
|
|
624
682
|
if (status?.status === "signed-in") refreshSelection();
|
|
625
683
|
}, [status?.status]);
|
|
@@ -838,12 +896,19 @@ function CodeArtsPluginCard({ t }) {
|
|
|
838
896
|
label: t("tabUsage"),
|
|
839
897
|
id: tabId("usage"),
|
|
840
898
|
panelId: panelId("usage")
|
|
899
|
+
},
|
|
900
|
+
display: {
|
|
901
|
+
value: "display",
|
|
902
|
+
label: t("tabDisplay"),
|
|
903
|
+
id: tabId("display"),
|
|
904
|
+
panelId: panelId("display")
|
|
841
905
|
}
|
|
842
906
|
};
|
|
843
907
|
const tabs = [
|
|
844
908
|
tabItems.accounts,
|
|
845
909
|
tabItems.models,
|
|
846
|
-
...usageAvailable ? [tabItems.usage] : []
|
|
910
|
+
...usageAvailable ? [tabItems.usage] : [],
|
|
911
|
+
tabItems.display
|
|
847
912
|
];
|
|
848
913
|
const activeTab = tabs.some((item) => item.value === tab) ? tab : "accounts";
|
|
849
914
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1138,6 +1203,57 @@ function CodeArtsPluginCard({ t }) {
|
|
|
1138
1203
|
accounts,
|
|
1139
1204
|
t
|
|
1140
1205
|
})
|
|
1206
|
+
}),
|
|
1207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabPanel, {
|
|
1208
|
+
id: panelId("display"),
|
|
1209
|
+
labelledBy: tabId("display"),
|
|
1210
|
+
active: activeTab === "display",
|
|
1211
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1212
|
+
className: CodeArtsPluginCard_module_css_default.section,
|
|
1213
|
+
children: [
|
|
1214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1215
|
+
className: CodeArtsPluginCard_module_css_default.sectionHead,
|
|
1216
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1217
|
+
className: CodeArtsPluginCard_module_css_default.sectionLabel,
|
|
1218
|
+
children: t("displayTitle")
|
|
1219
|
+
})
|
|
1220
|
+
}),
|
|
1221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1222
|
+
className: CodeArtsPluginCard_module_css_default.hint,
|
|
1223
|
+
children: t("displayHint")
|
|
1224
|
+
}),
|
|
1225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1226
|
+
className: CodeArtsPluginCard_module_css_default.choices,
|
|
1227
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1228
|
+
className: CodeArtsPluginCard_module_css_default.choiceRow,
|
|
1229
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Checkbox, {
|
|
1230
|
+
className: CodeArtsPluginCard_module_css_default.check,
|
|
1231
|
+
checked: dock.credits,
|
|
1232
|
+
disabled: !dockWritable || dockSaving,
|
|
1233
|
+
onChange: (on) => void toggleDock("credits", on),
|
|
1234
|
+
label: t("displayCreditsToggle")
|
|
1235
|
+
})
|
|
1236
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1237
|
+
className: CodeArtsPluginCard_module_css_default.choiceRow,
|
|
1238
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Checkbox, {
|
|
1239
|
+
className: CodeArtsPluginCard_module_css_default.check,
|
|
1240
|
+
checked: dock.queue,
|
|
1241
|
+
disabled: !dockWritable || dockSaving,
|
|
1242
|
+
onChange: (on) => void toggleDock("queue", on),
|
|
1243
|
+
label: t("displayQueueToggle")
|
|
1244
|
+
})
|
|
1245
|
+
})]
|
|
1246
|
+
}),
|
|
1247
|
+
!dockWritable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1248
|
+
className: CodeArtsPluginCard_module_css_default.hint,
|
|
1249
|
+
children: t("displayNotWritable")
|
|
1250
|
+
}),
|
|
1251
|
+
dockError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1252
|
+
className: CodeArtsPluginCard_module_css_default.error,
|
|
1253
|
+
children: t("displayFailed", { message: dockError })
|
|
1254
|
+
})
|
|
1255
|
+
]
|
|
1256
|
+
})
|
|
1141
1257
|
})
|
|
1142
1258
|
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1143
1259
|
className: CodeArtsPluginCard_module_css_default.section,
|
|
@@ -1197,15 +1313,20 @@ function CodeArtsPluginCard({ t }) {
|
|
|
1197
1313
|
//#endregion
|
|
1198
1314
|
//#region src/client/CodeArtsCreditDock.tsx
|
|
1199
1315
|
/**
|
|
1200
|
-
*
|
|
1316
|
+
* 对话底部的积分条 + 点开后的积分弹层。
|
|
1201
1317
|
*
|
|
1202
1318
|
* 用户最初的诉求就是「对话底部要显示积分」—— 这一条挂在
|
|
1203
1319
|
* `conversation.composer.dock` 槽位上(与宿主 session-stats strip 同位)。
|
|
1204
1320
|
* 只在当前 provider 是 codearts 时才拉取并显示一行「积分 N」,别的 provider 下
|
|
1205
1321
|
* 完全不渲染,不抢别人的行。
|
|
1206
1322
|
*
|
|
1323
|
+
* 那一行是个按钮:点开就是逐号明细(多账号下聚合数字本身没意义,得能看出是哪个号)。
|
|
1324
|
+
* 显隐由设置卡片「显示」页那两个开关管着,开关值随 `/status` 一起下发 —— 底部条本来
|
|
1325
|
+
* 就在轮询它,不必为两个布尔开一条新链路。代价是关掉开关最多要等一个轮询周期(60 秒)
|
|
1326
|
+
* 才看得见效果,这个延迟在设置卡片上写明了。
|
|
1327
|
+
*
|
|
1207
1328
|
* 积分数据走插件自己的 `/api/codearts/status`(带 TTL 缓存,见 src/credits.ts),
|
|
1208
|
-
* 不会因为这条 dock
|
|
1329
|
+
* 不会因为这条 dock 让上游多挨一次请求;弹层上那个「刷新积分」是唯一绕缓存的出口。
|
|
1209
1330
|
*/
|
|
1210
1331
|
/** 与 src/adapter.ts 的 CODEARTS_PROVIDER 对齐 —— client 不能 import host 模块。 */
|
|
1211
1332
|
const CODEARTS_PROVIDER = "codearts";
|
|
@@ -1215,6 +1336,12 @@ const QUEUE_PATH = "/api/codearts/queue";
|
|
|
1215
1336
|
const REFRESH_INTERVAL_MS = 6e4;
|
|
1216
1337
|
/** 排队位置变得快:官方客户端也是 5 秒一次;这是本机路由,打不疼上游。 */
|
|
1217
1338
|
const QUEUE_INTERVAL_MS = 5e3;
|
|
1339
|
+
/** 弹层宽度固定,好算它会不会顶出屏幕右边。 */
|
|
1340
|
+
const PANEL_WIDTH = 280;
|
|
1341
|
+
/** 弹层与那一行之间的缝隙。 */
|
|
1342
|
+
const PANEL_GAP = 8;
|
|
1343
|
+
/** 那一行在输入框下方,往上开才够空间;给弹层留的最小高度,不够就翻到下面。 */
|
|
1344
|
+
const PANEL_MIN_ABOVE = 260;
|
|
1218
1345
|
function readSelection(useProjection) {
|
|
1219
1346
|
if (useProjection === void 0) return void 0;
|
|
1220
1347
|
try {
|
|
@@ -1226,48 +1353,118 @@ function readSelection(useProjection) {
|
|
|
1226
1353
|
function isCodeArts(selection) {
|
|
1227
1354
|
return (selection?.next ?? selection?.lastUsed)?.provider === CODEARTS_PROVIDER;
|
|
1228
1355
|
}
|
|
1356
|
+
function formatNumber(value) {
|
|
1357
|
+
return new Intl.NumberFormat().format(Math.round(value));
|
|
1358
|
+
}
|
|
1359
|
+
function formatClock(ts) {
|
|
1360
|
+
if (ts === void 0 || !Number.isFinite(ts)) return "";
|
|
1361
|
+
try {
|
|
1362
|
+
return new Date(ts).toLocaleTimeString([], {
|
|
1363
|
+
hour: "2-digit",
|
|
1364
|
+
minute: "2-digit"
|
|
1365
|
+
});
|
|
1366
|
+
} catch {
|
|
1367
|
+
return "";
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/** 账号名与设置卡片同一套取法:备注 > 用户名 > id。 */
|
|
1371
|
+
function accountName(account) {
|
|
1372
|
+
return account.label?.trim() || account.userName?.trim() || account.id;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* 逐号明细。
|
|
1376
|
+
*
|
|
1377
|
+
* 老后端只有聚合的一份 `credits`,那时单账号就退回它 —— 别因为后端没升级就把
|
|
1378
|
+
* 弹层弹成一句「没有数据」。
|
|
1379
|
+
*/
|
|
1380
|
+
function rowsOf(data) {
|
|
1381
|
+
const byId = new Map((data.creditsByAccount ?? []).map((entry) => [entry.id, entry]));
|
|
1382
|
+
return (data.accounts ?? []).map((account) => {
|
|
1383
|
+
const entry = byId.get(account.id) ?? (byId.size === 0 && (data.accounts?.length ?? 0) === 1 ? {
|
|
1384
|
+
id: account.id,
|
|
1385
|
+
credits: data.credits,
|
|
1386
|
+
creditsError: data.creditsError
|
|
1387
|
+
} : void 0);
|
|
1388
|
+
const snapshot = entry?.credits;
|
|
1389
|
+
return {
|
|
1390
|
+
id: account.id,
|
|
1391
|
+
name: accountName(account),
|
|
1392
|
+
remain: snapshot?.credits.total.remain ?? null,
|
|
1393
|
+
expiring: snapshot?.credits.total.expiring ?? 0,
|
|
1394
|
+
...snapshot?.credits.fetchedAt === void 0 ? {} : { fetchedAt: snapshot.credits.fetchedAt },
|
|
1395
|
+
...entry?.creditsError === void 0 ? {} : { error: entry.creditsError }
|
|
1396
|
+
};
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
/** 把弹层钉在按钮边上:优先往上开(那一行本来就在输入框下面),放不下就翻到下面。 */
|
|
1400
|
+
function anchorOf(rect) {
|
|
1401
|
+
const above = rect.top > PANEL_MIN_ABOVE;
|
|
1402
|
+
const width = Math.min(PANEL_WIDTH, window.innerWidth - 16);
|
|
1403
|
+
return {
|
|
1404
|
+
left: Math.max(8, Math.min(rect.left, window.innerWidth - width - 8)),
|
|
1405
|
+
top: above ? rect.top - PANEL_GAP : rect.bottom + PANEL_GAP,
|
|
1406
|
+
above
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1229
1409
|
function CodeArtsCreditDock({ useProjection, t }) {
|
|
1230
1410
|
const active = isCodeArts(readSelection(useProjection));
|
|
1231
1411
|
const [remain, setRemain] = (0, react.useState)(null);
|
|
1232
1412
|
const [total, setTotal] = (0, react.useState)(0);
|
|
1413
|
+
const [rows, setRows] = (0, react.useState)([]);
|
|
1414
|
+
const [flags, setFlags] = (0, react.useState)({
|
|
1415
|
+
credits: true,
|
|
1416
|
+
queue: true
|
|
1417
|
+
});
|
|
1233
1418
|
const [queued, setQueued] = (0, react.useState)(null);
|
|
1419
|
+
const [anchor, setAnchor] = (0, react.useState)(null);
|
|
1420
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
1234
1421
|
const mounted = (0, react.useRef)(true);
|
|
1422
|
+
const triggerRef = (0, react.useRef)(null);
|
|
1423
|
+
const panelRef = (0, react.useRef)(null);
|
|
1235
1424
|
(0, react.useEffect)(() => {
|
|
1236
1425
|
mounted.current = true;
|
|
1237
1426
|
return () => {
|
|
1238
1427
|
mounted.current = false;
|
|
1239
1428
|
};
|
|
1240
1429
|
}, []);
|
|
1430
|
+
const loadStatus = (0, react.useCallback)(async (force) => {
|
|
1431
|
+
try {
|
|
1432
|
+
const res = await fetch(force ? `${STATUS_PATH}?force=1` : STATUS_PATH, {
|
|
1433
|
+
headers: { Accept: "application/json" },
|
|
1434
|
+
credentials: "same-origin"
|
|
1435
|
+
});
|
|
1436
|
+
if (!res.ok) return;
|
|
1437
|
+
const data = await res.json().catch(() => void 0);
|
|
1438
|
+
if (data === void 0 || !mounted.current) return;
|
|
1439
|
+
setFlags(data.dock ?? {
|
|
1440
|
+
credits: true,
|
|
1441
|
+
queue: true
|
|
1442
|
+
});
|
|
1443
|
+
setRows(rowsOf(data));
|
|
1444
|
+
const snapshots = (data.creditsByAccount ?? []).map((entry) => entry.credits).filter((value) => value !== void 0);
|
|
1445
|
+
const pool = snapshots.length > 0 ? snapshots : data.credits === void 0 ? [] : [data.credits];
|
|
1446
|
+
if (pool.length === 0) {
|
|
1447
|
+
setRemain(null);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
setRemain(Math.round(pool.reduce((sum, item) => sum + item.credits.total.remain, 0)));
|
|
1451
|
+
setTotal(Math.round(pool.reduce((sum, item) => sum + item.credits.total.total, 0)));
|
|
1452
|
+
} catch {}
|
|
1453
|
+
}, []);
|
|
1241
1454
|
(0, react.useEffect)(() => {
|
|
1242
1455
|
if (!active) {
|
|
1243
1456
|
setRemain(null);
|
|
1457
|
+
setRows([]);
|
|
1458
|
+
setAnchor(null);
|
|
1244
1459
|
return;
|
|
1245
1460
|
}
|
|
1246
1461
|
let timer;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
const res = await fetch(STATUS_PATH, {
|
|
1250
|
-
headers: { Accept: "application/json" },
|
|
1251
|
-
credentials: "same-origin"
|
|
1252
|
-
});
|
|
1253
|
-
if (!res.ok) return;
|
|
1254
|
-
const data = await res.json().catch(() => void 0);
|
|
1255
|
-
if (data === void 0) return;
|
|
1256
|
-
const snapshots = (data.creditsByAccount ?? []).map((entry) => entry.credits).filter((value) => value !== void 0);
|
|
1257
|
-
const pool = snapshots.length > 0 ? snapshots : data.credits === void 0 ? [] : [data.credits];
|
|
1258
|
-
if (pool.length === 0) return;
|
|
1259
|
-
if (mounted.current) {
|
|
1260
|
-
setRemain(Math.round(pool.reduce((sum, item) => sum + item.credits.total.remain, 0)));
|
|
1261
|
-
setTotal(Math.round(pool.reduce((sum, item) => sum + item.credits.total.total, 0)));
|
|
1262
|
-
}
|
|
1263
|
-
} catch {}
|
|
1264
|
-
};
|
|
1265
|
-
refresh();
|
|
1266
|
-
timer = window.setInterval(refresh, REFRESH_INTERVAL_MS);
|
|
1462
|
+
loadStatus(false);
|
|
1463
|
+
timer = window.setInterval(() => void loadStatus(false), REFRESH_INTERVAL_MS);
|
|
1267
1464
|
return () => {
|
|
1268
1465
|
if (timer !== void 0) window.clearInterval(timer);
|
|
1269
1466
|
};
|
|
1270
|
-
}, [active]);
|
|
1467
|
+
}, [active, loadStatus]);
|
|
1271
1468
|
(0, react.useEffect)(() => {
|
|
1272
1469
|
if (!active) {
|
|
1273
1470
|
setQueued(null);
|
|
@@ -1291,16 +1488,59 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1291
1488
|
if (timer !== void 0) window.clearInterval(timer);
|
|
1292
1489
|
};
|
|
1293
1490
|
}, [active]);
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1491
|
+
const close = (0, react.useCallback)(() => setAnchor(null), []);
|
|
1492
|
+
(0, react.useEffect)(() => {
|
|
1493
|
+
if (anchor === null) return;
|
|
1494
|
+
const onPointerDown = (event) => {
|
|
1495
|
+
const target = event.target;
|
|
1496
|
+
if (target === null) return;
|
|
1497
|
+
if (panelRef.current?.contains(target) === true || triggerRef.current?.contains(target) === true) return;
|
|
1498
|
+
close();
|
|
1499
|
+
};
|
|
1500
|
+
const onKeyDown = (event) => {
|
|
1501
|
+
if (event.key === "Escape") close();
|
|
1502
|
+
};
|
|
1503
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
1504
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1505
|
+
window.addEventListener("resize", close);
|
|
1506
|
+
window.addEventListener("scroll", close, true);
|
|
1507
|
+
return () => {
|
|
1508
|
+
window.removeEventListener("pointerdown", onPointerDown, true);
|
|
1509
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
1510
|
+
window.removeEventListener("resize", close);
|
|
1511
|
+
window.removeEventListener("scroll", close, true);
|
|
1512
|
+
};
|
|
1513
|
+
}, [anchor, close]);
|
|
1514
|
+
if (!active) return null;
|
|
1515
|
+
const showCredits = flags.credits && remain !== null;
|
|
1516
|
+
const showQueue = flags.queue && queued !== null;
|
|
1517
|
+
if (!showCredits && !showQueue) return null;
|
|
1518
|
+
const queueTitle = queued === null ? void 0 : t("queueDockTitle", {
|
|
1297
1519
|
model: queued.model,
|
|
1298
1520
|
position: queued.position,
|
|
1299
1521
|
length: queued.length
|
|
1300
|
-
})
|
|
1522
|
+
});
|
|
1523
|
+
const creditsTitle = showCredits ? t("creditsDockTitle", {
|
|
1301
1524
|
remain: remain ?? 0,
|
|
1302
1525
|
total
|
|
1303
|
-
});
|
|
1526
|
+
}) : void 0;
|
|
1527
|
+
const fetchedAt = rows.reduce((latest, row) => Math.max(latest, row.fetchedAt ?? 0), 0);
|
|
1528
|
+
const lastUpdated = fetchedAt > 0 ? formatClock(fetchedAt) : "";
|
|
1529
|
+
const toggle = (event) => {
|
|
1530
|
+
if (anchor !== null) {
|
|
1531
|
+
close();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
setAnchor(anchorOf(event.currentTarget.getBoundingClientRect()));
|
|
1535
|
+
};
|
|
1536
|
+
const forceRefresh = async () => {
|
|
1537
|
+
setRefreshing(true);
|
|
1538
|
+
try {
|
|
1539
|
+
await loadStatus(true);
|
|
1540
|
+
} finally {
|
|
1541
|
+
setRefreshing(false);
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1304
1544
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1305
1545
|
style: {
|
|
1306
1546
|
display: "inline-flex",
|
|
@@ -1314,8 +1554,149 @@ function CodeArtsCreditDock({ useProjection, t }) {
|
|
|
1314
1554
|
flex: "0 0 auto",
|
|
1315
1555
|
minWidth: "max-content"
|
|
1316
1556
|
},
|
|
1317
|
-
title,
|
|
1318
|
-
children: [
|
|
1557
|
+
title: queueTitle ?? creditsTitle,
|
|
1558
|
+
children: [
|
|
1559
|
+
showQueue && queued !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("queueDock", { position: queued.position }) }),
|
|
1560
|
+
showCredits && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1561
|
+
ref: triggerRef,
|
|
1562
|
+
type: "button",
|
|
1563
|
+
"aria-expanded": anchor !== null,
|
|
1564
|
+
"aria-label": t("creditsDockExpand"),
|
|
1565
|
+
title: creditsTitle,
|
|
1566
|
+
onClick: toggle,
|
|
1567
|
+
style: {
|
|
1568
|
+
display: "inline-flex",
|
|
1569
|
+
alignItems: "center",
|
|
1570
|
+
gap: 4,
|
|
1571
|
+
margin: 0,
|
|
1572
|
+
padding: 0,
|
|
1573
|
+
border: 0,
|
|
1574
|
+
background: "none",
|
|
1575
|
+
color: "inherit",
|
|
1576
|
+
font: "inherit",
|
|
1577
|
+
lineHeight: "inherit",
|
|
1578
|
+
cursor: "pointer"
|
|
1579
|
+
},
|
|
1580
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("creditsDock", { remain: formatNumber(remain ?? 0) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1581
|
+
"aria-hidden": "true",
|
|
1582
|
+
style: {
|
|
1583
|
+
opacity: .6,
|
|
1584
|
+
fontSize: "0.8em"
|
|
1585
|
+
},
|
|
1586
|
+
children: "▾"
|
|
1587
|
+
})]
|
|
1588
|
+
}),
|
|
1589
|
+
anchor !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1590
|
+
ref: panelRef,
|
|
1591
|
+
role: "dialog",
|
|
1592
|
+
"aria-label": t("creditsDockPanelTitle"),
|
|
1593
|
+
style: {
|
|
1594
|
+
position: "fixed",
|
|
1595
|
+
left: anchor.left,
|
|
1596
|
+
top: anchor.top,
|
|
1597
|
+
transform: anchor.above ? "translateY(-100%)" : void 0,
|
|
1598
|
+
width: PANEL_WIDTH,
|
|
1599
|
+
maxWidth: "calc(100vw - 16px)",
|
|
1600
|
+
zIndex: 1e3,
|
|
1601
|
+
display: "flex",
|
|
1602
|
+
flexDirection: "column",
|
|
1603
|
+
gap: 8,
|
|
1604
|
+
padding: 12,
|
|
1605
|
+
boxSizing: "border-box",
|
|
1606
|
+
borderRadius: "var(--dsw-radius-lg, 10px)",
|
|
1607
|
+
border: "0.5px solid var(--dsw-alias-settings-card-stroke, rgba(127, 127, 127, 0.28))",
|
|
1608
|
+
background: "var(--dsw-alias-settings-card-fill, rgba(32, 32, 32, 0.96))",
|
|
1609
|
+
boxShadow: "0 12px 32px rgba(0, 0, 0, 0.28)",
|
|
1610
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1611
|
+
fontSize: "var(--dsh-content-font-size-secondary, 13px)",
|
|
1612
|
+
lineHeight: "18px",
|
|
1613
|
+
whiteSpace: "normal"
|
|
1614
|
+
},
|
|
1615
|
+
children: [
|
|
1616
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1617
|
+
style: {
|
|
1618
|
+
display: "flex",
|
|
1619
|
+
alignItems: "center",
|
|
1620
|
+
justifyContent: "space-between",
|
|
1621
|
+
gap: 8
|
|
1622
|
+
},
|
|
1623
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1624
|
+
style: { fontWeight: 600 },
|
|
1625
|
+
children: t("creditsDockPanelTitle")
|
|
1626
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1627
|
+
variant: "outline",
|
|
1628
|
+
size: "sm",
|
|
1629
|
+
disabled: refreshing,
|
|
1630
|
+
onClick: () => void forceRefresh(),
|
|
1631
|
+
children: refreshing ? t("creditsDockRefreshing") : t("creditsDockRefresh")
|
|
1632
|
+
})]
|
|
1633
|
+
}),
|
|
1634
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1635
|
+
style: {
|
|
1636
|
+
display: "grid",
|
|
1637
|
+
gridTemplateColumns: "minmax(0, 1fr) auto",
|
|
1638
|
+
gap: "4px 12px",
|
|
1639
|
+
alignItems: "center",
|
|
1640
|
+
borderTop: "0.5px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.22))",
|
|
1641
|
+
paddingTop: 8,
|
|
1642
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
1643
|
+
},
|
|
1644
|
+
children: [
|
|
1645
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("creditsDockColAccount") }),
|
|
1646
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1647
|
+
style: { justifySelf: "end" },
|
|
1648
|
+
children: t("creditsDockColBalance")
|
|
1649
|
+
}),
|
|
1650
|
+
rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1651
|
+
style: { gridColumn: "1 / -1" },
|
|
1652
|
+
children: t("creditsDockEmpty")
|
|
1653
|
+
}),
|
|
1654
|
+
rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1655
|
+
style: { display: "contents" },
|
|
1656
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1657
|
+
style: {
|
|
1658
|
+
display: "flex",
|
|
1659
|
+
alignItems: "center",
|
|
1660
|
+
gap: 6,
|
|
1661
|
+
minWidth: 0
|
|
1662
|
+
},
|
|
1663
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: row.remain === null ? "warning" : "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1664
|
+
style: {
|
|
1665
|
+
overflow: "hidden",
|
|
1666
|
+
textOverflow: "ellipsis",
|
|
1667
|
+
whiteSpace: "nowrap",
|
|
1668
|
+
color: "var(--dsw-alias-label-primary)"
|
|
1669
|
+
},
|
|
1670
|
+
title: row.error === void 0 ? row.name : row.error,
|
|
1671
|
+
children: row.name
|
|
1672
|
+
})]
|
|
1673
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1674
|
+
style: {
|
|
1675
|
+
justifySelf: "end",
|
|
1676
|
+
fontVariantNumeric: "tabular-nums"
|
|
1677
|
+
},
|
|
1678
|
+
children: row.remain === null ? "—" : formatNumber(row.remain)
|
|
1679
|
+
})]
|
|
1680
|
+
}, row.id))
|
|
1681
|
+
]
|
|
1682
|
+
}),
|
|
1683
|
+
rows.filter((row) => row.expiring > 0).map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1684
|
+
style: {
|
|
1685
|
+
margin: 0,
|
|
1686
|
+
color: "var(--dsw-alias-state-warn-label, var(--dsw-alias-label-secondary))"
|
|
1687
|
+
},
|
|
1688
|
+
children: t("creditsDockExpiring", {
|
|
1689
|
+
account: row.name,
|
|
1690
|
+
expiring: formatNumber(row.expiring)
|
|
1691
|
+
})
|
|
1692
|
+
}, `${row.id}-expiring`)),
|
|
1693
|
+
lastUpdated !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1694
|
+
style: { color: "var(--dsw-alias-label-tertiary)" },
|
|
1695
|
+
children: t("creditsDockUpdated", { time: lastUpdated })
|
|
1696
|
+
})
|
|
1697
|
+
]
|
|
1698
|
+
})
|
|
1699
|
+
]
|
|
1319
1700
|
});
|
|
1320
1701
|
}
|
|
1321
1702
|
//#endregion
|
|
@@ -1405,6 +1786,22 @@ const zh = {
|
|
|
1405
1786
|
creditsNeedSignIn: "这个账号需要重新登录",
|
|
1406
1787
|
creditsDock: "积分 {remain}",
|
|
1407
1788
|
creditsDockTitle: "CodeArts 剩余积分 {remain} / {total}",
|
|
1789
|
+
creditsDockExpand: "点开看各账号积分明细",
|
|
1790
|
+
creditsDockPanelTitle: "CodeArts 积分",
|
|
1791
|
+
creditsDockRefresh: "刷新积分",
|
|
1792
|
+
creditsDockRefreshing: "刷新中…",
|
|
1793
|
+
creditsDockColAccount: "账号",
|
|
1794
|
+
creditsDockColBalance: "剩余积分",
|
|
1795
|
+
creditsDockEmpty: "暂时没有积分数据",
|
|
1796
|
+
creditsDockExpiring: "{account}:{expiring} 积分将在本周期结束时作废",
|
|
1797
|
+
creditsDockUpdated: "最近刷新 {time}",
|
|
1798
|
+
tabDisplay: "显示",
|
|
1799
|
+
displayTitle: "对话底部显示",
|
|
1800
|
+
displayHint: "这两项只管输入框下方那一行,设置卡片里的积分明细不受影响。底部那条每 60 秒读一次设置,所以关掉最多要等一分钟才见效。",
|
|
1801
|
+
displayCreditsToggle: "显示剩余积分(点开可看各账号明细)",
|
|
1802
|
+
displayQueueToggle: "显示上游排队位置",
|
|
1803
|
+
displayNotWritable: "这个宿主没给插件开配置存储,开关只能看不能改。",
|
|
1804
|
+
displayFailed: "保存失败:{message}",
|
|
1408
1805
|
queueDock: "排队第 {position} 位",
|
|
1409
1806
|
queueDockTitle: "上游排队:{model} · 第 {position} 位 / 共 {length} 条",
|
|
1410
1807
|
claimButton: "签到领积分",
|
|
@@ -1502,6 +1899,22 @@ const en = {
|
|
|
1502
1899
|
creditsNeedSignIn: "This account needs signing in again",
|
|
1503
1900
|
creditsDock: "Credits {remain}",
|
|
1504
1901
|
creditsDockTitle: "CodeArts credits left {remain} / {total}",
|
|
1902
|
+
creditsDockExpand: "Show per-account credit details",
|
|
1903
|
+
creditsDockPanelTitle: "CodeArts credits",
|
|
1904
|
+
creditsDockRefresh: "Refresh credits",
|
|
1905
|
+
creditsDockRefreshing: "Refreshing…",
|
|
1906
|
+
creditsDockColAccount: "Account",
|
|
1907
|
+
creditsDockColBalance: "Credits left",
|
|
1908
|
+
creditsDockEmpty: "No credit data yet",
|
|
1909
|
+
creditsDockExpiring: "{account}: {expiring} credits expire when this cycle ends",
|
|
1910
|
+
creditsDockUpdated: "Last updated {time}",
|
|
1911
|
+
tabDisplay: "Display",
|
|
1912
|
+
displayTitle: "Under the chat input box",
|
|
1913
|
+
displayHint: "These two only control that line under the input box — the credit details in this card are unaffected. The bar re-reads settings every 60 seconds, so hiding it can take up to a minute.",
|
|
1914
|
+
displayCreditsToggle: "Show remaining credits (click it for per-account details)",
|
|
1915
|
+
displayQueueToggle: "Show the upstream queue position",
|
|
1916
|
+
displayNotWritable: "This host gives the plugin nowhere to persist settings, so the switches are read-only.",
|
|
1917
|
+
displayFailed: "Failed to save: {message}",
|
|
1505
1918
|
queueDock: "Queued #{position}",
|
|
1506
1919
|
queueDockTitle: "Upstream queue: {model} · position {position} of {length}",
|
|
1507
1920
|
claimButton: "Claim daily credits",
|
package/lib/index.d.mts
CHANGED
|
@@ -1572,10 +1572,14 @@ declare const name = "llm-codearts";
|
|
|
1572
1572
|
declare const inject: string[];
|
|
1573
1573
|
/** Settings namespace owning the configuration card. */
|
|
1574
1574
|
declare const CODEARTS_SETTINGS_NS: SettingsNamespace;
|
|
1575
|
-
/** Plugin configuration
|
|
1575
|
+
/** Plugin configuration. */
|
|
1576
1576
|
interface Config {
|
|
1577
1577
|
/** Allowlist of CodeArts model ids offered in the model pickers. */
|
|
1578
1578
|
enabledModels?: string[];
|
|
1579
|
+
/** 对话输入框下方那行「积分 N」是否显示。缺省 = 显示(老配置不改行为)。 */
|
|
1580
|
+
showCreditDock?: boolean;
|
|
1581
|
+
/** 等上游开流时那行「排队第 N 位」是否显示。缺省 = 显示。 */
|
|
1582
|
+
showQueueDock?: boolean;
|
|
1579
1583
|
}
|
|
1580
1584
|
declare const Config: z<Config>;
|
|
1581
1585
|
declare function apply(ctx: Context, rawConfig: Config): void;
|
package/lib/index.mjs
CHANGED
|
@@ -1404,6 +1404,11 @@ const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
|
|
|
1404
1404
|
const CODEARTS_REFRESH_MODELS_PATH = "/api/codearts/refresh-models";
|
|
1405
1405
|
/** GET /api/codearts/queue — 上游排队位置(只在等开流期间有值,底部积分条旁边展示)。 */
|
|
1406
1406
|
const CODEARTS_QUEUE_PATH = "/api/codearts/queue";
|
|
1407
|
+
/**
|
|
1408
|
+
* GET/POST /api/codearts/dock — 对话底部那一行的显隐开关。
|
|
1409
|
+
* GET 给当前值与 writable,POST 收 `{credits?, queue?}` 写进 settings。
|
|
1410
|
+
*/
|
|
1411
|
+
const CODEARTS_DOCK_PATH = "/api/codearts/dock";
|
|
1407
1412
|
/** 最近出站请求的**形状**摘要(只有 role 名与计数,不含内容),排障用。 */
|
|
1408
1413
|
const CODEARTS_DEBUG_PATH = "/api/codearts/debug";
|
|
1409
1414
|
/**
|
|
@@ -1442,6 +1447,11 @@ function choicesOf(deps) {
|
|
|
1442
1447
|
};
|
|
1443
1448
|
});
|
|
1444
1449
|
}
|
|
1450
|
+
/** 开关的默认值:没有 settings provider(或老配置没这两个键)时保持原有行为 —— 都显示。 */
|
|
1451
|
+
const DEFAULT_DOCK_FLAGS = {
|
|
1452
|
+
credits: true,
|
|
1453
|
+
queue: true
|
|
1454
|
+
};
|
|
1445
1455
|
/**
|
|
1446
1456
|
* 把逐账号的领取结果汇总成一条:HTTP 层只看 ok,卡片看 results 逐号展示。
|
|
1447
1457
|
* 有一个号失败就算整批 ok=false,但别的号领到的结果照样在 results 里。
|
|
@@ -1563,11 +1573,15 @@ function usageOf(deps) {
|
|
|
1563
1573
|
*
|
|
1564
1574
|
* 积分是「锦上添花」:拉不到也绝不能把状态接口本身拖垮,所以失败一律降级成
|
|
1565
1575
|
* `creditsError` 交给卡片显示原因(超时由注入方在自己的闭包里保证)。
|
|
1576
|
+
*
|
|
1577
|
+
* `force` 是给对话底部弹层那个「刷新积分」按钮用的:不强制的话它会命中 60 秒缓存,
|
|
1578
|
+
* 按钮点了数字不动 —— 那还不如不给这个按钮。
|
|
1566
1579
|
*/
|
|
1567
|
-
async function creditsOf(deps) {
|
|
1568
|
-
|
|
1580
|
+
async function creditsOf(deps, force) {
|
|
1581
|
+
const load = force ? deps.refreshCredits ?? deps.credits : deps.credits;
|
|
1582
|
+
if (load === void 0) return {};
|
|
1569
1583
|
try {
|
|
1570
|
-
const entries = await
|
|
1584
|
+
const entries = await load();
|
|
1571
1585
|
const primary = entries.find((entry) => entry.credits !== void 0) ?? entries[0];
|
|
1572
1586
|
return {
|
|
1573
1587
|
...primary?.credits === void 0 ? {} : { credits: primary.credits },
|
|
@@ -1578,7 +1592,7 @@ async function creditsOf(deps) {
|
|
|
1578
1592
|
return { creditsError: safeMessage(error) };
|
|
1579
1593
|
}
|
|
1580
1594
|
}
|
|
1581
|
-
async function buildStatus(deps) {
|
|
1595
|
+
async function buildStatus(deps, force = false) {
|
|
1582
1596
|
const models = deps.models().map((m) => ({
|
|
1583
1597
|
id: m.id,
|
|
1584
1598
|
name: m.name
|
|
@@ -1586,6 +1600,7 @@ async function buildStatus(deps) {
|
|
|
1586
1600
|
const accounts = await deps.store.list();
|
|
1587
1601
|
const cred = await deps.store.current();
|
|
1588
1602
|
const usage = usageOf(deps);
|
|
1603
|
+
const dock = deps.dock === void 0 ? {} : { dock: deps.dock() };
|
|
1589
1604
|
const piAi = await probePiAiCompat();
|
|
1590
1605
|
const compat = piAi === void 0 ? {} : { piAiError: piAi };
|
|
1591
1606
|
if (accounts.length === 0 || cred === void 0) return {
|
|
@@ -1593,6 +1608,7 @@ async function buildStatus(deps) {
|
|
|
1593
1608
|
models,
|
|
1594
1609
|
accounts,
|
|
1595
1610
|
usage,
|
|
1611
|
+
...dock,
|
|
1596
1612
|
...compat
|
|
1597
1613
|
};
|
|
1598
1614
|
const usable = accounts.some(accountUsable);
|
|
@@ -1601,7 +1617,8 @@ async function buildStatus(deps) {
|
|
|
1601
1617
|
...cred.storedAt === void 0 ? {} : { storedAt: cred.storedAt },
|
|
1602
1618
|
models,
|
|
1603
1619
|
accounts,
|
|
1604
|
-
usage
|
|
1620
|
+
usage,
|
|
1621
|
+
...dock
|
|
1605
1622
|
};
|
|
1606
1623
|
if (!usable) return {
|
|
1607
1624
|
status: "expired",
|
|
@@ -1612,7 +1629,7 @@ async function buildStatus(deps) {
|
|
|
1612
1629
|
status: "signed-in",
|
|
1613
1630
|
...identity,
|
|
1614
1631
|
...compat,
|
|
1615
|
-
...await creditsOf(deps)
|
|
1632
|
+
...await creditsOf(deps, force)
|
|
1616
1633
|
};
|
|
1617
1634
|
}
|
|
1618
1635
|
/** GET /api/codearts/status */
|
|
@@ -1627,12 +1644,65 @@ function statusHandler(deps) {
|
|
|
1627
1644
|
return;
|
|
1628
1645
|
}
|
|
1629
1646
|
try {
|
|
1630
|
-
json(res, 200, await buildStatus(deps));
|
|
1647
|
+
json(res, 200, await buildStatus(deps, new URL(req.url ?? "", "http://localhost").searchParams.get("force") === "1"));
|
|
1631
1648
|
} catch (error) {
|
|
1632
1649
|
json(res, 500, { error: safeMessage(error) });
|
|
1633
1650
|
}
|
|
1634
1651
|
};
|
|
1635
1652
|
}
|
|
1653
|
+
/**
|
|
1654
|
+
* GET/POST /api/codearts/dock — 对话底部那一行的显隐开关读写。
|
|
1655
|
+
*
|
|
1656
|
+
* 和 enabled-models 同一套规矩:GET 给当前值 + writable,POST 只收布尔、
|
|
1657
|
+
* 没接 settings provider 时回 403 让卡片把勾选框置灰。
|
|
1658
|
+
*/
|
|
1659
|
+
function dockHandler(deps) {
|
|
1660
|
+
return async (req, res) => {
|
|
1661
|
+
if (!loopbackRequest(req)) {
|
|
1662
|
+
json(res, 403, { error: "request-not-trusted" });
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
const current = deps.dock?.() ?? DEFAULT_DOCK_FLAGS;
|
|
1666
|
+
if (req.method === "GET") {
|
|
1667
|
+
json(res, 200, {
|
|
1668
|
+
dock: current,
|
|
1669
|
+
writable: deps.setDock !== void 0
|
|
1670
|
+
});
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
if (req.method === "POST") {
|
|
1674
|
+
if (deps.setDock === void 0) {
|
|
1675
|
+
json(res, 403, { error: "dock-not-writable" });
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const type = req.headers["content-type"];
|
|
1679
|
+
if (typeof type !== "string" || !type.trim().toLowerCase().startsWith("application/json")) {
|
|
1680
|
+
json(res, 415, { error: "content-type must be application/json" });
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
const body = JSON.parse(await readBody(req));
|
|
1685
|
+
const touched = ["credits", "queue"].filter((key) => body[key] !== void 0);
|
|
1686
|
+
if (touched.length === 0 || touched.some((key) => typeof body[key] !== "boolean")) {
|
|
1687
|
+
json(res, 400, { error: "expected {\"credits\"?: boolean, \"queue\"?: boolean}" });
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (!await deps.setDock(Object.fromEntries(touched.map((key) => [key, body[key]])))) {
|
|
1691
|
+
json(res, 403, { error: "dock-not-writable" });
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
json(res, 200, {
|
|
1695
|
+
dock: deps.dock?.() ?? DEFAULT_DOCK_FLAGS,
|
|
1696
|
+
writable: true
|
|
1697
|
+
});
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
json(res, 500, { error: safeMessage(error) });
|
|
1700
|
+
}
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
json(res, 405, { error: "method not allowed" });
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1636
1706
|
/** GET /api/codearts/models — currently offered model ids (honours allowlist). */
|
|
1637
1707
|
function modelsHandler(deps) {
|
|
1638
1708
|
return async (req, res) => {
|
|
@@ -1935,6 +2005,11 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1935
2005
|
path: CODEARTS_QUEUE_PATH,
|
|
1936
2006
|
handler: queueHandler(deps)
|
|
1937
2007
|
});
|
|
2008
|
+
const disposeDock = ctx.webServer.register({
|
|
2009
|
+
kind: "exact",
|
|
2010
|
+
path: CODEARTS_DOCK_PATH,
|
|
2011
|
+
handler: dockHandler(deps)
|
|
2012
|
+
});
|
|
1938
2013
|
const disposeDebug = ctx.webServer.register({
|
|
1939
2014
|
kind: "exact",
|
|
1940
2015
|
path: CODEARTS_DEBUG_PATH,
|
|
@@ -1951,6 +2026,7 @@ function registerCodeArtsStatusRoute(ctx, deps) {
|
|
|
1951
2026
|
disposeClaim();
|
|
1952
2027
|
disposeRefresh();
|
|
1953
2028
|
disposeQueue();
|
|
2029
|
+
disposeDock();
|
|
1954
2030
|
disposeDebug();
|
|
1955
2031
|
for (const s of activeLogins.values()) s.close();
|
|
1956
2032
|
activeLogins.clear();
|
|
@@ -2115,7 +2191,11 @@ function unwrapConfig(value) {
|
|
|
2115
2191
|
for (const [key, field] of Object.entries(value)) out[key] = unwrapVolatileValue(field);
|
|
2116
2192
|
return out;
|
|
2117
2193
|
}
|
|
2118
|
-
const Config = z.object({
|
|
2194
|
+
const Config = z.object({
|
|
2195
|
+
enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile(),
|
|
2196
|
+
showCreditDock: z.boolean().default(true).description("Show the remaining-credits line under the chat input box").volatile(),
|
|
2197
|
+
showQueueDock: z.boolean().default(true).description("Show the upstream queue position under the chat input box").volatile()
|
|
2198
|
+
});
|
|
2119
2199
|
function settingsCompatNamespace(ctx) {
|
|
2120
2200
|
return resolveSettingsNamespaceCompat(ctx, ctx.get("settings"), CODEARTS_SETTINGS_NS);
|
|
2121
2201
|
}
|
|
@@ -2187,6 +2267,19 @@ function apply(ctx, rawConfig) {
|
|
|
2187
2267
|
await settings.update(settingsCompatNamespace(ctx), { enabledModels: [...ids] });
|
|
2188
2268
|
return true;
|
|
2189
2269
|
};
|
|
2270
|
+
const dock = () => ({
|
|
2271
|
+
credits: current().showCreditDock !== false,
|
|
2272
|
+
queue: current().showQueueDock !== false
|
|
2273
|
+
});
|
|
2274
|
+
const setDock = async (patch) => {
|
|
2275
|
+
const settings = ctx.get("settings");
|
|
2276
|
+
if (settings === void 0) return false;
|
|
2277
|
+
await settings.update(settingsCompatNamespace(ctx), {
|
|
2278
|
+
...patch.credits === void 0 ? {} : { showCreditDock: patch.credits },
|
|
2279
|
+
...patch.queue === void 0 ? {} : { showQueueDock: patch.queue }
|
|
2280
|
+
});
|
|
2281
|
+
return true;
|
|
2282
|
+
};
|
|
2190
2283
|
/** 手动「刷新模型列表」的预算:三路来源各打一次,比每小时的后台刷新更该给足时间。 */
|
|
2191
2284
|
const MANUAL_REFRESH_MS = 6e4;
|
|
2192
2285
|
ctx.inject(["webServer"], (webCtx) => registerCodeArtsStatusRoute(webCtx, {
|
|
@@ -2200,6 +2293,12 @@ function apply(ctx, rawConfig) {
|
|
|
2200
2293
|
queue: () => queue.snapshot(),
|
|
2201
2294
|
recentRequests: () => recentRequestShapes(),
|
|
2202
2295
|
credits: (signal) => loadCredits(signal),
|
|
2296
|
+
refreshCredits: (signal) => {
|
|
2297
|
+
credits.invalidate();
|
|
2298
|
+
return loadCredits(signal);
|
|
2299
|
+
},
|
|
2300
|
+
dock,
|
|
2301
|
+
setDock,
|
|
2203
2302
|
claim: async (accountId) => {
|
|
2204
2303
|
const ids = accountId === void 0 ? await store.ids() : [accountId];
|
|
2205
2304
|
if (ids.length === 0) return {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-codearts",
|
|
3
3
|
"displayName": "DSH CodeArts Connect",
|
|
4
4
|
"description": "把华为云 CodeArts 接入 DeepSeek Harness —— 内置 OAuth2 登录(PKCE + DPoP),登录后即可在 DSH 对话窗口使用 CodeArts 的模型。",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.2.0",
|
|
6
6
|
"packageManager": "pnpm@11.24.0",
|
|
7
7
|
"author": "elegant01",
|
|
8
8
|
"keywords": [
|