coding-proxy 0.2.3a2__py3-none-any.whl → 0.2.3a4__py3-none-any.whl
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.
- coding/proxy/config/config.default.yaml +1 -1
- coding/proxy/logging/db.py +1 -1
- coding/proxy/routing/executor.py +6 -1
- coding/proxy/routing/quota_guard.py +1 -0
- coding/proxy/server/dashboard.py +490 -225
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a4.dist-info}/METADATA +1 -1
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a4.dist-info}/RECORD +10 -10
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a4.dist-info}/WHEEL +0 -0
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a4.dist-info}/entry_points.txt +0 -0
- {coding_proxy-0.2.3a2.dist-info → coding_proxy-0.2.3a4.dist-info}/licenses/LICENSE +0 -0
|
@@ -58,7 +58,7 @@ vendors:
|
|
|
58
58
|
probe_interval_seconds: 300
|
|
59
59
|
weekly_quota_guard:
|
|
60
60
|
enabled: true
|
|
61
|
-
token_budget:
|
|
61
|
+
token_budget: 500000000 # 一周 token 预算(根据订阅计划调整)
|
|
62
62
|
window_hours: 168.0 # 7 天滑动窗口
|
|
63
63
|
threshold_percent: 99.0
|
|
64
64
|
probe_interval_seconds: 1800 # 每 30 分钟探测一次
|
coding/proxy/logging/db.py
CHANGED
|
@@ -497,7 +497,7 @@ class TokenLogger:
|
|
|
497
497
|
return 0
|
|
498
498
|
cutoff_iso = _hours_ago_utc_iso(window_hours)
|
|
499
499
|
cursor = await self._db.execute(
|
|
500
|
-
"""SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
|
|
500
|
+
"""SELECT COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) AS total
|
|
501
501
|
FROM usage_log
|
|
502
502
|
WHERE vendor = ? AND success = 1
|
|
503
503
|
AND ts >= ?""",
|
coding/proxy/routing/executor.py
CHANGED
|
@@ -276,7 +276,12 @@ class _RouteExecutor:
|
|
|
276
276
|
tier.name,
|
|
277
277
|
usage,
|
|
278
278
|
)
|
|
279
|
-
tier.record_success(
|
|
279
|
+
tier.record_success(
|
|
280
|
+
info.input_tokens
|
|
281
|
+
+ info.output_tokens
|
|
282
|
+
+ info.cache_creation_tokens
|
|
283
|
+
+ info.cache_read_tokens
|
|
284
|
+
)
|
|
280
285
|
duration = int((time.monotonic() - start) * 1000)
|
|
281
286
|
model = body.get("model", "unknown")
|
|
282
287
|
model_served = usage.get("model_served") or tier.vendor.map_model(model)
|
coding/proxy/server/dashboard.py
CHANGED
|
@@ -19,14 +19,20 @@ def _build_favicon() -> bytes:
|
|
|
19
19
|
|
|
20
20
|
width, height = 16, 16
|
|
21
21
|
pixel_rows: list[bytes] = []
|
|
22
|
+
cx, cy = width / 2.0, height / 2.0
|
|
22
23
|
for y in range(height - 1, -1, -1): # BMP bottom-up
|
|
23
24
|
row = bytearray()
|
|
24
25
|
for x in range(width):
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
dx = x - cx + 0.5
|
|
27
|
+
dy = y - cy + 0.5
|
|
28
|
+
if dx * dx + dy * dy > (width / 2.0) ** 2:
|
|
29
|
+
row.extend([0, 0, 0, 0]) # 圆外透明
|
|
30
|
+
else:
|
|
31
|
+
t = (x + (height - 1 - y)) / (width + height - 2)
|
|
32
|
+
r = int(88 + (188 - 88) * t)
|
|
33
|
+
g = int(166 + (140 - 166) * t)
|
|
34
|
+
b = 255
|
|
35
|
+
row.extend([b, g, r, 255]) # BGRA
|
|
30
36
|
pixel_rows.append(bytes(row))
|
|
31
37
|
|
|
32
38
|
bmp_hdr = struct.pack(
|
|
@@ -56,6 +62,9 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
56
62
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
57
63
|
<title>Coding Proxy Dashboard</title>
|
|
58
64
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
65
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
66
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
67
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
|
|
59
68
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
|
60
69
|
<style>
|
|
61
70
|
:root {
|
|
@@ -63,16 +72,26 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
63
72
|
--bg-card: #161b22;
|
|
64
73
|
--bg-card-hover: #1c2128;
|
|
65
74
|
--border: #30363d;
|
|
75
|
+
--border-subtle: rgba(48,54,61,.6);
|
|
66
76
|
--text-primary: #e6edf3;
|
|
67
77
|
--text-secondary: #8b949e;
|
|
78
|
+
--text-tertiary: #6e7681;
|
|
68
79
|
--accent-blue: #58a6ff;
|
|
69
80
|
--accent-green: #3fb950;
|
|
70
81
|
--accent-yellow: #d29922;
|
|
71
82
|
--accent-red: #f85149;
|
|
72
83
|
--accent-purple: #bc8cff;
|
|
73
84
|
--accent-orange: #ffa657;
|
|
74
|
-
--
|
|
75
|
-
--
|
|
85
|
+
--accent-teal: #39d353;
|
|
86
|
+
--radius: 10px;
|
|
87
|
+
--radius-sm: 6px;
|
|
88
|
+
--shadow: 0 1px 3px rgba(0,0,0,.4), 0 1px 2px rgba(0,0,0,.3);
|
|
89
|
+
--shadow-md: 0 4px 12px rgba(0,0,0,.4), 0 2px 4px rgba(0,0,0,.3);
|
|
90
|
+
--glow-blue: 0 0 0 1px rgba(88,166,255,.15), 0 4px 16px rgba(88,166,255,.06);
|
|
91
|
+
}
|
|
92
|
+
@keyframes fadeInUp {
|
|
93
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
94
|
+
to { opacity: 1; transform: translateY(0); }
|
|
76
95
|
}
|
|
77
96
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
78
97
|
body {
|
|
@@ -85,9 +104,11 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
85
104
|
}
|
|
86
105
|
/* ── 头部 ── */
|
|
87
106
|
header {
|
|
88
|
-
background:
|
|
107
|
+
background: rgba(22,27,34,.85);
|
|
108
|
+
backdrop-filter: blur(12px);
|
|
109
|
+
-webkit-backdrop-filter: blur(12px);
|
|
89
110
|
border-bottom: 1px solid var(--border);
|
|
90
|
-
padding:
|
|
111
|
+
padding: 13px 24px;
|
|
91
112
|
display: flex;
|
|
92
113
|
align-items: center;
|
|
93
114
|
justify-content: space-between;
|
|
@@ -97,75 +118,105 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
97
118
|
}
|
|
98
119
|
.header-left { display: flex; align-items: center; gap: 12px; }
|
|
99
120
|
.logo {
|
|
100
|
-
width:
|
|
121
|
+
width: 30px; height: 30px;
|
|
101
122
|
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
102
|
-
border-radius:
|
|
123
|
+
border-radius: 50%;
|
|
103
124
|
display: flex; align-items: center; justify-content: center;
|
|
104
|
-
font-size:
|
|
125
|
+
font-size: 15px; font-weight: 700; color: #fff;
|
|
126
|
+
box-shadow: 0 2px 8px rgba(88,166,255,.3);
|
|
105
127
|
}
|
|
106
|
-
h1 { font-size:
|
|
128
|
+
h1 { font-size: 15px; font-weight: 600; color: var(--text-primary); letter-spacing: -.2px; }
|
|
107
129
|
.header-right { display: flex; align-items: center; gap: 12px; }
|
|
108
130
|
.badge {
|
|
109
131
|
font-size: 11px; padding: 2px 8px;
|
|
110
132
|
border-radius: 12px;
|
|
111
|
-
background: rgba(88,166,255,.
|
|
133
|
+
background: rgba(88,166,255,.1);
|
|
112
134
|
color: var(--accent-blue);
|
|
113
|
-
border: 1px solid rgba(88,166,255,.
|
|
135
|
+
border: 1px solid rgba(88,166,255,.2);
|
|
136
|
+
font-family: 'JetBrains Mono', monospace;
|
|
114
137
|
}
|
|
115
|
-
.refresh-time { font-size:
|
|
138
|
+
.refresh-time { font-size: 11px; color: var(--text-tertiary); }
|
|
116
139
|
.btn-refresh {
|
|
117
|
-
padding: 5px 12px; border-radius: var(--radius);
|
|
118
|
-
background: rgba(48,54,61,.
|
|
140
|
+
padding: 5px 12px; border-radius: var(--radius-sm);
|
|
141
|
+
background: rgba(48,54,61,.5);
|
|
119
142
|
border: 1px solid var(--border);
|
|
120
|
-
color: var(--text-
|
|
143
|
+
color: var(--text-secondary);
|
|
121
144
|
font-size: 12px; cursor: pointer;
|
|
122
|
-
transition:
|
|
145
|
+
transition: all .2s ease;
|
|
146
|
+
}
|
|
147
|
+
.btn-refresh:hover {
|
|
148
|
+
background: var(--bg-card-hover);
|
|
149
|
+
color: var(--text-primary);
|
|
150
|
+
border-color: rgba(88,166,255,.4);
|
|
123
151
|
}
|
|
124
|
-
.btn-refresh:hover { background: var(--bg-card-hover); }
|
|
125
152
|
/* ── 主内容 ── */
|
|
126
|
-
main { padding: 20px 24px; max-width:
|
|
153
|
+
main { padding: 20px 24px; max-width: 1440px; margin: 0 auto; }
|
|
127
154
|
/* ── KPI 卡片 ── */
|
|
128
155
|
.kpi-grid {
|
|
129
156
|
display: grid;
|
|
130
157
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
131
|
-
gap:
|
|
132
|
-
margin-bottom:
|
|
158
|
+
gap: 12px;
|
|
159
|
+
margin-bottom: 18px;
|
|
133
160
|
}
|
|
134
161
|
.kpi-card {
|
|
135
162
|
background: var(--bg-card);
|
|
136
163
|
border: 1px solid var(--border);
|
|
137
164
|
border-radius: var(--radius);
|
|
138
|
-
padding: 16px
|
|
165
|
+
padding: 16px 18px 14px;
|
|
139
166
|
box-shadow: var(--shadow);
|
|
140
|
-
transition:
|
|
167
|
+
transition: all .2s ease;
|
|
168
|
+
animation: fadeInUp .4s ease both;
|
|
169
|
+
position: relative;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
}
|
|
172
|
+
.kpi-card::before {
|
|
173
|
+
content: '';
|
|
174
|
+
position: absolute;
|
|
175
|
+
top: 0; left: 0; right: 0;
|
|
176
|
+
height: 2px;
|
|
177
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
|
178
|
+
}
|
|
179
|
+
.kpi-card:nth-child(1)::before { background: var(--accent-blue); }
|
|
180
|
+
.kpi-card:nth-child(2)::before { background: var(--accent-purple); }
|
|
181
|
+
.kpi-card:nth-child(3)::before { background: var(--accent-green); }
|
|
182
|
+
.kpi-card:nth-child(4)::before { background: var(--accent-yellow); }
|
|
183
|
+
.kpi-card:nth-child(5)::before { background: var(--accent-red); }
|
|
184
|
+
.kpi-card:nth-child(6)::before { background: var(--accent-orange); }
|
|
185
|
+
.kpi-card:hover {
|
|
186
|
+
background: var(--bg-card-hover);
|
|
187
|
+
box-shadow: var(--glow-blue);
|
|
188
|
+
transform: translateY(-1px);
|
|
189
|
+
}
|
|
190
|
+
.kpi-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
|
191
|
+
.kpi-icon { font-size: 13px; opacity: .8; }
|
|
192
|
+
.kpi-label { font-size: 11px; color: var(--text-secondary); font-weight: 500; letter-spacing: .2px; }
|
|
193
|
+
.kpi-value {
|
|
194
|
+
font-size: 24px; font-weight: 700; line-height: 1.2;
|
|
195
|
+
font-family: 'JetBrains Mono', monospace;
|
|
196
|
+
letter-spacing: -0.5px;
|
|
141
197
|
}
|
|
142
|
-
.kpi-
|
|
143
|
-
.kpi-label { font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
|
|
144
|
-
.kpi-value { font-size: 26px; font-weight: 700; line-height: 1.2; }
|
|
145
|
-
.kpi-sub { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
|
|
146
|
-
.kpi-delta { font-size: 12px; margin-top: 4px; }
|
|
147
|
-
.kpi-delta.up { color: var(--accent-green); }
|
|
148
|
-
.kpi-delta.down { color: var(--accent-red); }
|
|
198
|
+
.kpi-sub { font-size: 11px; color: var(--text-tertiary); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
149
199
|
.color-blue { color: var(--accent-blue); }
|
|
150
200
|
.color-green { color: var(--accent-green); }
|
|
151
201
|
.color-yellow { color: var(--accent-yellow); }
|
|
152
202
|
.color-red { color: var(--accent-red); }
|
|
153
203
|
.color-purple { color: var(--accent-purple); }
|
|
204
|
+
.color-orange { color: var(--accent-orange); }
|
|
154
205
|
/* ── 图表网格 ── */
|
|
155
206
|
.charts-grid {
|
|
156
207
|
display: grid;
|
|
157
208
|
grid-template-columns: 1fr 2fr;
|
|
158
|
-
gap:
|
|
159
|
-
margin-bottom:
|
|
209
|
+
gap: 12px;
|
|
210
|
+
margin-bottom: 12px;
|
|
160
211
|
}
|
|
161
|
-
.charts-grid-
|
|
212
|
+
.charts-grid-2 {
|
|
162
213
|
display: grid;
|
|
163
214
|
grid-template-columns: 1fr 2fr;
|
|
164
|
-
gap:
|
|
165
|
-
margin-bottom:
|
|
215
|
+
gap: 12px;
|
|
216
|
+
margin-bottom: 12px;
|
|
166
217
|
}
|
|
167
|
-
@media (max-width:
|
|
168
|
-
.charts-grid, .charts-grid-
|
|
218
|
+
@media (max-width: 960px) {
|
|
219
|
+
.charts-grid, .charts-grid-2 { grid-template-columns: 1fr; }
|
|
169
220
|
}
|
|
170
221
|
.card {
|
|
171
222
|
background: var(--bg-card);
|
|
@@ -173,101 +224,131 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
173
224
|
border-radius: var(--radius);
|
|
174
225
|
padding: 16px 20px;
|
|
175
226
|
box-shadow: var(--shadow);
|
|
227
|
+
transition: box-shadow .2s ease;
|
|
228
|
+
animation: fadeInUp .4s ease both;
|
|
176
229
|
}
|
|
230
|
+
.card:hover { box-shadow: var(--shadow-md); }
|
|
177
231
|
.card-title {
|
|
178
|
-
font-size:
|
|
179
|
-
color: var(--text-
|
|
232
|
+
font-size: 11px; font-weight: 600;
|
|
233
|
+
color: var(--text-tertiary);
|
|
180
234
|
text-transform: uppercase;
|
|
181
|
-
letter-spacing: .
|
|
235
|
+
letter-spacing: .8px;
|
|
182
236
|
margin-bottom: 14px;
|
|
183
237
|
display: flex; align-items: center; justify-content: space-between;
|
|
184
238
|
}
|
|
185
239
|
.chart-wrap { position: relative; height: 220px; }
|
|
186
240
|
.chart-wrap-lg { position: relative; height: 240px; }
|
|
241
|
+
.chart-wrap-xl { position: relative; height: 260px; }
|
|
187
242
|
/* ── 供应商状态 ── */
|
|
188
|
-
.vendor-list { display: flex; flex-direction: column; gap:
|
|
243
|
+
.vendor-list { display: flex; flex-direction: column; gap: 8px; }
|
|
189
244
|
.vendor-item {
|
|
190
245
|
display: flex; align-items: center; justify-content: space-between;
|
|
191
246
|
padding: 10px 12px;
|
|
192
|
-
background: rgba(255,255,255,.
|
|
193
|
-
border: 1px solid var(--border);
|
|
194
|
-
border-radius:
|
|
247
|
+
background: rgba(255,255,255,.02);
|
|
248
|
+
border: 1px solid var(--border-subtle);
|
|
249
|
+
border-radius: var(--radius-sm);
|
|
250
|
+
transition: background .15s;
|
|
251
|
+
}
|
|
252
|
+
.vendor-item:hover { background: rgba(255,255,255,.04); }
|
|
253
|
+
.vendor-info { display: flex; align-items: center; gap: 10px; }
|
|
254
|
+
.vendor-avatar {
|
|
255
|
+
width: 28px; height: 28px; border-radius: 50%;
|
|
256
|
+
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
257
|
+
display: flex; align-items: center; justify-content: center;
|
|
258
|
+
font-size: 11px; font-weight: 700; color: #fff;
|
|
259
|
+
flex-shrink: 0;
|
|
195
260
|
}
|
|
196
|
-
.vendor-name { font-weight: 600; font-size:
|
|
197
|
-
.vendor-badges { display: flex; gap:
|
|
261
|
+
.vendor-name { font-weight: 600; font-size: 12px; }
|
|
262
|
+
.vendor-badges { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
|
|
198
263
|
.status-badge {
|
|
199
|
-
font-size:
|
|
264
|
+
font-size: 10px; padding: 2px 7px;
|
|
200
265
|
border-radius: 10px;
|
|
201
266
|
font-weight: 500;
|
|
202
267
|
}
|
|
203
|
-
.sb-ok { background: rgba(63,185,80,.
|
|
204
|
-
.sb-warn { background: rgba(210,153,34,.
|
|
205
|
-
.sb-err { background: rgba(248,81,73,.
|
|
206
|
-
.sb-info { background: rgba(88,166,255,.
|
|
207
|
-
.quota-bar-wrap { flex: 1; margin: 0
|
|
268
|
+
.sb-ok { background: rgba(63,185,80,.12); color: var(--accent-green); border: 1px solid rgba(63,185,80,.2); }
|
|
269
|
+
.sb-warn { background: rgba(210,153,34,.12); color: var(--accent-yellow); border: 1px solid rgba(210,153,34,.2); }
|
|
270
|
+
.sb-err { background: rgba(248,81,73,.12); color: var(--accent-red); border: 1px solid rgba(248,81,73,.2); }
|
|
271
|
+
.sb-info { background: rgba(88,166,255,.12); color: var(--accent-blue); border: 1px solid rgba(88,166,255,.2); }
|
|
272
|
+
.quota-bar-wrap { flex: 1; margin: 0 10px; max-width: 100px; }
|
|
208
273
|
.quota-bar-bg {
|
|
209
|
-
height:
|
|
210
|
-
background: rgba(255,255,255,.
|
|
274
|
+
height: 4px; border-radius: 2px;
|
|
275
|
+
background: rgba(255,255,255,.06);
|
|
211
276
|
overflow: hidden;
|
|
212
277
|
}
|
|
213
|
-
.quota-bar-fill {
|
|
214
|
-
|
|
278
|
+
.quota-bar-fill {
|
|
279
|
+
height: 100%; border-radius: 2px;
|
|
280
|
+
transition: width .6s cubic-bezier(.4,0,.2,1);
|
|
281
|
+
}
|
|
215
282
|
/* ── 故障转移表 ── */
|
|
216
283
|
.ft-table-wrap { overflow-x: auto; }
|
|
217
284
|
table { width: 100%; border-collapse: collapse; }
|
|
285
|
+
thead tr { position: sticky; top: 0; background: var(--bg-card); z-index: 1; }
|
|
218
286
|
th {
|
|
219
|
-
text-align: left; font-size:
|
|
220
|
-
font-weight:
|
|
287
|
+
text-align: left; font-size: 11px; color: var(--text-tertiary);
|
|
288
|
+
font-weight: 600; padding: 6px 10px;
|
|
221
289
|
border-bottom: 1px solid var(--border);
|
|
290
|
+
letter-spacing: .4px; text-transform: uppercase;
|
|
222
291
|
}
|
|
223
|
-
td { padding: 8px 10px; font-size: 13px; border-bottom: 1px solid
|
|
292
|
+
td { padding: 8px 10px; font-size: 13px; border-bottom: 1px solid var(--border-subtle); }
|
|
224
293
|
tr:last-child td { border-bottom: none; }
|
|
225
294
|
tr:hover td { background: rgba(255,255,255,.02); }
|
|
226
295
|
.tag-vendor {
|
|
227
296
|
display: inline-block;
|
|
228
|
-
font-size: 11px; padding:
|
|
297
|
+
font-size: 11px; padding: 2px 8px;
|
|
229
298
|
border-radius: 10px;
|
|
230
|
-
background: rgba(188,140,255,.
|
|
299
|
+
background: rgba(188,140,255,.1);
|
|
231
300
|
color: var(--accent-purple);
|
|
232
|
-
border: 1px solid rgba(188,140,255,.
|
|
301
|
+
border: 1px solid rgba(188,140,255,.2);
|
|
302
|
+
font-weight: 500;
|
|
233
303
|
}
|
|
234
|
-
.arrow { color: var(--text-
|
|
304
|
+
.arrow { color: var(--text-tertiary); margin: 0 4px; }
|
|
235
305
|
/* ── 时间区间选择栏 ── */
|
|
236
306
|
.time-range-bar {
|
|
237
307
|
display: flex; align-items: center; gap: 8px;
|
|
238
|
-
margin-bottom:
|
|
308
|
+
margin-bottom: 18px; flex-wrap: wrap;
|
|
309
|
+
padding: 10px 14px;
|
|
310
|
+
background: rgba(22,27,34,.6);
|
|
311
|
+
border: 1px solid var(--border-subtle);
|
|
312
|
+
border-radius: var(--radius);
|
|
313
|
+
backdrop-filter: blur(8px);
|
|
239
314
|
}
|
|
240
|
-
.time-range-label { font-size:
|
|
315
|
+
.time-range-label { font-size: 12px; color: var(--text-tertiary); font-weight: 500; }
|
|
241
316
|
.range-btn {
|
|
242
317
|
padding: 4px 14px; border-radius: 14px;
|
|
243
|
-
background:
|
|
244
|
-
border: 1px solid
|
|
318
|
+
background: transparent;
|
|
319
|
+
border: 1px solid transparent;
|
|
245
320
|
color: var(--text-secondary);
|
|
246
321
|
font-size: 12px; cursor: pointer;
|
|
247
|
-
transition:
|
|
322
|
+
transition: all .2s ease;
|
|
248
323
|
}
|
|
249
|
-
.range-btn:hover { background:
|
|
324
|
+
.range-btn:hover { background: rgba(255,255,255,.05); color: var(--text-primary); }
|
|
250
325
|
.range-btn.active {
|
|
251
|
-
background: rgba(88,166,255,.
|
|
252
|
-
border-color: rgba(88,166,255,.
|
|
326
|
+
background: rgba(88,166,255,.12);
|
|
327
|
+
border-color: rgba(88,166,255,.35);
|
|
253
328
|
color: var(--accent-blue);
|
|
329
|
+
font-weight: 500;
|
|
254
330
|
}
|
|
255
331
|
.range-custom { display: none; align-items: center; gap: 6px; }
|
|
256
332
|
.range-custom.visible { display: flex; }
|
|
257
333
|
.range-date {
|
|
258
|
-
padding: 3px
|
|
259
|
-
background:
|
|
334
|
+
padding: 3px 10px; border-radius: var(--radius-sm);
|
|
335
|
+
background: rgba(48,54,61,.4); border: 1px solid var(--border);
|
|
260
336
|
color: var(--text-primary); font-size: 12px;
|
|
261
337
|
color-scheme: dark;
|
|
338
|
+
transition: border-color .2s;
|
|
262
339
|
}
|
|
263
|
-
.range-
|
|
340
|
+
.range-date:focus { outline: none; border-color: rgba(88,166,255,.5); }
|
|
341
|
+
.range-sep { font-size: 12px; color: var(--text-tertiary); }
|
|
264
342
|
/* ── 空态 ── */
|
|
265
343
|
.empty {
|
|
266
344
|
text-align: center; padding: 32px;
|
|
267
|
-
color: var(--text-
|
|
345
|
+
color: var(--text-tertiary); font-size: 13px;
|
|
268
346
|
}
|
|
347
|
+
.empty-icon { font-size: 28px; margin-bottom: 8px; opacity: .5; }
|
|
269
348
|
/* ── 加载态 ── */
|
|
270
349
|
.loading { opacity: .4; pointer-events: none; }
|
|
350
|
+
/* ── 图表标签截断 ── */
|
|
351
|
+
.chart-legend-note { font-size: 11px; color: var(--text-tertiary); margin-top: 4px; text-align: center; }
|
|
271
352
|
</style>
|
|
272
353
|
</head>
|
|
273
354
|
<body>
|
|
@@ -286,28 +367,52 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
286
367
|
<main>
|
|
287
368
|
<!-- 时间区间选择器 -->
|
|
288
369
|
<div class="time-range-bar">
|
|
289
|
-
<span class="time-range-label"
|
|
290
|
-
<button class="range-btn active" onclick="setTimeRange(7, this)"
|
|
291
|
-
<button class="range-btn" onclick="setTimeRange(30, this)"
|
|
370
|
+
<span class="time-range-label">时间区间</span>
|
|
371
|
+
<button class="range-btn active" onclick="setTimeRange(7, this)">近 7 天</button>
|
|
372
|
+
<button class="range-btn" onclick="setTimeRange(30, this)">近 30 天</button>
|
|
292
373
|
<button class="range-btn" onclick="setTimeRange(0, this)">自选区间</button>
|
|
293
374
|
<div class="range-custom" id="range-custom">
|
|
294
375
|
<input type="date" id="range-start" class="range-date" onchange="applyCustomRange()" />
|
|
295
|
-
<span class="range-sep"
|
|
376
|
+
<span class="range-sep">→</span>
|
|
296
377
|
<input type="date" id="range-end" class="range-date" onchange="applyCustomRange()" />
|
|
297
378
|
</div>
|
|
298
379
|
</div>
|
|
299
380
|
|
|
300
381
|
<!-- KPI 卡片 -->
|
|
301
382
|
<div class="kpi-grid" id="kpi-grid">
|
|
302
|
-
<div class="kpi-card"
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
<div class="kpi-card"
|
|
383
|
+
<div class="kpi-card">
|
|
384
|
+
<div class="kpi-header"><span class="kpi-icon">📊</span><span class="kpi-label">今日请求数</span></div>
|
|
385
|
+
<div class="kpi-value color-blue" id="kpi-req-today">–</div>
|
|
386
|
+
<div class="kpi-sub" id="kpi-req-week">本周 –</div>
|
|
387
|
+
</div>
|
|
388
|
+
<div class="kpi-card">
|
|
389
|
+
<div class="kpi-header"><span class="kpi-icon">🔢</span><span class="kpi-label">今日 Token 总量</span></div>
|
|
390
|
+
<div class="kpi-value color-purple" id="kpi-tok-today">–</div>
|
|
391
|
+
<div class="kpi-sub" id="kpi-tok-week">本周 –</div>
|
|
392
|
+
</div>
|
|
393
|
+
<div class="kpi-card">
|
|
394
|
+
<div class="kpi-header"><span class="kpi-icon">💬</span><span class="kpi-label">今日输出 Token</span></div>
|
|
395
|
+
<div class="kpi-value color-green" id="kpi-out-today">–</div>
|
|
396
|
+
<div class="kpi-sub" id="kpi-out-week">本周 –</div>
|
|
397
|
+
</div>
|
|
398
|
+
<div class="kpi-card">
|
|
399
|
+
<div class="kpi-header"><span class="kpi-icon">💰</span><span class="kpi-label">今日费用估算</span></div>
|
|
400
|
+
<div class="kpi-value color-yellow" id="kpi-cost-today">–</div>
|
|
401
|
+
<div class="kpi-sub" id="kpi-cost-week">本周 –</div>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="kpi-card">
|
|
404
|
+
<div class="kpi-header"><span class="kpi-icon">🔄</span><span class="kpi-label">故障转移(今日)</span></div>
|
|
405
|
+
<div class="kpi-value color-red" id="kpi-fo-today">–</div>
|
|
406
|
+
<div class="kpi-sub" id="kpi-fo-week">本周 –</div>
|
|
407
|
+
</div>
|
|
408
|
+
<div class="kpi-card">
|
|
409
|
+
<div class="kpi-header"><span class="kpi-icon">⚡</span><span class="kpi-label">平均延迟(今日)</span></div>
|
|
410
|
+
<div class="kpi-value color-orange" id="kpi-lat-today">–</div>
|
|
411
|
+
<div class="kpi-sub" id="kpi-lat-week">本周 –</div>
|
|
412
|
+
</div>
|
|
308
413
|
</div>
|
|
309
414
|
|
|
310
|
-
<!-- 供应商状态 +
|
|
415
|
+
<!-- 供应商状态 + 请求量趋势折线图 -->
|
|
311
416
|
<div class="charts-grid">
|
|
312
417
|
<div class="card">
|
|
313
418
|
<div class="card-title">供应商状态</div>
|
|
@@ -323,8 +428,8 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
323
428
|
</div>
|
|
324
429
|
</div>
|
|
325
430
|
|
|
326
|
-
<!-- 供应商分布 + Token
|
|
327
|
-
<div class="charts-grid-
|
|
431
|
+
<!-- 供应商分布 + Token 量趋势(按 vendor) -->
|
|
432
|
+
<div class="charts-grid-2">
|
|
328
433
|
<div class="card">
|
|
329
434
|
<div class="card-title" id="title-vendor-dist">供应商请求分布(近 7 天)</div>
|
|
330
435
|
<div class="chart-wrap">
|
|
@@ -332,45 +437,48 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
332
437
|
</div>
|
|
333
438
|
</div>
|
|
334
439
|
<div class="card">
|
|
335
|
-
<div class="card-title" id="title-token-timeline">近 7 天 Token
|
|
440
|
+
<div class="card-title" id="title-token-timeline">近 7 天 Token 量趋势(按供应商)</div>
|
|
336
441
|
<div class="chart-wrap-lg">
|
|
337
442
|
<canvas id="chart-token-timeline"></canvas>
|
|
338
443
|
</div>
|
|
339
444
|
</div>
|
|
340
445
|
</div>
|
|
341
446
|
|
|
342
|
-
<!--
|
|
343
|
-
<div class="card">
|
|
344
|
-
<div class="card-title"
|
|
345
|
-
<div class="
|
|
346
|
-
<
|
|
347
|
-
<thead>
|
|
348
|
-
<tr>
|
|
349
|
-
<th>来源供应商</th>
|
|
350
|
-
<th>目标供应商</th>
|
|
351
|
-
<th>次数</th>
|
|
352
|
-
</tr>
|
|
353
|
-
</thead>
|
|
354
|
-
<tbody id="ft-tbody">
|
|
355
|
-
<tr><td colspan="3" class="empty">加载中…</td></tr>
|
|
356
|
-
</tbody>
|
|
357
|
-
</table>
|
|
447
|
+
<!-- Token 用量(按 Vendor / 模型)堆叠图 -->
|
|
448
|
+
<div class="card" style="margin-bottom:12px">
|
|
449
|
+
<div class="card-title" id="title-model-token-timeline">近 7 天 Token 用量(按 Vendor / 模型)</div>
|
|
450
|
+
<div class="chart-wrap-xl">
|
|
451
|
+
<canvas id="chart-model-token-timeline"></canvas>
|
|
358
452
|
</div>
|
|
359
453
|
</div>
|
|
454
|
+
|
|
360
455
|
</main>
|
|
361
456
|
|
|
362
457
|
<script>
|
|
363
458
|
// ── 颜色配置 ──────────────────────────────────────────────
|
|
459
|
+
// 调色盘参考 Tailwind CSS 400-level,深色背景高区分度最佳实践
|
|
364
460
|
const VENDOR_COLORS = [
|
|
365
|
-
'#
|
|
366
|
-
'#
|
|
461
|
+
'#60A5FA', // blue-400
|
|
462
|
+
'#FB923C', // orange-400
|
|
463
|
+
'#34D399', // emerald-400
|
|
464
|
+
'#A78BFA', // violet-400
|
|
465
|
+
'#F87171', // red-400
|
|
466
|
+
'#38BDF8', // sky-400
|
|
467
|
+
'#FBBF24', // amber-400
|
|
468
|
+
'#F472B6', // pink-400
|
|
469
|
+
'#4ADE80', // green-400
|
|
470
|
+
'#E879F9', // fuchsia-400
|
|
471
|
+
'#818CF8', // indigo-400
|
|
472
|
+
'#2DD4BF', // teal-400
|
|
473
|
+
'#FB7185', // rose-400
|
|
474
|
+
'#FCD34D', // yellow-300
|
|
475
|
+
'#6EE7B7', // emerald-300
|
|
476
|
+
'#C4B5FD', // violet-300
|
|
477
|
+
'#7DD3FC', // sky-300
|
|
478
|
+
'#FED7AA', // orange-200
|
|
479
|
+
'#FECDD3', // rose-200
|
|
480
|
+
'#BBF7D0', // green-200
|
|
367
481
|
];
|
|
368
|
-
const TOKEN_COLORS = {
|
|
369
|
-
input: '#58a6ff',
|
|
370
|
-
output: '#3fb950',
|
|
371
|
-
cache_creation: '#d29922',
|
|
372
|
-
cache_read: '#bc8cff',
|
|
373
|
-
};
|
|
374
482
|
|
|
375
483
|
// ── 工具函数 ──────────────────────────────────────────────
|
|
376
484
|
function fmtTokens(n) {
|
|
@@ -385,20 +493,95 @@ function now() {
|
|
|
385
493
|
return new Date().toLocaleTimeString('zh-CN', {hour:'2-digit',minute:'2-digit',second:'2-digit'});
|
|
386
494
|
}
|
|
387
495
|
|
|
496
|
+
// ── 渐变填充工具 ──────────────────────────────────────────
|
|
497
|
+
function makeGradient(ctx, color) {
|
|
498
|
+
const h = ctx.canvas.height;
|
|
499
|
+
const grad = ctx.createLinearGradient(0, 0, 0, h);
|
|
500
|
+
grad.addColorStop(0, color + '44');
|
|
501
|
+
grad.addColorStop(1, color + '04');
|
|
502
|
+
return grad;
|
|
503
|
+
}
|
|
504
|
+
|
|
388
505
|
// ── Chart.js 全局默认 ─────────────────────────────────────
|
|
389
506
|
Chart.defaults.color = '#8b949e';
|
|
390
|
-
Chart.defaults.borderColor = '
|
|
507
|
+
Chart.defaults.borderColor = 'rgba(255,255,255,.04)';
|
|
391
508
|
Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
392
|
-
Chart.defaults.font.size =
|
|
509
|
+
Chart.defaults.font.size = 11;
|
|
510
|
+
|
|
511
|
+
const COMMON_SCALE_X = { grid: { display: false }, ticks: { maxTicksLimit: 10 } };
|
|
512
|
+
const COMMON_SCALE_Y = { grid: { color: 'rgba(255,255,255,.04)' }, beginAtZero: true };
|
|
513
|
+
const COMMON_LEGEND = {
|
|
514
|
+
position: 'bottom',
|
|
515
|
+
labels: {
|
|
516
|
+
boxWidth: 8,
|
|
517
|
+
padding: 14,
|
|
518
|
+
usePointStyle: true,
|
|
519
|
+
pointStyle: 'circle',
|
|
520
|
+
pointStyleWidth: 8,
|
|
521
|
+
font: { size: 11 },
|
|
522
|
+
generateLabels: chart => {
|
|
523
|
+
const items = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
|
524
|
+
items.forEach(item => { item.pointStyle = 'circle'; item.lineWidth = 0; });
|
|
525
|
+
return items;
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
const COMMON_LINE_DATASET = { tension: .35, pointRadius: 0, pointHoverRadius: 5, borderWidth: 2 };
|
|
530
|
+
|
|
531
|
+
// ── Legend 点击交互:单击=仅选该项,Ctrl/Meta+单击=多选追加,Shift+单击=排除 ──
|
|
532
|
+
function legendOnClick(e, legendItem, legend) {
|
|
533
|
+
const chart = legend.chart;
|
|
534
|
+
const isShift = e.native.shiftKey;
|
|
535
|
+
const isCtrl = e.native.ctrlKey || e.native.metaKey;
|
|
536
|
+
if (chart.config.type === 'doughnut' || chart.config.type === 'pie') {
|
|
537
|
+
const idx = legendItem.index;
|
|
538
|
+
const dataLen = chart.data.labels.length;
|
|
539
|
+
if (isShift) {
|
|
540
|
+
chart.toggleDataVisibility(idx);
|
|
541
|
+
} else if (isCtrl) {
|
|
542
|
+
if (!chart.getDataVisibility(idx)) chart.toggleDataVisibility(idx);
|
|
543
|
+
} else {
|
|
544
|
+
const allOthersHidden = [...Array(dataLen).keys()].filter(i => i !== idx).every(i => !chart.getDataVisibility(i));
|
|
545
|
+
if (allOthersHidden) {
|
|
546
|
+
for (let i = 0; i < dataLen; i++) { if (!chart.getDataVisibility(i)) chart.toggleDataVisibility(i); }
|
|
547
|
+
} else {
|
|
548
|
+
for (let i = 0; i < dataLen; i++) {
|
|
549
|
+
const vis = chart.getDataVisibility(i);
|
|
550
|
+
if (i === idx && !vis) chart.toggleDataVisibility(i);
|
|
551
|
+
if (i !== idx && vis) chart.toggleDataVisibility(i);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
const idx = legendItem.datasetIndex;
|
|
557
|
+
const datasets = chart.data.datasets;
|
|
558
|
+
if (isShift) {
|
|
559
|
+
const meta = chart.getDatasetMeta(idx);
|
|
560
|
+
meta.hidden = !meta.hidden;
|
|
561
|
+
} else if (isCtrl) {
|
|
562
|
+
chart.getDatasetMeta(idx).hidden = false;
|
|
563
|
+
} else {
|
|
564
|
+
const allOthersHidden = datasets.every((_, i) => i === idx || !!chart.getDatasetMeta(i).hidden);
|
|
565
|
+
if (allOthersHidden) {
|
|
566
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = false; });
|
|
567
|
+
} else {
|
|
568
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = (i !== idx); });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
chart.update();
|
|
573
|
+
}
|
|
393
574
|
|
|
394
575
|
// ── 图表实例 ──────────────────────────────────────────────
|
|
395
576
|
let chartTimeline = null;
|
|
396
577
|
let chartVendorDist = null;
|
|
397
578
|
let chartTokenTimeline = null;
|
|
579
|
+
let chartModelTokenTimeline = null;
|
|
398
580
|
|
|
399
581
|
function destroyCharts() {
|
|
400
|
-
[chartTimeline, chartVendorDist, chartTokenTimeline]
|
|
401
|
-
|
|
582
|
+
[chartTimeline, chartVendorDist, chartTokenTimeline, chartModelTokenTimeline]
|
|
583
|
+
.forEach(c => c && c.destroy());
|
|
584
|
+
chartTimeline = chartVendorDist = chartTokenTimeline = chartModelTokenTimeline = null;
|
|
402
585
|
}
|
|
403
586
|
|
|
404
587
|
// ── 数据拉取 ──────────────────────────────────────────────
|
|
@@ -410,28 +593,29 @@ async function fetchJSON(url) {
|
|
|
410
593
|
|
|
411
594
|
// ── KPI 更新 ──────────────────────────────────────────────
|
|
412
595
|
function updateKPI(summary) {
|
|
413
|
-
const t = summary.today,
|
|
596
|
+
const t = summary.today, r = summary.range;
|
|
597
|
+
const lbl = currentRangeLabel;
|
|
414
598
|
|
|
415
599
|
document.getElementById('kpi-req-today').textContent = fmtNum(t.requests);
|
|
416
|
-
document.getElementById('kpi-req-week').textContent = '
|
|
600
|
+
document.getElementById('kpi-req-week').textContent = lbl + ' ' + fmtNum(r.requests);
|
|
417
601
|
|
|
418
|
-
const tokT = t.tokens,
|
|
602
|
+
const tokT = t.tokens, tokR = r.tokens;
|
|
419
603
|
const totalT = tokT.input + tokT.output + tokT.cache_creation + tokT.cache_read;
|
|
420
|
-
const
|
|
604
|
+
const totalR = tokR.input + tokR.output + tokR.cache_creation + tokR.cache_read;
|
|
421
605
|
document.getElementById('kpi-tok-today').textContent = fmtTokens(totalT);
|
|
422
|
-
document.getElementById('kpi-tok-week').textContent = '
|
|
606
|
+
document.getElementById('kpi-tok-week').textContent = lbl + ' ' + fmtTokens(totalR);
|
|
423
607
|
|
|
424
608
|
document.getElementById('kpi-out-today').textContent = fmtTokens(tokT.output);
|
|
425
|
-
document.getElementById('kpi-out-week').textContent = '
|
|
609
|
+
document.getElementById('kpi-out-week').textContent = lbl + ' ' + fmtTokens(tokR.output);
|
|
426
610
|
|
|
427
611
|
document.getElementById('kpi-cost-today').textContent = t.cost || '–';
|
|
428
|
-
document.getElementById('kpi-cost-week').textContent = '
|
|
612
|
+
document.getElementById('kpi-cost-week').textContent = lbl + ' ' + (r.cost || '–');
|
|
429
613
|
|
|
430
614
|
document.getElementById('kpi-fo-today').textContent = fmtNum(t.failovers);
|
|
431
|
-
document.getElementById('kpi-fo-week').textContent = '
|
|
615
|
+
document.getElementById('kpi-fo-week').textContent = lbl + ' ' + fmtNum(r.failovers);
|
|
432
616
|
|
|
433
617
|
document.getElementById('kpi-lat-today').textContent = t.avg_duration_ms ? t.avg_duration_ms + 'ms' : '–';
|
|
434
|
-
document.getElementById('kpi-lat-week').textContent = '
|
|
618
|
+
document.getElementById('kpi-lat-week').textContent = lbl + ' ' + (r.avg_duration_ms ? r.avg_duration_ms + 'ms' : '–');
|
|
435
619
|
}
|
|
436
620
|
|
|
437
621
|
// ── 供应商状态 ────────────────────────────────────────────
|
|
@@ -459,41 +643,47 @@ function quotaBarColor(pct) {
|
|
|
459
643
|
if (pct >= 70) return 'var(--accent-yellow)';
|
|
460
644
|
return 'var(--accent-green)';
|
|
461
645
|
}
|
|
646
|
+
function quotaWindowLabel(wh) {
|
|
647
|
+
if (!wh) return '配额';
|
|
648
|
+
const h = parseFloat(wh);
|
|
649
|
+
if (h >= 24) return Math.round(h / 24) + 'd配额';
|
|
650
|
+
return Math.round(h) + 'h配额';
|
|
651
|
+
}
|
|
652
|
+
function renderQuotaBar(qg) {
|
|
653
|
+
if (!qg || qg.usage_percent == null) return '';
|
|
654
|
+
const pct = Math.round(qg.usage_percent);
|
|
655
|
+
const label = quotaWindowLabel(qg.window_hours);
|
|
656
|
+
return `<span class="status-badge ${quotaClass(pct)}">${label} ${pct}%</span>` +
|
|
657
|
+
`<div class="quota-bar-wrap"><div class="quota-bar-bg">` +
|
|
658
|
+
`<div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div>` +
|
|
659
|
+
`</div></div>`;
|
|
660
|
+
}
|
|
462
661
|
|
|
463
662
|
function updateVendorStatus(status) {
|
|
464
663
|
const tiers = status.tiers || [];
|
|
465
664
|
const list = document.getElementById('vendor-list');
|
|
466
665
|
if (!tiers.length) {
|
|
467
|
-
list.innerHTML = '<div class="empty">无供应商数据</div>';
|
|
666
|
+
list.innerHTML = '<div class="empty"><div class="empty-icon">🔌</div>无供应商数据</div>';
|
|
468
667
|
return;
|
|
469
668
|
}
|
|
470
669
|
list.innerHTML = tiers.map(tier => {
|
|
471
670
|
const cb = tier.circuit_breaker || {};
|
|
472
|
-
const qg = tier.quota_guard || {};
|
|
473
|
-
const wqg = tier.weekly_quota_guard || {};
|
|
474
671
|
const cbClass = cbStateClass(cb.state);
|
|
475
672
|
const cbLabel = cbStateLabel(cb.state);
|
|
476
|
-
const
|
|
477
|
-
const wpct = wqg.usage_percent != null ? Math.round(wqg.usage_percent) : null;
|
|
673
|
+
const initial = (tier.name || '?').charAt(0).toUpperCase();
|
|
478
674
|
|
|
479
675
|
let quotaHTML = '';
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
<span class="status-badge ${quotaClass(pct)}">日配额 ${pct}%</span>
|
|
483
|
-
<div class="quota-bar-wrap">
|
|
484
|
-
<div class="quota-bar-bg"><div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div></div>
|
|
485
|
-
</div>`;
|
|
486
|
-
}
|
|
487
|
-
if (wpct != null) {
|
|
488
|
-
quotaHTML += `<span class="status-badge ${quotaClass(wpct)}">周配额 ${wpct}%</span>`;
|
|
489
|
-
}
|
|
676
|
+
if (tier.quota_guard) quotaHTML += renderQuotaBar(tier.quota_guard);
|
|
677
|
+
if (tier.weekly_quota_guard) quotaHTML += renderQuotaBar(tier.weekly_quota_guard);
|
|
490
678
|
|
|
491
679
|
const rlInfo = tier.rate_limit || {};
|
|
492
|
-
const rlHtml = rlInfo.limited
|
|
493
|
-
? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
680
|
+
const rlHtml = rlInfo.limited ? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
494
681
|
|
|
495
682
|
return `<div class="vendor-item">
|
|
496
|
-
<
|
|
683
|
+
<div class="vendor-info">
|
|
684
|
+
<div class="vendor-avatar">${initial}</div>
|
|
685
|
+
<span class="vendor-name">${tier.name}</span>
|
|
686
|
+
</div>
|
|
497
687
|
<div class="vendor-badges">
|
|
498
688
|
<span class="status-badge ${cbClass}">${cbLabel}${cb.failure_count ? ' ×'+cb.failure_count : ''}</span>
|
|
499
689
|
${quotaHTML}
|
|
@@ -503,10 +693,9 @@ function updateVendorStatus(status) {
|
|
|
503
693
|
}).join('');
|
|
504
694
|
}
|
|
505
695
|
|
|
506
|
-
// ──
|
|
696
|
+
// ── 时序折线图(请求量,按 vendor)────────────────────────
|
|
507
697
|
function buildTimeline(rows) {
|
|
508
|
-
|
|
509
|
-
const vendorDateMap = {}; // vendor → {date → count}
|
|
698
|
+
const vendorDateMap = {};
|
|
510
699
|
const allDates = new Set();
|
|
511
700
|
for (const r of rows) {
|
|
512
701
|
const v = r.vendor, d = r.date;
|
|
@@ -518,29 +707,33 @@ function buildTimeline(rows) {
|
|
|
518
707
|
const dates = [...allDates].sort();
|
|
519
708
|
const vendors = Object.keys(vendorDateMap).sort();
|
|
520
709
|
|
|
521
|
-
const datasets = vendors.map((v, i) => ({
|
|
522
|
-
label: v,
|
|
523
|
-
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
524
|
-
borderColor: VENDOR_COLORS[i % VENDOR_COLORS.length],
|
|
525
|
-
backgroundColor: VENDOR_COLORS[i % VENDOR_COLORS.length] + '22',
|
|
526
|
-
fill: true,
|
|
527
|
-
tension: .3,
|
|
528
|
-
pointRadius: 3,
|
|
529
|
-
pointHoverRadius: 5,
|
|
530
|
-
}));
|
|
531
|
-
|
|
532
710
|
if (chartTimeline) chartTimeline.destroy();
|
|
533
711
|
const ctx = document.getElementById('chart-timeline').getContext('2d');
|
|
712
|
+
const datasets = vendors.map((v, i) => {
|
|
713
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
714
|
+
return {
|
|
715
|
+
...COMMON_LINE_DATASET,
|
|
716
|
+
label: v,
|
|
717
|
+
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
718
|
+
borderColor: color,
|
|
719
|
+
backgroundColor: makeGradient(ctx, color),
|
|
720
|
+
fill: true,
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
|
|
534
724
|
chartTimeline = new Chart(ctx, {
|
|
535
725
|
type: 'line',
|
|
536
726
|
data: { labels: dates, datasets },
|
|
537
727
|
options: {
|
|
538
728
|
responsive: true, maintainAspectRatio: false,
|
|
539
729
|
interaction: { mode: 'index', intersect: false },
|
|
540
|
-
plugins: {
|
|
730
|
+
plugins: {
|
|
731
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
732
|
+
tooltip: { itemSort: (a, b) => (b.raw || 0) - (a.raw || 0) },
|
|
733
|
+
},
|
|
541
734
|
scales: {
|
|
542
|
-
x:
|
|
543
|
-
y: {
|
|
735
|
+
x: COMMON_SCALE_X,
|
|
736
|
+
y: { ...COMMON_SCALE_Y, ticks: { precision: 0 } },
|
|
544
737
|
},
|
|
545
738
|
},
|
|
546
739
|
});
|
|
@@ -560,7 +753,7 @@ function buildVendorDist(rows) {
|
|
|
560
753
|
if (chartVendorDist) chartVendorDist.destroy();
|
|
561
754
|
const ctx = document.getElementById('chart-vendor-dist').getContext('2d');
|
|
562
755
|
if (!labels.length) {
|
|
563
|
-
ctx.canvas.parentElement.innerHTML = '<div class="empty">暂无数据</div>';
|
|
756
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
564
757
|
return;
|
|
565
758
|
}
|
|
566
759
|
chartVendorDist = new Chart(ctx, {
|
|
@@ -571,23 +764,22 @@ function buildVendorDist(rows) {
|
|
|
571
764
|
data,
|
|
572
765
|
backgroundColor: labels.map((_,i) => VENDOR_COLORS[i % VENDOR_COLORS.length]),
|
|
573
766
|
borderWidth: 0,
|
|
574
|
-
hoverOffset:
|
|
767
|
+
hoverOffset: 8,
|
|
575
768
|
}],
|
|
576
769
|
},
|
|
577
770
|
options: {
|
|
578
771
|
responsive: true, maintainAspectRatio: false,
|
|
579
772
|
plugins: {
|
|
580
|
-
legend: {
|
|
581
|
-
tooltip: { callbacks: { label:
|
|
773
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
774
|
+
tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw.toLocaleString()} 次` } },
|
|
582
775
|
},
|
|
583
776
|
},
|
|
584
777
|
});
|
|
585
778
|
}
|
|
586
779
|
|
|
587
|
-
// ── Token
|
|
780
|
+
// ── Token 量趋势折线图(按 vendor)───────────────────────
|
|
588
781
|
function buildTokenTimeline(rows) {
|
|
589
|
-
|
|
590
|
-
const vendorDateMap = {}; // vendor → {date → total_tokens}
|
|
782
|
+
const vendorDateMap = {};
|
|
591
783
|
const allDates = new Set();
|
|
592
784
|
for (const r of rows) {
|
|
593
785
|
const v = r.vendor, d = r.date;
|
|
@@ -604,20 +796,21 @@ function buildTokenTimeline(rows) {
|
|
|
604
796
|
if (chartTokenTimeline) chartTokenTimeline.destroy();
|
|
605
797
|
const ctx = document.getElementById('chart-token-timeline').getContext('2d');
|
|
606
798
|
if (!dates.length) {
|
|
607
|
-
ctx.canvas.parentElement.innerHTML = '<div class="empty">暂无数据</div>';
|
|
799
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
608
800
|
return;
|
|
609
801
|
}
|
|
610
802
|
|
|
611
|
-
const datasets = vendors.map((v, i) =>
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
803
|
+
const datasets = vendors.map((v, i) => {
|
|
804
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
805
|
+
return {
|
|
806
|
+
...COMMON_LINE_DATASET,
|
|
807
|
+
label: v,
|
|
808
|
+
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
809
|
+
borderColor: color,
|
|
810
|
+
backgroundColor: makeGradient(ctx, color),
|
|
811
|
+
fill: true,
|
|
812
|
+
};
|
|
813
|
+
});
|
|
621
814
|
|
|
622
815
|
chartTokenTimeline = new Chart(ctx, {
|
|
623
816
|
type: 'line',
|
|
@@ -625,44 +818,122 @@ function buildTokenTimeline(rows) {
|
|
|
625
818
|
options: {
|
|
626
819
|
responsive: true, maintainAspectRatio: false,
|
|
627
820
|
interaction: { mode: 'index', intersect: false },
|
|
628
|
-
plugins: {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
ticks: { callback: v => fmtTokens(v) },
|
|
821
|
+
plugins: {
|
|
822
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
823
|
+
tooltip: {
|
|
824
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
825
|
+
callbacks: { label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}` },
|
|
634
826
|
},
|
|
635
827
|
},
|
|
828
|
+
scales: {
|
|
829
|
+
x: COMMON_SCALE_X,
|
|
830
|
+
y: { ...COMMON_SCALE_Y, ticks: { callback: v => fmtTokens(v) } },
|
|
831
|
+
},
|
|
636
832
|
},
|
|
637
833
|
});
|
|
638
834
|
}
|
|
639
835
|
|
|
640
|
-
// ──
|
|
641
|
-
function
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
836
|
+
// ── Token 用量趋势(按 Vendor / 模型,堆叠面积图)────────
|
|
837
|
+
function buildModelTokenTimeline(rows) {
|
|
838
|
+
const modelDateMap = {};
|
|
839
|
+
const allDates = new Set();
|
|
840
|
+
for (const r of rows) {
|
|
841
|
+
const key = (r.vendor || '?') + ' / ' + (r.model_served || '?');
|
|
842
|
+
const d = r.date;
|
|
843
|
+
if (!d) continue;
|
|
844
|
+
if (!modelDateMap[key]) modelDateMap[key] = {};
|
|
845
|
+
const total = (r.total_input || 0) + (r.total_output || 0)
|
|
846
|
+
+ (r.total_cache_creation || 0) + (r.total_cache_read || 0);
|
|
847
|
+
modelDateMap[key][d] = (modelDateMap[key][d] || 0) + total;
|
|
848
|
+
allDates.add(d);
|
|
849
|
+
}
|
|
850
|
+
const dates = [...allDates].sort();
|
|
851
|
+
// 按总量降序排列 key
|
|
852
|
+
const keys = Object.keys(modelDateMap).sort((a, b) => {
|
|
853
|
+
const sumA = Object.values(modelDateMap[a]).reduce((s, v) => s + v, 0);
|
|
854
|
+
const sumB = Object.values(modelDateMap[b]).reduce((s, v) => s + v, 0);
|
|
855
|
+
return sumB - sumA;
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
if (chartModelTokenTimeline) chartModelTokenTimeline.destroy();
|
|
859
|
+
const canvasEl = document.getElementById('chart-model-token-timeline');
|
|
860
|
+
if (!canvasEl) return;
|
|
861
|
+
const ctx = canvasEl.getContext('2d');
|
|
862
|
+
if (!dates.length || !keys.length) {
|
|
863
|
+
ctx.canvas.parentElement.innerHTML = '<div class="empty"><div class="empty-icon">📭</div>暂无数据</div>';
|
|
645
864
|
return;
|
|
646
865
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
866
|
+
|
|
867
|
+
const datasets = keys.map((key, i) => {
|
|
868
|
+
const color = VENDOR_COLORS[i % VENDOR_COLORS.length];
|
|
869
|
+
return {
|
|
870
|
+
...COMMON_LINE_DATASET,
|
|
871
|
+
label: key,
|
|
872
|
+
data: dates.map(d => modelDateMap[key][d] || 0),
|
|
873
|
+
borderColor: color,
|
|
874
|
+
backgroundColor: color + '30',
|
|
875
|
+
fill: true,
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
chartModelTokenTimeline = new Chart(ctx, {
|
|
880
|
+
type: 'line',
|
|
881
|
+
data: { labels: dates, datasets },
|
|
882
|
+
options: {
|
|
883
|
+
responsive: true, maintainAspectRatio: false,
|
|
884
|
+
interaction: { mode: 'index', intersect: false },
|
|
885
|
+
plugins: {
|
|
886
|
+
legend: {
|
|
887
|
+
position: keys.length > 8 ? 'right' : 'bottom',
|
|
888
|
+
onClick: legendOnClick,
|
|
889
|
+
labels: {
|
|
890
|
+
...COMMON_LEGEND.labels,
|
|
891
|
+
generateLabels: chart => {
|
|
892
|
+
const items = COMMON_LEGEND.labels.generateLabels(chart);
|
|
893
|
+
const maxLen = 32;
|
|
894
|
+
items.forEach(item => {
|
|
895
|
+
if (item.text.length > maxLen) item.text = item.text.slice(0, maxLen) + '…';
|
|
896
|
+
});
|
|
897
|
+
return items;
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
},
|
|
901
|
+
tooltip: {
|
|
902
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
903
|
+
callbacks: {
|
|
904
|
+
label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}`,
|
|
905
|
+
footer: items => {
|
|
906
|
+
const total = items.reduce((s, i) => s + (i.raw || 0), 0);
|
|
907
|
+
return total > 0 ? '合计: ' + fmtTokens(total) : '';
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
scales: {
|
|
913
|
+
x: COMMON_SCALE_X,
|
|
914
|
+
y: {
|
|
915
|
+
...COMMON_SCALE_Y,
|
|
916
|
+
stacked: true,
|
|
917
|
+
ticks: { callback: v => fmtTokens(v) },
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
});
|
|
653
922
|
}
|
|
654
923
|
|
|
655
924
|
// ── 时间区间控制 ──────────────────────────────────────────
|
|
656
925
|
let currentDays = 7;
|
|
926
|
+
let currentRangeLabel = '本周';
|
|
657
927
|
|
|
658
928
|
function setTimeRange(days, btn) {
|
|
659
929
|
currentDays = days;
|
|
930
|
+
if (days === 7) currentRangeLabel = '本周';
|
|
931
|
+
else if (days === 30) currentRangeLabel = '本月';
|
|
660
932
|
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
|
|
661
933
|
if (btn) btn.classList.add('active');
|
|
662
934
|
const customEl = document.getElementById('range-custom');
|
|
663
935
|
if (days === 0) {
|
|
664
936
|
customEl.classList.add('visible');
|
|
665
|
-
// 初始化日期:默认今天往前 7 天
|
|
666
937
|
const today = new Date();
|
|
667
938
|
const weekAgo = new Date(today);
|
|
668
939
|
weekAgo.setDate(weekAgo.getDate() - 6);
|
|
@@ -683,23 +954,20 @@ function applyCustomRange() {
|
|
|
683
954
|
const endMs = new Date(e).getTime();
|
|
684
955
|
if (endMs < startMs) return;
|
|
685
956
|
currentDays = Math.ceil((endMs - startMs) / 86400000) + 1;
|
|
957
|
+
currentRangeLabel = s + '—' + e;
|
|
686
958
|
refresh();
|
|
687
959
|
}
|
|
688
960
|
|
|
689
|
-
function rangeLabel() {
|
|
690
|
-
if (currentDays <= 7) return '近 7 天';
|
|
691
|
-
if (currentDays <= 30) return '近 30 天';
|
|
692
|
-
return '近 ' + currentDays + ' 天';
|
|
693
|
-
}
|
|
694
|
-
|
|
695
961
|
function updateChartTitles(days) {
|
|
696
962
|
const label = days <= 7 ? '近 7 天' : (days <= 30 ? '近 30 天' : '近 ' + days + ' 天');
|
|
697
963
|
const tl = document.getElementById('title-timeline');
|
|
698
964
|
const tt = document.getElementById('title-token-timeline');
|
|
699
965
|
const vd = document.getElementById('title-vendor-dist');
|
|
966
|
+
const mt = document.getElementById('title-model-token-timeline');
|
|
700
967
|
if (tl) tl.textContent = label + ' 请求量趋势';
|
|
701
|
-
if (tt) tt.textContent = label + ' Token
|
|
968
|
+
if (tt) tt.textContent = label + ' Token 量趋势(按供应商)';
|
|
702
969
|
if (vd) vd.textContent = '供应商请求分布(' + label + ')';
|
|
970
|
+
if (mt) mt.textContent = label + ' Token 用量(按 Vendor / 模型)';
|
|
703
971
|
}
|
|
704
972
|
|
|
705
973
|
// ── 主刷新逻辑 ────────────────────────────────────────────
|
|
@@ -711,12 +979,11 @@ async function refresh() {
|
|
|
711
979
|
try {
|
|
712
980
|
const days = currentDays > 0 ? currentDays : 7;
|
|
713
981
|
const [summary, timeline, status] = await Promise.all([
|
|
714
|
-
fetchJSON('/api/dashboard/summary'),
|
|
982
|
+
fetchJSON('/api/dashboard/summary?days=' + days),
|
|
715
983
|
fetchJSON('/api/dashboard/timeline?days=' + days),
|
|
716
984
|
fetchJSON('/api/status'),
|
|
717
985
|
]);
|
|
718
986
|
|
|
719
|
-
// 版本号
|
|
720
987
|
if (summary.version) {
|
|
721
988
|
document.getElementById('version-badge').textContent = 'v' + summary.version;
|
|
722
989
|
}
|
|
@@ -729,8 +996,7 @@ async function refresh() {
|
|
|
729
996
|
buildTimeline(rows);
|
|
730
997
|
buildVendorDist(rows);
|
|
731
998
|
buildTokenTimeline(rows);
|
|
732
|
-
|
|
733
|
-
updateFtTable(summary.failover_stats || []);
|
|
999
|
+
buildModelTokenTimeline(rows);
|
|
734
1000
|
|
|
735
1001
|
document.getElementById('refresh-time').textContent = '上次刷新: ' + now();
|
|
736
1002
|
} catch (e) {
|
|
@@ -830,8 +1096,8 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
830
1096
|
return HTMLResponse(content=_DASHBOARD_HTML)
|
|
831
1097
|
|
|
832
1098
|
@app.get("/api/dashboard/summary")
|
|
833
|
-
async def dashboard_summary(request: Request) -> Response:
|
|
834
|
-
"""返回 Dashboard 汇总数据(今日 /
|
|
1099
|
+
async def dashboard_summary(request: Request, days: int = 7) -> Response:
|
|
1100
|
+
"""返回 Dashboard 汇总数据(今日 / 所选区间)."""
|
|
835
1101
|
token_logger = getattr(request.app.state, "token_logger", None)
|
|
836
1102
|
pricing_table = getattr(request.app.state, "pricing_table", None)
|
|
837
1103
|
|
|
@@ -842,15 +1108,17 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
842
1108
|
media_type="application/json",
|
|
843
1109
|
)
|
|
844
1110
|
|
|
1111
|
+
days = max(1, min(days, 90)) # 限制范围 1~90 天
|
|
1112
|
+
|
|
845
1113
|
try:
|
|
846
1114
|
# 今日(最近 1 天)
|
|
847
1115
|
today_rows = await token_logger.query_usage(period=TimePeriod.DAY, count=1)
|
|
848
|
-
#
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
#
|
|
853
|
-
failover_stats = await token_logger.query_failover_stats(days=
|
|
1116
|
+
# 所选区间
|
|
1117
|
+
range_rows = await token_logger.query_usage(
|
|
1118
|
+
period=TimePeriod.DAY, count=days
|
|
1119
|
+
)
|
|
1120
|
+
# 故障转移(所选区间)
|
|
1121
|
+
failover_stats = await token_logger.query_failover_stats(days=days)
|
|
854
1122
|
except Exception as exc:
|
|
855
1123
|
logger.error("dashboard_summary query error: %s", exc, exc_info=True)
|
|
856
1124
|
return Response(
|
|
@@ -860,18 +1128,15 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
860
1128
|
)
|
|
861
1129
|
|
|
862
1130
|
today = _sum_rows(today_rows)
|
|
863
|
-
|
|
864
|
-
month = _sum_rows(month_rows)
|
|
1131
|
+
range_stat = _sum_rows(range_rows)
|
|
865
1132
|
|
|
866
1133
|
today["cost"] = _compute_cost_str(today_rows, pricing_table)
|
|
867
|
-
|
|
868
|
-
month["cost"] = _compute_cost_str(month_rows, pricing_table)
|
|
1134
|
+
range_stat["cost"] = _compute_cost_str(range_rows, pricing_table)
|
|
869
1135
|
|
|
870
1136
|
result = {
|
|
871
1137
|
"version": __version__,
|
|
872
1138
|
"today": today,
|
|
873
|
-
"
|
|
874
|
-
"month": month,
|
|
1139
|
+
"range": range_stat,
|
|
875
1140
|
"failover_stats": failover_stats,
|
|
876
1141
|
}
|
|
877
1142
|
return Response(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3a4
|
|
4
4
|
Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao...
|
|
5
5
|
Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
|
|
6
6
|
Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
|
|
@@ -17,7 +17,7 @@ coding/proxy/compat/canonical.py,sha256=-zcuEwZ402xeH3C545RmuYRkT2HDuvFloyFydDv8
|
|
|
17
17
|
coding/proxy/compat/session_store.py,sha256=B9IFjjQJnHMg1244m__jG9gnqGWi26-JyEtwopEri6Q,5244
|
|
18
18
|
coding/proxy/config/__init__.py,sha256=hzgU5noJGecjj13UY38cC_p6jpWO3GO7okSW-A2XkJ0,127
|
|
19
19
|
coding/proxy/config/auth_schema.py,sha256=LYrJQU_fgW-6AoQdjXt4-MgPJjXjv9HrghlCcZwotnA,696
|
|
20
|
-
coding/proxy/config/config.default.yaml,sha256=
|
|
20
|
+
coding/proxy/config/config.default.yaml,sha256=NIVvRD_mzMvsuDtyRnF5SGCB1OviT73DUQIVHuz8ni4,16575
|
|
21
21
|
coding/proxy/config/loader.py,sha256=1J_RBJgjuC8RwB2mwewLMS7vy9WEhUqttZG2igeDm-w,8984
|
|
22
22
|
coding/proxy/config/resiliency.py,sha256=GnzY-LoyfFqXFM1l6xEru418v-cKlv97-HM0noGPdks,1308
|
|
23
23
|
coding/proxy/config/routing.py,sha256=aJMhfCRyoZIvemM3Q2_KV9rpWxUsFnoY0ZdCr4TwSs8,11765
|
|
@@ -31,7 +31,7 @@ coding/proxy/convert/gemini_sse_adapter.py,sha256=b7zQ9wBBn1bbG46WpLrAYOl0ExpyMT
|
|
|
31
31
|
coding/proxy/convert/gemini_to_anthropic.py,sha256=EE_rUsLmWTsF2QSYWqIYzsjyk1mKXFp2sziqj_S2W5U,3446
|
|
32
32
|
coding/proxy/convert/openai_to_anthropic.py,sha256=8rg8NZs_1up27MixrXRJAfmSiYrOkh_lkAJuytDGZow,3694
|
|
33
33
|
coding/proxy/logging/__init__.py,sha256=6zEh2CELMJ5aZvM6PNCOFlZ55De8xoC6QNyMAa9WeCA,6755
|
|
34
|
-
coding/proxy/logging/db.py,sha256=
|
|
34
|
+
coding/proxy/logging/db.py,sha256=MwpWxS9Rf4QhTj0_VuBdnqNjY3oJtEvdFq_T2AvPSCQ,19102
|
|
35
35
|
coding/proxy/logging/formatters.py,sha256=8LDqiNAxGcfn9fsHqZzdK9TKGqOGNAOls75B1byBjnM,823
|
|
36
36
|
coding/proxy/logging/stats.py,sha256=JlhJC4a2RmuCjbaseSUONScbcgn41KNR_sNsZ0OScbw,9203
|
|
37
37
|
coding/proxy/model/__init__.py,sha256=E8E59yabP-CF5GIwHE4OEGPLiJj3yTLjFWmCorkQTxc,3947
|
|
@@ -44,9 +44,9 @@ coding/proxy/model/vendor.py,sha256=Yg4AYN7tgjHaGWRGEccCs3AdO8vBc-KxIelnqRowpyo,
|
|
|
44
44
|
coding/proxy/routing/__init__.py,sha256=2Tpc6MQ5Oy_w-YlEWQVDSgcSgiHp3OwKOABIjj5kfHA,1595
|
|
45
45
|
coding/proxy/routing/circuit_breaker.py,sha256=hOyoY_RWB5dTVbipyuiWfAelnva3cZRLbXrSmSn8KTs,8096
|
|
46
46
|
coding/proxy/routing/error_classifier.py,sha256=oDxp69sCb-pdfe1hWce29A9tsN3jAbhqfl2fRECBtCo,3747
|
|
47
|
-
coding/proxy/routing/executor.py,sha256=
|
|
47
|
+
coding/proxy/routing/executor.py,sha256=mE-vfLCxxGMfW2HQSQ49aXgoTfDZPsKY914cni-PjQs,30420
|
|
48
48
|
coding/proxy/routing/model_mapper.py,sha256=b72CGRdusLAwoq7p8-8TWAv9-Zv8BozssiZC8XnNTx4,3574
|
|
49
|
-
coding/proxy/routing/quota_guard.py,sha256=
|
|
49
|
+
coding/proxy/routing/quota_guard.py,sha256=uoQSr2siv6aIBVylvXG_PsfdpY_i4QJxc-WHz4MGDa4,7791
|
|
50
50
|
coding/proxy/routing/rate_limit.py,sha256=i2cCqtbmXrP6wjRUTWXLP4DdYwVj0C4o59QdYGkPQj0,5122
|
|
51
51
|
coding/proxy/routing/retry.py,sha256=KEYVToBkBOtXdqY7He70RrtVWFqIpnOQf2VVksdLrlk,2386
|
|
52
52
|
coding/proxy/routing/router.py,sha256=x5x6cjL5BOnRXZZuzK3GCnNtaMVjw75xw2OQ-DoO9_s,5830
|
|
@@ -56,7 +56,7 @@ coding/proxy/routing/usage_parser.py,sha256=j4G0ArFduQ2D4Yeuad94DVlwdc-JvSh7SJLV
|
|
|
56
56
|
coding/proxy/routing/usage_recorder.py,sha256=pObOrX2yIITTiyojl1fJcqO0yWWpbP4KqsJvFdmlt04,6273
|
|
57
57
|
coding/proxy/server/__init__.py,sha256=KeH7mEu36v9v27m3VgeSxSeFz9sLTJrxS_EVATJ7Vks,20
|
|
58
58
|
coding/proxy/server/app.py,sha256=kRGgb772dZu8200LPnn7Nt0IU5oagcbBf_Vc5ynxxzE,5599
|
|
59
|
-
coding/proxy/server/dashboard.py,sha256=
|
|
59
|
+
coding/proxy/server/dashboard.py,sha256=V3gxYNRZz5S7-hi137VolpKNVlvyrbgZQfRjOS5phD4,44712
|
|
60
60
|
coding/proxy/server/factory.py,sha256=w8VFvxoogw9K9sO8MlT6bIP7xM7mR6sCohrZle9y_Gg,9985
|
|
61
61
|
coding/proxy/server/request_normalizer.py,sha256=XUqpZP42_DJmsoX9aMFVk7oLWDeG3UgXdmCv0A76FN4,12334
|
|
62
62
|
coding/proxy/server/responses.py,sha256=i0ugnLRNOdRYGHEWxkwsxR35ChmdMQsSaD8AjRluTn4,2167
|
|
@@ -80,8 +80,8 @@ coding/proxy/vendors/native_anthropic.py,sha256=SxtM71PDci0gqLqiwCrFnT410SnSoD7F
|
|
|
80
80
|
coding/proxy/vendors/token_manager.py,sha256=s10t4Com0jNnKGkPyJ_HpG5SjHrCEJvfArEOAaPKA_k,4189
|
|
81
81
|
coding/proxy/vendors/xiaomi.py,sha256=E-GcmJBZh7GOtDFonxZmlf0hKRhrlrXzL0IxHFRYcRo,860
|
|
82
82
|
coding/proxy/vendors/zhipu.py,sha256=3j_rqNFu1CX-B5ugtrL6Y1OeWSy9yiqsVa9Bi1ssaAA,1062
|
|
83
|
-
coding_proxy-0.2.
|
|
84
|
-
coding_proxy-0.2.
|
|
85
|
-
coding_proxy-0.2.
|
|
86
|
-
coding_proxy-0.2.
|
|
87
|
-
coding_proxy-0.2.
|
|
83
|
+
coding_proxy-0.2.3a4.dist-info/METADATA,sha256=-lTOQrW0TP9A8dyPrsSmZFds6_f0qW4zGzTPyKlqero,10819
|
|
84
|
+
coding_proxy-0.2.3a4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
85
|
+
coding_proxy-0.2.3a4.dist-info/entry_points.txt,sha256=moIVzt5ho0Wk9B47LOo2SEAbhzuDDHWi-EfM30U0XBg,54
|
|
86
|
+
coding_proxy-0.2.3a4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
87
|
+
coding_proxy-0.2.3a4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|