dsh-token-gauge 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.mjs +42 -75
- package/package.json +80 -84
- package/src/client.ts +302 -334
package/lib/client.mjs
CHANGED
|
@@ -32,6 +32,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
var client_exports = {};
|
|
33
33
|
__export(client_exports, {
|
|
34
34
|
apply: () => apply,
|
|
35
|
+
inject: () => inject,
|
|
35
36
|
name: () => name
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(client_exports);
|
|
@@ -943,90 +944,56 @@ var gaugeRef = (el) => {
|
|
|
943
944
|
mountGauge(el);
|
|
944
945
|
}
|
|
945
946
|
};
|
|
946
|
-
function
|
|
947
|
-
return import_react.default.createElement("div", {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
d: "M8 11.6 10.4 7.2",
|
|
967
|
-
stroke: "currentColor",
|
|
968
|
-
strokeWidth: 1.5,
|
|
969
|
-
strokeLinecap: "round"
|
|
947
|
+
function GaugePanel() {
|
|
948
|
+
return import_react.default.createElement("div", {
|
|
949
|
+
ref: gaugeRef,
|
|
950
|
+
className: "tg-panel-root",
|
|
951
|
+
style: {
|
|
952
|
+
position: "fixed",
|
|
953
|
+
right: "0",
|
|
954
|
+
bottom: "0",
|
|
955
|
+
width: "420px",
|
|
956
|
+
height: "33vh",
|
|
957
|
+
zIndex: 50,
|
|
958
|
+
pointerEvents: "auto",
|
|
959
|
+
boxSizing: "border-box",
|
|
960
|
+
overflow: "hidden",
|
|
961
|
+
borderTop: "1px solid var(--dsw-alias-hairline)",
|
|
962
|
+
borderLeft: "1px solid var(--dsw-alias-hairline)",
|
|
963
|
+
background: "var(--dsw-alias-bg-base)",
|
|
964
|
+
color: "var(--dsw-alias-label-primary)",
|
|
965
|
+
boxShadow: "0 0 24px rgba(0,0,0,0.28)"
|
|
966
|
+
}
|
|
970
967
|
});
|
|
971
|
-
|
|
972
|
-
};
|
|
973
|
-
var TAB_ORDER = 90;
|
|
968
|
+
}
|
|
974
969
|
var name = PLUGIN_ID;
|
|
970
|
+
var inject = ["slots"];
|
|
975
971
|
function apply(ctx) {
|
|
976
|
-
let
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
try {
|
|
982
|
-
unregister = sidebar.registerTab({
|
|
972
|
+
let disposeRegistration = void 0;
|
|
973
|
+
const doRegister = () => {
|
|
974
|
+
disposeRegistration = ctx.slots.register(
|
|
975
|
+
{
|
|
976
|
+
name: "shell.overlay",
|
|
983
977
|
id: PLUGIN_ID,
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
});
|
|
990
|
-
} catch (e) {
|
|
991
|
-
registered = false;
|
|
992
|
-
}
|
|
978
|
+
order: 100,
|
|
979
|
+
label: "Token \u4EEA\u8868\u76D8"
|
|
980
|
+
},
|
|
981
|
+
() => import_react.default.createElement(GaugePanel)
|
|
982
|
+
);
|
|
993
983
|
};
|
|
994
|
-
let
|
|
984
|
+
let stop = void 0;
|
|
995
985
|
try {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
}
|
|
1001
|
-
const tryRead = () => {
|
|
1002
|
-
if (registered) return;
|
|
1003
|
-
try {
|
|
1004
|
-
const s = typeof ctx.get === "function" ? ctx.get("betterSidebar") : ctx.betterSidebar;
|
|
1005
|
-
register(s);
|
|
1006
|
-
} catch (e) {
|
|
986
|
+
if (typeof ctx.slots.inject === "function") {
|
|
987
|
+
stop = ctx.slots.inject("shell.overlay", doRegister);
|
|
988
|
+
} else {
|
|
989
|
+
doRegister();
|
|
1007
990
|
}
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
let timer = void 0;
|
|
1011
|
-
if (!registered) {
|
|
1012
|
-
timer = setInterval(() => {
|
|
1013
|
-
tryRead();
|
|
1014
|
-
if (registered && timer !== void 0) clearInterval(timer);
|
|
1015
|
-
}, 500);
|
|
991
|
+
} catch (e) {
|
|
992
|
+
console.error("[dsh-token-gauge] \u6CE8\u518C shell.overlay \u5931\u8D25", e);
|
|
1016
993
|
}
|
|
1017
|
-
const stop = setTimeout(() => {
|
|
1018
|
-
if (timer !== void 0) clearInterval(timer);
|
|
1019
|
-
if (!registered) {
|
|
1020
|
-
console.error(
|
|
1021
|
-
"[dsh-token-gauge] \u672A\u53D6\u5F97 dsh-better-sidebar \u670D\u52A1\uFF0C\u4FA7\u8FB9\u680F Tab \u672A\u6CE8\u518C\u3002\n \u8BF7\u628A\u5B83\u88C5\u6210 profile \u7684\u300C\u76F4\u63A5\u4F9D\u8D56\u300D\u5E76\u6302\u8F7D\uFF1A\n dsh plugin --profile web add dsh-better-sidebar\n \u7136\u540E\u5B8C\u6574\u91CD\u542F dsh web\u3002\n \u6CE8\u610F\uFF1A\u4EC5\u4F5C\u4E3A\u672C\u63D2\u4EF6\u7684\u4F20\u9012\u4F9D\u8D56\u5B58\u5728\u4E8E node_modules \u662F\u4E0D\u591F\u7684\u2014\u2014DSH \u4E0D\u4F1A\u628A\n \u4F20\u9012\u4F9D\u8D56\u6302\u6210 bundle \u5C42\uFF0C\u90A3\u79CD\u60C5\u51B5\u4E0B\u672C\u63D2\u4EF6\u4F1A\u9759\u9ED8\u5730\u6CA1\u6709 Tab\u3002"
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
}, 3e4);
|
|
1025
994
|
return () => {
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1028
|
-
if (typeof unregister === "function") unregister();
|
|
1029
|
-
if (typeof disposeInject === "function") disposeInject();
|
|
995
|
+
if (typeof disposeRegistration === "function") disposeRegistration();
|
|
996
|
+
if (typeof stop === "function") stop();
|
|
1030
997
|
};
|
|
1031
998
|
}
|
|
1032
999
|
; return module.exports; } });
|
package/package.json
CHANGED
|
@@ -1,84 +1,80 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-token-gauge",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DSH 机车风格 Token 用量/费用仪表盘侧边栏插件(稳定版 0.1.1-rc.x 兼容)",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.mjs",
|
|
7
|
-
"module": "lib/index.mjs",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./lib/index.mjs",
|
|
10
|
-
"./client": "./lib/client.mjs",
|
|
11
|
-
"./package.json": "./package.json"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"lib/index.mjs",
|
|
15
|
-
"lib/client.mjs",
|
|
16
|
-
"lib/LICENSE",
|
|
17
|
-
"src",
|
|
18
|
-
"preview",
|
|
19
|
-
"README.md",
|
|
20
|
-
"README.en.md",
|
|
21
|
-
"LICENSE",
|
|
22
|
-
"cordis.patch.yml",
|
|
23
|
-
"build.mjs",
|
|
24
|
-
"tsconfig.json"
|
|
25
|
-
],
|
|
26
|
-
"author": "WorkBuddy User",
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=20"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "git+https://github.com/liumiaozhi/dsh-token-gauge.git"
|
|
34
|
-
},
|
|
35
|
-
"homepage": "https://github.com/liumiaozhi/dsh-token-gauge#readme",
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/liumiaozhi/dsh-token-gauge/issues"
|
|
38
|
-
},
|
|
39
|
-
"publishConfig": {
|
|
40
|
-
"registry": "https://registry.npmjs.org",
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
|
-
"dsh": {
|
|
44
|
-
"engines": {
|
|
45
|
-
"dsh": ">=0.1.1-rc.1"
|
|
46
|
-
},
|
|
47
|
-
"bundle": {
|
|
48
|
-
"patch": "./cordis.patch.yml"
|
|
49
|
-
},
|
|
50
|
-
"compatibility": {
|
|
51
|
-
"dshReleases": {
|
|
52
|
-
"0.1.1-rc.1": "compatible",
|
|
53
|
-
"0.1.1-rc.2": "compatible"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"build": "node build.mjs",
|
|
59
|
-
"prepack": "node build.mjs"
|
|
60
|
-
},
|
|
61
|
-
"peerDependencies": {
|
|
62
|
-
"@deepseek-ai/cordis": "*",
|
|
63
|
-
"@deepseek-ai/dsh-host-webserver": "*"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"react": "^18.2.0",
|
|
82
|
-
"typescript": "^5.4.0"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-token-gauge",
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "DSH 机车风格 Token 用量/费用仪表盘侧边栏插件(稳定版 0.1.1-rc.x 兼容)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.mjs",
|
|
7
|
+
"module": "lib/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/index.mjs",
|
|
10
|
+
"./client": "./lib/client.mjs",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"lib/index.mjs",
|
|
15
|
+
"lib/client.mjs",
|
|
16
|
+
"lib/LICENSE",
|
|
17
|
+
"src",
|
|
18
|
+
"preview",
|
|
19
|
+
"README.md",
|
|
20
|
+
"README.en.md",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"build.mjs",
|
|
24
|
+
"tsconfig.json"
|
|
25
|
+
],
|
|
26
|
+
"author": "WorkBuddy User",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/liumiaozhi/dsh-token-gauge.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/liumiaozhi/dsh-token-gauge#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/liumiaozhi/dsh-token-gauge/issues"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"registry": "https://registry.npmjs.org",
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dsh": {
|
|
44
|
+
"engines": {
|
|
45
|
+
"dsh": ">=0.1.1-rc.1"
|
|
46
|
+
},
|
|
47
|
+
"bundle": {
|
|
48
|
+
"patch": "./cordis.patch.yml"
|
|
49
|
+
},
|
|
50
|
+
"compatibility": {
|
|
51
|
+
"dshReleases": {
|
|
52
|
+
"0.1.1-rc.1": "compatible",
|
|
53
|
+
"0.1.1-rc.2": "compatible"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "node build.mjs",
|
|
59
|
+
"prepack": "node build.mjs"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"@deepseek-ai/cordis": "*",
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "*"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"@deepseek-ai/cordis": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
70
|
+
"optional": true
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@deepseek-ai/cordis": "*",
|
|
75
|
+
"@types/react": "^18.2.0",
|
|
76
|
+
"esbuild": "^0.21.5",
|
|
77
|
+
"react": "^18.2.0",
|
|
78
|
+
"typescript": "^5.4.0"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,334 +1,302 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// DSH Client 半边:注册侧边栏 Tab,渲染机车风格仪表盘 + 设置弹窗
|
|
3
|
-
// 运行环境:浏览器(web 平台,与 webServer 同域)。纯原生 DOM/SVG 仪表盘,
|
|
4
|
-
// 通过同域 HTTP 与 Host 半边通信:
|
|
5
|
-
// GET /plugins/dsh-token-gauge/state -> 完整 State(JSON)
|
|
6
|
-
// POST /plugins/dsh-token-gauge/cmd -> {type,payload} -> 更新后 State
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
st.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
titleEl.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
gear.
|
|
111
|
-
gear.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
empty.
|
|
119
|
-
empty
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
root
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
empty.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
void poll()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
el
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
timer = setInterval(() => {
|
|
304
|
-
tryRead()
|
|
305
|
-
if (registered && timer !== void 0) clearInterval(timer as number)
|
|
306
|
-
}, 500)
|
|
307
|
-
}
|
|
308
|
-
const stop = setTimeout(() => {
|
|
309
|
-
if (timer !== void 0) clearInterval(timer as number)
|
|
310
|
-
if (!registered) {
|
|
311
|
-
// 服务始终不可达 ≈ dsh-better-sidebar 没被挂成 profile 层。
|
|
312
|
-
// 关键陷阱:它若只是本插件的「传递依赖」,只会落进 node_modules 而不会被
|
|
313
|
-
// 加入 dsh.profile.bundles —— 加载树里没有条目,服务根本不会被 provide,
|
|
314
|
-
// 本插件于是「已挂载但无 Tab」且全程零报错(真·静默失败)。故这里必须
|
|
315
|
-
// 打出可操作的指引,而不是默默放弃。
|
|
316
|
-
console.error(
|
|
317
|
-
'[dsh-token-gauge] 未取得 dsh-better-sidebar 服务,侧边栏 Tab 未注册。\n' +
|
|
318
|
-
' 请把它装成 profile 的「直接依赖」并挂载:\n' +
|
|
319
|
-
' dsh plugin --profile web add dsh-better-sidebar\n' +
|
|
320
|
-
' 然后完整重启 dsh web。\n' +
|
|
321
|
-
' 注意:仅作为本插件的传递依赖存在于 node_modules 是不够的——DSH 不会把\n' +
|
|
322
|
-
' 传递依赖挂成 bundle 层,那种情况下本插件会静默地没有 Tab。',
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
}, 30000)
|
|
326
|
-
|
|
327
|
-
// 插件级 disposer
|
|
328
|
-
return () => {
|
|
329
|
-
if (timer !== void 0) clearInterval(timer as number)
|
|
330
|
-
clearTimeout(stop)
|
|
331
|
-
if (typeof unregister === 'function') (unregister as () => void)()
|
|
332
|
-
if (typeof disposeInject === 'function') (disposeInject as () => void)()
|
|
333
|
-
}
|
|
334
|
-
}
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// DSH Client 半边:注册侧边栏 Tab,渲染机车风格仪表盘 + 设置弹窗
|
|
3
|
+
// 运行环境:浏览器(web 平台,与 webServer 同域)。纯原生 DOM/SVG 仪表盘,
|
|
4
|
+
// 通过同域 HTTP 与 Host 半边通信:
|
|
5
|
+
// GET /plugins/dsh-token-gauge/state -> 完整 State(JSON)
|
|
6
|
+
// POST /plugins/dsh-token-gauge/cmd -> {type,payload} -> 更新后 State
|
|
7
|
+
// 通过官方 shell.overlay 插槽独立挂载浮层面板(方案 A),不再依赖 dsh-better-sidebar。
|
|
8
|
+
// 为避免与运行时 React 产生双实例 hook 冲突,本组件不调用任何 hook,
|
|
9
|
+
// 仅在 ref 回调里挂载一次原生 DOM 仪表盘,并自行轮询刷新。
|
|
10
|
+
// ============================================================================
|
|
11
|
+
import React from 'react'
|
|
12
|
+
import { getStyle, applyPalette, buildCss } from './styles'
|
|
13
|
+
import { createGauge } from './gauge'
|
|
14
|
+
import { t } from './i18n'
|
|
15
|
+
import { downloadReport } from './export'
|
|
16
|
+
import { createSettingsModal } from './settings'
|
|
17
|
+
|
|
18
|
+
const PLUGIN_ID = 'dsh-token-gauge'
|
|
19
|
+
const ROUTE_STATE = '/plugins/dsh-token-gauge/state'
|
|
20
|
+
const ROUTE_CMD = '/plugins/dsh-token-gauge/cmd'
|
|
21
|
+
|
|
22
|
+
function totalOf(b: any): number {
|
|
23
|
+
return (b?.input || 0) + (b?.cacheRead || 0) + (b?.cacheWrite || 0) + (b?.output || 0)
|
|
24
|
+
}
|
|
25
|
+
function todayKey(d = new Date()): string {
|
|
26
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
27
|
+
}
|
|
28
|
+
function monthKey(d = new Date()): string {
|
|
29
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`
|
|
30
|
+
}
|
|
31
|
+
function weekKey(d = new Date()): string {
|
|
32
|
+
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()))
|
|
33
|
+
const dayNum = (date.getUTCDay() + 6) % 7
|
|
34
|
+
date.setUTCDate(date.getUTCDate() - dayNum + 3)
|
|
35
|
+
const firstThursday = new Date(Date.UTC(date.getUTCFullYear(), 0, 4))
|
|
36
|
+
const week =
|
|
37
|
+
1 +
|
|
38
|
+
Math.round(
|
|
39
|
+
((date.getTime() - firstThursday.getTime()) / 86400000 - 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7,
|
|
40
|
+
)
|
|
41
|
+
return `${d.getFullYear()}-W${String(week).padStart(2, '0')}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function fallbackState(): any {
|
|
45
|
+
return {
|
|
46
|
+
global: {},
|
|
47
|
+
daily: {},
|
|
48
|
+
weekly: {},
|
|
49
|
+
monthly: {},
|
|
50
|
+
sessions: {},
|
|
51
|
+
models: {},
|
|
52
|
+
activeSession: '',
|
|
53
|
+
settings: {
|
|
54
|
+
style: 'ev',
|
|
55
|
+
palette: 'neon',
|
|
56
|
+
lang: 'zh',
|
|
57
|
+
metric: 'global',
|
|
58
|
+
display: { usage: true, cost: true, io: true, cacheMiss: true },
|
|
59
|
+
limits: { global: null, daily: null, weekly: null, monthly: null },
|
|
60
|
+
pricing: {},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 计算当前指标的量程/费用
|
|
66
|
+
function computeMetric(s: any): { value: number; limit: number | null; cost: number } {
|
|
67
|
+
const metric = s.settings.metric
|
|
68
|
+
let entry: any = s.global
|
|
69
|
+
let limit: number | null = s.settings.limits.global
|
|
70
|
+
if (metric === 'session') {
|
|
71
|
+
entry = s.sessions[s.activeSession] || {}
|
|
72
|
+
limit = s.settings.limits.daily
|
|
73
|
+
} else if (metric === 'today' || metric === 'daily') {
|
|
74
|
+
entry = s.daily[todayKey()] || {}
|
|
75
|
+
limit = s.settings.limits.daily
|
|
76
|
+
} else if (metric === 'weekly') {
|
|
77
|
+
entry = s.weekly[weekKey()] || {}
|
|
78
|
+
limit = s.settings.limits.weekly
|
|
79
|
+
} else if (metric === 'monthly') {
|
|
80
|
+
entry = s.monthly[monthKey()] || {}
|
|
81
|
+
limit = s.settings.limits.monthly
|
|
82
|
+
}
|
|
83
|
+
return { value: totalOf(entry), limit, cost: entry.cost || 0 }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 把原生 DOM 仪表盘挂载进容器,并启动轮询
|
|
87
|
+
function mountGauge(container: HTMLElement): void {
|
|
88
|
+
// 注入一次全局样式
|
|
89
|
+
if (!document.getElementById('tg-gauge-style')) {
|
|
90
|
+
const st = document.createElement('style')
|
|
91
|
+
st.id = 'tg-gauge-style'
|
|
92
|
+
st.textContent = buildCss()
|
|
93
|
+
document.head.appendChild(st)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let state: any = null
|
|
97
|
+
const tr = (k: string) => t((state?.settings?.lang as 'zh' | 'en') || 'zh', k)
|
|
98
|
+
|
|
99
|
+
const root = container
|
|
100
|
+
root.className = 'tg-root tg-style-ev'
|
|
101
|
+
|
|
102
|
+
// 头部
|
|
103
|
+
const head = document.createElement('div')
|
|
104
|
+
head.className = 'tg-head'
|
|
105
|
+
const titleEl = document.createElement('div')
|
|
106
|
+
titleEl.className = 'tg-title'
|
|
107
|
+
titleEl.textContent = 'Token 仪表盘'
|
|
108
|
+
const gear = document.createElement('div')
|
|
109
|
+
gear.className = 'tg-gear'
|
|
110
|
+
gear.textContent = '⚙'
|
|
111
|
+
gear.title = '设置'
|
|
112
|
+
head.append(titleEl, gear)
|
|
113
|
+
root.appendChild(head)
|
|
114
|
+
|
|
115
|
+
// 空数据提示
|
|
116
|
+
const empty = document.createElement('div')
|
|
117
|
+
empty.className = 'tg-empty'
|
|
118
|
+
empty.style.display = 'none'
|
|
119
|
+
root.appendChild(empty)
|
|
120
|
+
|
|
121
|
+
// 仪表盘
|
|
122
|
+
const gauge = createGauge()
|
|
123
|
+
root.appendChild(gauge.el)
|
|
124
|
+
|
|
125
|
+
// 设置弹窗
|
|
126
|
+
const modal = createSettingsModal({
|
|
127
|
+
getLang: () => (state?.settings?.lang as 'zh' | 'en') || 'zh',
|
|
128
|
+
onSettings: (patch) => sendCmd('setSettings', patch),
|
|
129
|
+
onExport: (format) => {
|
|
130
|
+
if (state) downloadReport(state, (state.settings.lang as 'zh' | 'en') || 'zh', format)
|
|
131
|
+
},
|
|
132
|
+
onClear: () => sendCmd('clearUsage', undefined),
|
|
133
|
+
onReset: () => sendCmd('resetSettings', undefined),
|
|
134
|
+
})
|
|
135
|
+
root.appendChild(modal.el)
|
|
136
|
+
|
|
137
|
+
gear.addEventListener('click', () => modal.open(state || fallbackState()))
|
|
138
|
+
|
|
139
|
+
const applyState = (s: any): void => {
|
|
140
|
+
state = s
|
|
141
|
+
root.className = 'tg-root ' + getStyle(s.settings.style).className
|
|
142
|
+
applyPalette(root, s.settings.palette)
|
|
143
|
+
titleEl.textContent = tr('title')
|
|
144
|
+
const m = computeMetric(s)
|
|
145
|
+
gauge.update({ value: m.value, limit: m.limit, cost: m.cost, state: s, display: s.settings.display, t: tr, lang: s.settings.lang })
|
|
146
|
+
const hasData = totalOf(s.global) > 0
|
|
147
|
+
empty.style.display = hasData ? 'none' : ''
|
|
148
|
+
empty.textContent = tr('noData')
|
|
149
|
+
modal.sync(s)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 拉取最新状态
|
|
153
|
+
async function poll(): Promise<void> {
|
|
154
|
+
try {
|
|
155
|
+
const r = await fetch(ROUTE_STATE, { headers: { Accept: 'application/json' } })
|
|
156
|
+
if (r.ok) {
|
|
157
|
+
const s = await r.json()
|
|
158
|
+
applyState(s)
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// 网络抖动忽略,下一轮重试
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 发送指令并刷新
|
|
166
|
+
async function sendCmd(type: string, payload: any): Promise<void> {
|
|
167
|
+
try {
|
|
168
|
+
await fetch(ROUTE_CMD, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: JSON.stringify({ type, payload }),
|
|
172
|
+
})
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.warn(`[${PLUGIN_ID}] cmd ${type} failed`, e)
|
|
175
|
+
}
|
|
176
|
+
await poll()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 立即拉一次,并定时轮询
|
|
180
|
+
void poll()
|
|
181
|
+
const timer = window.setInterval(() => void poll(), 2000)
|
|
182
|
+
// 容器被移除时停止轮询
|
|
183
|
+
const mo = new MutationObserver(() => {
|
|
184
|
+
if (!document.body.contains(container)) {
|
|
185
|
+
window.clearInterval(timer)
|
|
186
|
+
mo.disconnect()
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
mo.observe(document.body, { childList: true, subtree: true })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 稳定的 ref 回调:只在首次挂载时构建一次仪表盘
|
|
193
|
+
const gaugeRef = (el: HTMLElement | null): void => {
|
|
194
|
+
if (el && !el.dataset.tgMounted) {
|
|
195
|
+
el.dataset.tgMounted = '1'
|
|
196
|
+
mountGauge(el)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 浮层面板组件:固定在右下角、占视口高度 1/3(方案 A:shell.overlay 插槽)。
|
|
201
|
+
// 仍是零 hook、ref 回调挂原生 DOM,规避双 React 实例;外层用内联定位样式,
|
|
202
|
+
// 不被 mountGauge 内的 className 覆盖(style 优先级高于 class)。
|
|
203
|
+
function GaugePanel(): React.ReactElement {
|
|
204
|
+
return React.createElement('div', {
|
|
205
|
+
ref: gaugeRef,
|
|
206
|
+
className: 'tg-panel-root',
|
|
207
|
+
style: {
|
|
208
|
+
position: 'fixed',
|
|
209
|
+
right: '0',
|
|
210
|
+
bottom: '0',
|
|
211
|
+
width: '420px',
|
|
212
|
+
height: '33vh',
|
|
213
|
+
zIndex: 50,
|
|
214
|
+
pointerEvents: 'auto',
|
|
215
|
+
boxSizing: 'border-box',
|
|
216
|
+
overflow: 'hidden',
|
|
217
|
+
borderTop: '1px solid var(--dsw-alias-hairline)',
|
|
218
|
+
borderLeft: '1px solid var(--dsw-alias-hairline)',
|
|
219
|
+
background: 'var(--dsw-alias-bg-base)',
|
|
220
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
221
|
+
boxShadow: '0 0 24px rgba(0,0,0,0.28)',
|
|
222
|
+
} as any,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 侧边栏图标:better-sidebar 以 `icon(size)` 形式调用(见其内置 tab:
|
|
227
|
+
// `icon: (size) => jsx(Icon, { size })`),故这里接收数字尺寸。用 currentColor
|
|
228
|
+
// 着色以自适应明暗主题,纯 SVG 零依赖。
|
|
229
|
+
const GaugeIcon = (size?: number): any => {
|
|
230
|
+
const s = typeof size === 'number' && size > 0 ? size : 16
|
|
231
|
+
const svgProps: any = {
|
|
232
|
+
width: s,
|
|
233
|
+
height: s,
|
|
234
|
+
viewBox: '0 0 16 16',
|
|
235
|
+
fill: 'none',
|
|
236
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
237
|
+
'aria-hidden': 'true',
|
|
238
|
+
}
|
|
239
|
+
// 仪表盘弧 + 指针
|
|
240
|
+
const arc = React.createElement('path', {
|
|
241
|
+
d: 'M2.6 11.6a5.6 5.6 0 1 1 10.8 0',
|
|
242
|
+
stroke: 'currentColor',
|
|
243
|
+
strokeWidth: 1.5,
|
|
244
|
+
strokeLinecap: 'round',
|
|
245
|
+
})
|
|
246
|
+
const needle = React.createElement('path', {
|
|
247
|
+
d: 'M8 11.6 10.4 7.2',
|
|
248
|
+
stroke: 'currentColor',
|
|
249
|
+
strokeWidth: 1.5,
|
|
250
|
+
strokeLinecap: 'round',
|
|
251
|
+
})
|
|
252
|
+
return React.createElement('svg', svgProps, arc, needle)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 在「+ 新建 Tab」菜单里的排序位。内置 tab 用 20/91/110 等,order 缺省为 100
|
|
256
|
+
// 会排到菜单最底部不易发现,这里显式给一个偏前的值。
|
|
257
|
+
const TAB_ORDER = 90
|
|
258
|
+
|
|
259
|
+
export const name = PLUGIN_ID
|
|
260
|
+
|
|
261
|
+
// 方案 A:通过官方 shell.overlay 插槽(list 型、additive、全框架级浮层)独立挂载仪表盘,
|
|
262
|
+
// 不再依赖第三方插件 dsh-better-sidebar。slots 是 layout 提供的框架级服务(root 早期即
|
|
263
|
+
// 就绪),故插件级 `inject: ['slots']` 不会触发此前 betterSidebar 的 pending / without-inject
|
|
264
|
+
// 问题——这正是脱离 better-sidebar 的核心收益。
|
|
265
|
+
export const inject = ['slots']
|
|
266
|
+
|
|
267
|
+
export function apply(ctx: any): (() => void) | void {
|
|
268
|
+
let disposeRegistration: unknown = void 0
|
|
269
|
+
|
|
270
|
+
const doRegister = (): void => {
|
|
271
|
+
// shell.overlay 是 click-through 层,entry 需自行 opt-in pointer events(已在
|
|
272
|
+
// GaugePanel 外层设 pointerEvents:'auto')。第二参是返回 React.Element 的工厂函数
|
|
273
|
+
//(照抄 dsh-cordis-client-runner 官方示例)。
|
|
274
|
+
disposeRegistration = ctx.slots.register(
|
|
275
|
+
{
|
|
276
|
+
name: 'shell.overlay',
|
|
277
|
+
id: PLUGIN_ID,
|
|
278
|
+
order: 100,
|
|
279
|
+
label: 'Token 仪表盘',
|
|
280
|
+
},
|
|
281
|
+
() => React.createElement(GaugePanel),
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let stop: unknown = void 0
|
|
286
|
+
try {
|
|
287
|
+
// slots.inject(seat, cb):等 shell.overlay 这个 seat 就绪再注册(root 已声明,
|
|
288
|
+
// 通常同步触发)。某些环境下若无 inject 方法,则直接 register 兜底。
|
|
289
|
+
if (typeof ctx.slots.inject === 'function') {
|
|
290
|
+
stop = ctx.slots.inject('shell.overlay', doRegister)
|
|
291
|
+
} else {
|
|
292
|
+
doRegister()
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error('[dsh-token-gauge] 注册 shell.overlay 失败', e)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return () => {
|
|
299
|
+
if (typeof disposeRegistration === 'function') (disposeRegistration as () => void)()
|
|
300
|
+
if (typeof stop === 'function') (stop as () => void)()
|
|
301
|
+
}
|
|
302
|
+
}
|