dsh-all-usage 1.1.2 → 1.1.3
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 +66 -0
- package/README.md +193 -19
- package/fixtures/usage-events.json +172 -0
- package/lib/aggregation.js +1002 -0
- package/lib/balance.js +112 -0
- package/lib/client.js +1 -2906
- package/lib/http.js +305 -0
- package/lib/index.js +2 -2119
- package/lib/ledger.js +464 -0
- package/lib/plugin.js +276 -0
- package/lib/pricing-runtime.js +282 -0
- package/lib/pricing.js +299 -36
- package/lib/session-sync.js +589 -0
- package/lib/usage-core.js +127 -0
- package/package.json +28 -3
- package/scripts/replay-fixture.mjs +155 -0
package/lib/client.js
CHANGED
|
@@ -1,2906 +1 @@
|
|
|
1
|
-
// dsh-all-usage 插件 Client 半(永久版,浏览器 bundle)
|
|
2
|
-
// 客户端模块工厂格式:window.__ModuleLoader__.load({ id, factory })
|
|
3
|
-
window.__ModuleLoader__.load({
|
|
4
|
-
id: "dsh-all-usage",
|
|
5
|
-
factory: (require) => {
|
|
6
|
-
var module = { exports: {} };
|
|
7
|
-
var exports = module.exports;
|
|
8
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
|
-
const React = require("react");
|
|
10
|
-
|
|
11
|
-
function pad2(n) {
|
|
12
|
-
return String(n).padStart(2, '0')
|
|
13
|
-
}
|
|
14
|
-
function fmtDate(d, utc) {
|
|
15
|
-
const year = utc ? d.getUTCFullYear() : d.getFullYear()
|
|
16
|
-
const month = utc ? d.getUTCMonth() : d.getMonth()
|
|
17
|
-
const day = utc ? d.getUTCDate() : d.getDate()
|
|
18
|
-
return year + '-' + pad2(month + 1) + '-' + pad2(day)
|
|
19
|
-
}
|
|
20
|
-
function shiftCalendarDate(d, days, utc) {
|
|
21
|
-
if (utc) return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + days))
|
|
22
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + days)
|
|
23
|
-
}
|
|
24
|
-
function isCalendarDate(value, utc) {
|
|
25
|
-
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false
|
|
26
|
-
const year = Number(value.slice(0, 4))
|
|
27
|
-
const month = Number(value.slice(5, 7))
|
|
28
|
-
const day = Number(value.slice(8, 10))
|
|
29
|
-
const date = utc ? new Date(Date.UTC(year, month - 1, day)) : new Date(year, month - 1, day)
|
|
30
|
-
return Number.isFinite(date.getTime()) && fmtDate(date, utc) === value
|
|
31
|
-
}
|
|
32
|
-
function normalizeCustomRange(range, utc) {
|
|
33
|
-
if (range === null || typeof range !== 'object') return null
|
|
34
|
-
const start = range.start
|
|
35
|
-
const end = range.end
|
|
36
|
-
if (!isCalendarDate(start, utc) || !isCalendarDate(end, utc) || start > end) return null
|
|
37
|
-
return { start, end }
|
|
38
|
-
}
|
|
39
|
-
function customRangeIssue(range, minDate, maxDate, utc) {
|
|
40
|
-
if (range === null || typeof range !== 'object' || !isCalendarDate(range.start, utc) || !isCalendarDate(range.end, utc)) return 'invalid'
|
|
41
|
-
if (range.start > range.end) return 'order'
|
|
42
|
-
if (range.start < minDate || range.end > maxDate) return 'bounds'
|
|
43
|
-
return ''
|
|
44
|
-
}
|
|
45
|
-
function availableDateBounds(days, maxDate) {
|
|
46
|
-
let min = maxDate
|
|
47
|
-
if (Array.isArray(days)) {
|
|
48
|
-
for (const day of days) {
|
|
49
|
-
if (day && isCalendarDate(day.date, true) && day.date <= maxDate && day.date < min) min = day.date
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return { min, max: maxDate }
|
|
53
|
-
}
|
|
54
|
-
function createRequestGate() {
|
|
55
|
-
let latest = 0
|
|
56
|
-
return {
|
|
57
|
-
next() { latest += 1; return latest },
|
|
58
|
-
isCurrent(seq) { return seq === latest },
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function snapshotVersion(data) {
|
|
62
|
-
if (data === null || typeof data !== 'object') return null
|
|
63
|
-
const instanceId = typeof data.instanceId === 'string' ? data.instanceId : ''
|
|
64
|
-
const revision = typeof data.revision === 'number' && Number.isFinite(data.revision) ? data.revision : null
|
|
65
|
-
return instanceId === '' || revision === null ? null : instanceId + ':' + revision
|
|
66
|
-
}
|
|
67
|
-
function statusRequiresFullSnapshot(status, snapshot) {
|
|
68
|
-
if (status === null || typeof status !== 'object' || snapshot === null || typeof snapshot !== 'object') return true
|
|
69
|
-
const statusVersion = snapshotVersion(status)
|
|
70
|
-
const snapshotVersionValue = snapshotVersion(snapshot)
|
|
71
|
-
if (statusVersion === null || snapshotVersionValue === null || statusVersion !== snapshotVersionValue) return true
|
|
72
|
-
const statusScan = status.scan
|
|
73
|
-
const snapshotScan = snapshot.scan
|
|
74
|
-
return !!(statusScan && snapshotScan && !!statusScan.done !== !!snapshotScan.done)
|
|
75
|
-
}
|
|
76
|
-
function retryDelayFor(failures) {
|
|
77
|
-
const count = Math.max(1, Math.min(4, typeof failures === 'number' && Number.isFinite(failures) ? failures : 1))
|
|
78
|
-
return 5000 * Math.pow(2, count - 1)
|
|
79
|
-
}
|
|
80
|
-
function rangeFilenamePart(range, customRange, utc) {
|
|
81
|
-
if (range !== 'custom') return range
|
|
82
|
-
const normalized = normalizeCustomRange(customRange, utc)
|
|
83
|
-
return normalized === null ? 'custom' : 'custom-' + normalized.start + '-to-' + normalized.end
|
|
84
|
-
}
|
|
85
|
-
function trim1(v) {
|
|
86
|
-
return String(Math.round(v * 10) / 10)
|
|
87
|
-
}
|
|
88
|
-
function fmtCompact(n) {
|
|
89
|
-
if (typeof n !== 'number' || !Number.isFinite(n)) return '0'
|
|
90
|
-
if (n < 1000) return String(n)
|
|
91
|
-
if (n < 1000000) return trim1(n / 1000) + 'k'
|
|
92
|
-
if (n < 1000000000) return trim1(n / 1000000) + 'M'
|
|
93
|
-
return trim1(n / 1000000000) + 'B'
|
|
94
|
-
}
|
|
95
|
-
function fmtCount(n, language) {
|
|
96
|
-
if (typeof n !== 'number' || !Number.isFinite(n)) return '0'
|
|
97
|
-
return Math.round(n).toLocaleString(language === 'en' ? 'en-US' : 'zh-CN')
|
|
98
|
-
}
|
|
99
|
-
function rateOf(input, cacheRead) {
|
|
100
|
-
const denom = input + cacheRead
|
|
101
|
-
if (denom <= 0) return 0
|
|
102
|
-
return (cacheRead / denom) * 100
|
|
103
|
-
}
|
|
104
|
-
function LineIcon(props) {
|
|
105
|
-
const size = props.size || 16
|
|
106
|
-
const base = { fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' }
|
|
107
|
-
const paths = {
|
|
108
|
-
edit: [React.createElement('path', { key: 'a', d: 'M12.2 3.4l2.4 2.4M4 16l2.8-.6L15 7.2a1.7 1.7 0 0 0-2.4-2.4L4.4 13z', ...base })],
|
|
109
|
-
export: [React.createElement('path', { key: 'a', d: 'M12 3v11M8 7l4-4 4 4M5 13v5h14v-5', ...base })],
|
|
110
|
-
refresh: [React.createElement('path', { key: 'a', d: 'M19 9a7 7 0 1 0 1.1 5.2M19 4v5h-5', ...base })],
|
|
111
|
-
close: [React.createElement('path', { key: 'a', d: 'M6 6l12 12M18 6L6 18', ...base })],
|
|
112
|
-
chart: [React.createElement('path', { key: 'a', d: 'M4 19V5M4 19h16M7 15l3-4 3 2 5-7', ...base })],
|
|
113
|
-
list: [React.createElement('path', { key: 'a', d: 'M6 6h12M6 12h12M6 18h12', ...base }), React.createElement('circle', { key: 'b', cx: 3.5, cy: 6, r: .7, fill: 'currentColor' }), React.createElement('circle', { key: 'c', cx: 3.5, cy: 12, r: .7, fill: 'currentColor' }), React.createElement('circle', { key: 'd', cx: 3.5, cy: 18, r: .7, fill: 'currentColor' })],
|
|
114
|
-
cache: [React.createElement('path', { key: 'a', d: 'M12 4l7 4-7 4-7-4 7-4zM5 12l7 4 7-4M5 16l7 4 7-4', ...base })],
|
|
115
|
-
wallet: [React.createElement('path', { key: 'a', d: 'M4 7.5A2.5 2.5 0 0 1 6.5 5H18v14H6.5A2.5 2.5 0 0 1 4 16.5zM4 8h14M14 13h.01', ...base })],
|
|
116
|
-
clock: [React.createElement('path', { key: 'a', d: 'M12 6v6l4 2M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z', ...base })],
|
|
117
|
-
folder: [React.createElement('path', { key: 'a', d: 'M3.5 7.5h6l2 2h9v8.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z', ...base })],
|
|
118
|
-
language: [React.createElement('circle', { key: 'a', cx: 12, cy: 12, r: 8, ...base }), React.createElement('path', { key: 'b', d: 'M4 12h16M12 4c2.1 2.2 3.2 4.9 3.2 8S14.1 17.8 12 20M12 4C9.9 6.2 8.8 8.9 8.8 12s1.1 5.8 3.2 8', ...base })],
|
|
119
|
-
chevron: [React.createElement('path', { key: 'a', d: 'M7 10l5 5 5-5', ...base })],
|
|
120
|
-
check: [React.createElement('path', { key: 'a', d: 'M5 12.5l4.2 4.1L19 7.3', ...base })],
|
|
121
|
-
plus: [React.createElement('path', { key: 'a', d: 'M12 5v14M5 12h14', ...base })],
|
|
122
|
-
calendar: [React.createElement('path', { key: 'a', d: 'M6 4v3M18 4v3M4 9h16M5 6h14a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z', ...base })],
|
|
123
|
-
}
|
|
124
|
-
return React.createElement('svg', { className: 'uh-line-icon ' + (props.className || ''), width: size, height: size, viewBox: '0 0 24 24', 'aria-hidden': true }, paths[props.name] || paths.chart)
|
|
125
|
-
}
|
|
126
|
-
function chineseMagnitude(n, language) {
|
|
127
|
-
if (language === 'en' || typeof n !== 'number' || !Number.isFinite(n) || n < 10000) return ''
|
|
128
|
-
const value = n >= 100000000 ? n / 100000000 : n / 10000
|
|
129
|
-
const rounded = Math.round(value * 1000) / 1000
|
|
130
|
-
return String(rounded) + (n >= 100000000 ? '亿' : '万')
|
|
131
|
-
}
|
|
132
|
-
function valueWithMagnitude(value, raw, language) {
|
|
133
|
-
const magnitude = chineseMagnitude(raw, language)
|
|
134
|
-
return React.createElement(React.Fragment, null, value, magnitude ? React.createElement('span', { className: 'uh-unit' }, magnitude) : null)
|
|
135
|
-
}
|
|
136
|
-
function money(currency, n, language) {
|
|
137
|
-
if (n === null || n === undefined) return '—'
|
|
138
|
-
const sym = currency === 'CNY' ? '¥' : currency === 'USD' ? '$' : currency + ' '
|
|
139
|
-
return sym + n.toLocaleString(language === 'en' ? 'en-US' : 'zh-CN', { minimumFractionDigits: 4, maximumFractionDigits: 4 })
|
|
140
|
-
}
|
|
141
|
-
function decimalParts(value) {
|
|
142
|
-
const raw = typeof value === 'number' ? String(value) : typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
143
|
-
const match = raw.match(/^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/)
|
|
144
|
-
if (!match) return { digits: 0n, scale: 0 }
|
|
145
|
-
let digits = (match[1] || '') + (match[2] || '')
|
|
146
|
-
let scale = (match[2] || '').length - (match[3] ? Number(match[3]) : 0)
|
|
147
|
-
if (scale < 0) { digits += '0'.repeat(-scale); scale = 0 }
|
|
148
|
-
if (scale > digits.length) digits = '0'.repeat(scale - digits.length + 1) + digits
|
|
149
|
-
while (scale > 0 && digits.length > 1 && digits.endsWith('0')) { digits = digits.slice(0, -1); scale -= 1 }
|
|
150
|
-
return { digits: BigInt(digits.replace(/^0+(?=\d)/, '') || '0'), scale }
|
|
151
|
-
}
|
|
152
|
-
function decimalText(value) {
|
|
153
|
-
const parts = decimalParts(value)
|
|
154
|
-
if (parts.digits === 0n) return '0'
|
|
155
|
-
const raw = parts.digits.toString()
|
|
156
|
-
if (parts.scale === 0) return raw
|
|
157
|
-
const padded = raw.padStart(parts.scale + 1, '0')
|
|
158
|
-
const split = padded.length - parts.scale
|
|
159
|
-
return padded.slice(0, split) + '.' + padded.slice(split)
|
|
160
|
-
}
|
|
161
|
-
function decimalAdd(left, right) {
|
|
162
|
-
const a = decimalParts(left); const b = decimalParts(right)
|
|
163
|
-
const scale = Math.max(a.scale, b.scale)
|
|
164
|
-
const value = a.digits * 10n ** BigInt(scale - a.scale) + b.digits * 10n ** BigInt(scale - b.scale)
|
|
165
|
-
return decimalText(value.toString() + (scale > 0 ? 'e-' + scale : ''))
|
|
166
|
-
}
|
|
167
|
-
function emptyCostAggregate() {
|
|
168
|
-
return { currency: 'USD', input: '0', output: '0', cacheRead: '0', cacheWrite: '0', baseTotal: '0', total: '0', pricedCalls: 0, unpricedCalls: 0, ambiguousCalls: 0, unsupportedCalls: 0 }
|
|
169
|
-
}
|
|
170
|
-
function costAggregate(row) {
|
|
171
|
-
const source = row && row.cost && typeof row.cost === 'object' ? row.cost : (row && typeof row === 'object' ? row : {})
|
|
172
|
-
const value = emptyCostAggregate()
|
|
173
|
-
value.currency = typeof source.currency === 'string' && source.currency !== '' ? source.currency : 'USD'
|
|
174
|
-
if (source.breakdown && typeof source.breakdown === 'object') {
|
|
175
|
-
if (source.status === 'priced') {
|
|
176
|
-
value.input = decimalText(source.breakdown.input)
|
|
177
|
-
value.output = decimalText(source.breakdown.output)
|
|
178
|
-
value.cacheRead = decimalText(source.breakdown.cacheRead)
|
|
179
|
-
value.cacheWrite = decimalText(source.breakdown.cacheWrite)
|
|
180
|
-
value.baseTotal = decimalText(source.baseTotal)
|
|
181
|
-
value.total = decimalText(source.total)
|
|
182
|
-
value.pricedCalls = 1
|
|
183
|
-
} else if (source.status === 'ambiguous') value.ambiguousCalls = 1
|
|
184
|
-
else if (source.status === 'unsupported') value.unsupportedCalls = 1
|
|
185
|
-
else value.unpricedCalls = 1
|
|
186
|
-
return value
|
|
187
|
-
}
|
|
188
|
-
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'baseTotal', 'total']) value[key] = decimalText(source[key])
|
|
189
|
-
for (const key of ['pricedCalls', 'unpricedCalls', 'ambiguousCalls', 'unsupportedCalls']) value[key] = Number.isFinite(source[key]) ? source[key] : 0
|
|
190
|
-
return value
|
|
191
|
-
}
|
|
192
|
-
function addCostAggregate(target, row) {
|
|
193
|
-
const value = costAggregate(row)
|
|
194
|
-
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'baseTotal', 'total']) target[key] = decimalAdd(target[key], value[key])
|
|
195
|
-
target.pricedCalls += value.pricedCalls
|
|
196
|
-
target.unpricedCalls += value.unpricedCalls
|
|
197
|
-
target.ambiguousCalls += value.ambiguousCalls
|
|
198
|
-
target.unsupportedCalls += value.unsupportedCalls
|
|
199
|
-
return target
|
|
200
|
-
}
|
|
201
|
-
function costDisplay(row, language) {
|
|
202
|
-
const value = costAggregate(row)
|
|
203
|
-
if (value.pricedCalls <= 0) return '—'
|
|
204
|
-
const numeric = Number(value.total)
|
|
205
|
-
return Number.isFinite(numeric) ? money(value.currency, numeric, language) : value.currency + ' ' + value.total
|
|
206
|
-
}
|
|
207
|
-
function costCoverageLabel(row, language) {
|
|
208
|
-
const value = costAggregate(row)
|
|
209
|
-
if (value.pricedCalls > 0 && value.unpricedCalls === 0 && value.ambiguousCalls === 0 && value.unsupportedCalls === 0) return language === 'en' ? value.pricedCalls + ' priced' : value.pricedCalls + ' 次已计价'
|
|
210
|
-
const pending = value.unpricedCalls + value.ambiguousCalls + value.unsupportedCalls
|
|
211
|
-
return pending > 0 ? (language === 'en' ? pending + ' unpriced' : pending + ' 次未计价') : (language === 'en' ? 'No pricing' : '暂无价格')
|
|
212
|
-
}
|
|
213
|
-
function pricingDraftOf(pricing) {
|
|
214
|
-
const config = pricing && pricing.config && typeof pricing.config === 'object' ? pricing.config : {}
|
|
215
|
-
const sync = config.sync && typeof config.sync === 'object' ? config.sync : {}
|
|
216
|
-
return {
|
|
217
|
-
sync: { autoEnabled: sync.autoEnabled === true, intervalMs: Number.isFinite(sync.intervalMs) ? sync.intervalMs : 21600000 },
|
|
218
|
-
providerAliases: config.providerAliases && typeof config.providerAliases === 'object' ? Object.assign({}, config.providerAliases) : {},
|
|
219
|
-
mappings: Array.isArray(config.mappings) ? config.mappings.map((mapping) => Object.assign({}, mapping)) : [],
|
|
220
|
-
overrides: Array.isArray(config.overrides) ? config.overrides.map((entry) => Object.assign({}, entry)) : [],
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
function pricingStatusLabel(status, language) {
|
|
224
|
-
const labels = { priced: ['已计价', 'priced'], unpriced: ['未计价', 'unpriced'], ambiguous: ['待确认', 'ambiguous'], unsupported: ['不支持', 'unsupported'] }
|
|
225
|
-
const pair = labels[status] || labels.unpriced
|
|
226
|
-
return language === 'en' ? pair[1] : pair[0]
|
|
227
|
-
}
|
|
228
|
-
function pricingModelKey(value) {
|
|
229
|
-
return String(value || '').trim().toLowerCase().replace(/^.*\//, '').split(':')[0]
|
|
230
|
-
}
|
|
231
|
-
function modelViewKey(value) {
|
|
232
|
-
const text = String(value || '').trim()
|
|
233
|
-
return text.includes(' / ') ? text : pricingModelKey(text)
|
|
234
|
-
}
|
|
235
|
-
function humanDate(date, language) {
|
|
236
|
-
const parts = date.split('-')
|
|
237
|
-
const utc = language === 'en'
|
|
238
|
-
const d = utc ? new Date(Date.UTC(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]))) : new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]))
|
|
239
|
-
const monthIndex = utc ? d.getUTCMonth() : d.getMonth()
|
|
240
|
-
const weekIndex = utc ? d.getUTCDay() : d.getDay()
|
|
241
|
-
if (language === 'en') {
|
|
242
|
-
const month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][monthIndex]
|
|
243
|
-
const week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][weekIndex]
|
|
244
|
-
return month + ' ' + Number(parts[2]) + ', ' + parts[0] + ' (' + week + ', UTC)'
|
|
245
|
-
}
|
|
246
|
-
const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][weekIndex]
|
|
247
|
-
return parts[0] + '年' + Number(parts[1]) + '月' + Number(parts[2]) + '日 ' + week
|
|
248
|
-
}
|
|
249
|
-
function monthLabel(year, month, language) {
|
|
250
|
-
if (language === 'en') {
|
|
251
|
-
const label = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month]
|
|
252
|
-
return month === 0 ? year + ' ' + label : label
|
|
253
|
-
}
|
|
254
|
-
return month === 0 ? year + '年1月' : (month + 1) + '月'
|
|
255
|
-
}
|
|
256
|
-
function levelOf(count) {
|
|
257
|
-
if (count >= 10) return 4
|
|
258
|
-
if (count >= 6) return 3
|
|
259
|
-
if (count >= 3) return 2
|
|
260
|
-
if (count >= 1) return 1
|
|
261
|
-
return 0
|
|
262
|
-
}
|
|
263
|
-
const GH_GREEN = '#2ea043'
|
|
264
|
-
const LEVEL_PCT = [20, 45, 70, 96]
|
|
265
|
-
function cellBg(level) {
|
|
266
|
-
if (level <= 0) return 'var(--dsw-alias-bg-layer-2)'
|
|
267
|
-
return 'color-mix(in srgb, ' + GH_GREEN + ' ' + LEVEL_PCT[level - 1] + '%, var(--dsw-alias-bg-layer-2))'
|
|
268
|
-
}
|
|
269
|
-
function wsColor(i) {
|
|
270
|
-
return 'hsl(' + ((i * 137) % 360) + ', 70%, 55%)'
|
|
271
|
-
}
|
|
272
|
-
function rangeAgg(stats, range, utc, customRange) {
|
|
273
|
-
const empty = { totals: { turns: 0, calls: 0, sessions: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }, perWs: [], perModel: [] }
|
|
274
|
-
if (stats === null) return empty
|
|
275
|
-
if (range === 'all') return { totals: stats.totals, perWs: stats.perWorkspace, perModel: stats.perModel || [] }
|
|
276
|
-
const days = utc && Array.isArray(stats.byDayUtc) ? stats.byDayUtc : (Array.isArray(stats.byDay) ? stats.byDay : [])
|
|
277
|
-
let start
|
|
278
|
-
let end = null
|
|
279
|
-
if (range === 'custom') {
|
|
280
|
-
const normalized = normalizeCustomRange(customRange, utc)
|
|
281
|
-
if (normalized === null) return empty
|
|
282
|
-
start = normalized.start
|
|
283
|
-
end = normalized.end
|
|
284
|
-
} else if (range === 'today') {
|
|
285
|
-
start = fmtDate(new Date(), utc)
|
|
286
|
-
} else if (range === '30d') {
|
|
287
|
-
start = fmtDate(shiftCalendarDate(new Date(), -29, utc), utc)
|
|
288
|
-
} else {
|
|
289
|
-
start = fmtDate(shiftCalendarDate(new Date(), -89, utc), utc)
|
|
290
|
-
}
|
|
291
|
-
const t = { turns: 0, calls: 0, sessions: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }
|
|
292
|
-
const per = new Map()
|
|
293
|
-
const models = new Map()
|
|
294
|
-
const sessionsInRange = new Set()
|
|
295
|
-
for (const day of days) {
|
|
296
|
-
if (day.date < start || (end !== null && day.date > end)) continue
|
|
297
|
-
const daySessionIds = Array.isArray(day.sessionIds) ? day.sessionIds : []
|
|
298
|
-
for (const sid of daySessionIds) sessionsInRange.add(sid)
|
|
299
|
-
t.turns += day.turns
|
|
300
|
-
t.input += day.tokens.input
|
|
301
|
-
t.output += day.tokens.output
|
|
302
|
-
t.cacheRead += day.tokens.cacheRead
|
|
303
|
-
t.cacheWrite += day.tokens.cacheWrite
|
|
304
|
-
t.reasoning += day.tokens.reasoning
|
|
305
|
-
addCostAggregate(t.cost, day.cost)
|
|
306
|
-
for (const w of day.byWorkspace) {
|
|
307
|
-
let p = per.get(w.workspaceId)
|
|
308
|
-
if (p === undefined) { p = { workspaceId: w.workspaceId, turns: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }; per.set(w.workspaceId, p) }
|
|
309
|
-
p.input += w.input
|
|
310
|
-
p.output += w.output
|
|
311
|
-
p.cacheRead += w.cacheRead
|
|
312
|
-
p.cacheWrite += w.cacheWrite
|
|
313
|
-
p.reasoning += w.reasoning
|
|
314
|
-
addCostAggregate(p.cost, w.cost)
|
|
315
|
-
}
|
|
316
|
-
for (const w of day.perWorkspace) {
|
|
317
|
-
let p = per.get(w.workspaceId)
|
|
318
|
-
if (p === undefined) { p = { workspaceId: w.workspaceId, turns: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }; per.set(w.workspaceId, p) }
|
|
319
|
-
p.turns += w.turns
|
|
320
|
-
}
|
|
321
|
-
for (const m of (day.byModel || [])) {
|
|
322
|
-
const key = m.identityKey || m.model
|
|
323
|
-
let p = models.get(key)
|
|
324
|
-
if (p === undefined) { p = { ...m, calls: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }; models.set(key, p) }
|
|
325
|
-
p.calls += m.calls; p.input += m.input; p.output += m.output; p.cacheRead += m.cacheRead; p.cacheWrite += m.cacheWrite; p.reasoning += m.reasoning
|
|
326
|
-
t.calls += Number.isFinite(m.calls) ? m.calls : 0
|
|
327
|
-
addCostAggregate(p.cost, m.cost)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
t.sessions = sessionsInRange.size
|
|
331
|
-
return { totals: t, perWs: Array.from(per.values()), perModel: Array.from(models.values()) }
|
|
332
|
-
}
|
|
333
|
-
function resolveRangeBounds(stats, range, utc, customRange) {
|
|
334
|
-
const days = utc && Array.isArray(stats && stats.byDayUtc) ? stats.byDayUtc : (Array.isArray(stats && stats.byDay) ? stats.byDay : [])
|
|
335
|
-
const latest = fmtDate(new Date(), utc)
|
|
336
|
-
if (range === 'custom') {
|
|
337
|
-
const normalized = normalizeCustomRange(customRange, utc)
|
|
338
|
-
return normalized === null ? null : { start: normalized.start, end: normalized.end }
|
|
339
|
-
}
|
|
340
|
-
if (range === 'today') return { start: latest, end: latest }
|
|
341
|
-
if (range === '30d') return { start: fmtDate(shiftCalendarDate(new Date(), -29, utc), utc), end: latest }
|
|
342
|
-
if (range === '90d') return { start: fmtDate(shiftCalendarDate(new Date(), -89, utc), utc), end: latest }
|
|
343
|
-
const bounds = availableDateBounds(days, latest)
|
|
344
|
-
return { start: bounds.min, end: bounds.max }
|
|
345
|
-
}
|
|
346
|
-
function makeUsageScope(stats, range, utc, customRange, workspaceId, provider, modelKey) {
|
|
347
|
-
const bounds = resolveRangeBounds(stats, range, utc, customRange)
|
|
348
|
-
if (bounds === null) return null
|
|
349
|
-
return { start: bounds.start, end: bounds.end, utc: utc === true, workspaceId: workspaceId || null, provider: provider || null, modelKey: modelKey || null }
|
|
350
|
-
}
|
|
351
|
-
function usageScopeKey(scope) {
|
|
352
|
-
return scope === null ? '' : JSON.stringify({ start: scope.start, end: scope.end, utc: scope.utc === true, workspaceId: scope.workspaceId || null, provider: scope.provider || null, modelKey: scope.modelKey || null })
|
|
353
|
-
}
|
|
354
|
-
function rowTokens(row) {
|
|
355
|
-
const tokens = row && row.tokens && typeof row.tokens === 'object' ? row.tokens : row || {}
|
|
356
|
-
return { input: Number.isFinite(tokens.input) ? tokens.input : 0, output: Number.isFinite(tokens.output) ? tokens.output : 0, cacheRead: Number.isFinite(tokens.cacheRead) ? tokens.cacheRead : 0, cacheWrite: Number.isFinite(tokens.cacheWrite) ? tokens.cacheWrite : 0, reasoning: Number.isFinite(tokens.reasoning) ? tokens.reasoning : 0 }
|
|
357
|
-
}
|
|
358
|
-
function buildTrendRows(rows, bounds, utc) {
|
|
359
|
-
if (bounds === null || typeof bounds !== 'object') return []
|
|
360
|
-
const source = new Map((Array.isArray(rows) ? rows : []).filter((row) => row && typeof row.date === 'string').map((row) => [row.date, row]))
|
|
361
|
-
const startParts = bounds.start.split('-').map(Number)
|
|
362
|
-
const endParts = bounds.end.split('-').map(Number)
|
|
363
|
-
const cursor = utc ? new Date(Date.UTC(startParts[0], startParts[1] - 1, startParts[2])) : new Date(startParts[0], startParts[1] - 1, startParts[2])
|
|
364
|
-
const end = utc ? new Date(Date.UTC(endParts[0], endParts[1] - 1, endParts[2])) : new Date(endParts[0], endParts[1] - 1, endParts[2])
|
|
365
|
-
const result = []
|
|
366
|
-
while (cursor.getTime() <= end.getTime()) {
|
|
367
|
-
const date = fmtDate(cursor, utc)
|
|
368
|
-
const row = source.get(date)
|
|
369
|
-
const tokens = rowTokens(row)
|
|
370
|
-
result.push({ date, turns: row && Number.isFinite(row.turns) ? row.turns : 0, calls: row && Number.isFinite(row.calls) ? row.calls : 0, sessions: row && Number.isFinite(row.sessions) ? row.sessions : 0, tokens, cost: costAggregate(row), total: tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning })
|
|
371
|
-
if (utc) cursor.setUTCDate(cursor.getUTCDate() + 1)
|
|
372
|
-
else cursor.setDate(cursor.getDate() + 1)
|
|
373
|
-
}
|
|
374
|
-
return result
|
|
375
|
-
}
|
|
376
|
-
function buildTrendHourlyRows(rows, utc) {
|
|
377
|
-
const result = []
|
|
378
|
-
for (const row of (Array.isArray(rows) ? rows : [])) {
|
|
379
|
-
if (row === null || typeof row !== 'object') continue
|
|
380
|
-
const time = Number.isFinite(row.time) ? row.time : (typeof row.date === 'string' ? Date.parse(row.date) : NaN)
|
|
381
|
-
if (!Number.isFinite(time)) continue
|
|
382
|
-
const tokens = rowTokens(row)
|
|
383
|
-
result.push({ date: fmtDate(new Date(time), utc), time, turns: Number.isFinite(row.turns) ? row.turns : 0, calls: Number.isFinite(row.calls) ? row.calls : 0, sessions: Number.isFinite(row.sessions) ? row.sessions : 0, tokens, cost: costAggregate(row), total: tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning })
|
|
384
|
-
}
|
|
385
|
-
return result.sort((a, b) => a.time - b.time)
|
|
386
|
-
}
|
|
387
|
-
function trendHourLabel(time, language, detailed) {
|
|
388
|
-
const options = detailed
|
|
389
|
-
? { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }
|
|
390
|
-
: { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }
|
|
391
|
-
if (language === 'en') options.timeZone = 'UTC'
|
|
392
|
-
return new Date(time).toLocaleString(language === 'en' ? 'en-US' : 'zh-CN', options)
|
|
393
|
-
}
|
|
394
|
-
function trendRowLabel(row, language, detailed) {
|
|
395
|
-
if (row && Number.isFinite(row.time)) return trendHourLabel(row.time, language, detailed)
|
|
396
|
-
if (!row || typeof row.date !== 'string') return ''
|
|
397
|
-
return detailed ? humanDate(row.date, language) : row.date.slice(5)
|
|
398
|
-
}
|
|
399
|
-
function trendRowKey(row, index) {
|
|
400
|
-
return row && Number.isFinite(row.time) ? String(row.time) : (row && typeof row.date === 'string' ? row.date : String(index))
|
|
401
|
-
}
|
|
402
|
-
function trendRowDate(row) {
|
|
403
|
-
return row && typeof row.date === 'string' ? row.date : ''
|
|
404
|
-
}
|
|
405
|
-
function buildTrendGeometry(rows, visible, width = 900, height = 250) {
|
|
406
|
-
const keys = Array.isArray(visible) && visible.length > 0 ? visible : ['total']
|
|
407
|
-
const padding = { left: 46, right: 14, top: 14, bottom: 30 }
|
|
408
|
-
const innerWidth = Math.max(1, width - padding.left - padding.right)
|
|
409
|
-
const innerHeight = Math.max(1, height - padding.top - padding.bottom)
|
|
410
|
-
const values = (Array.isArray(rows) ? rows : []).flatMap((row) => keys.map((key) => key === 'total' ? row.total : row.tokens[key] || 0))
|
|
411
|
-
const max = Math.max(1, ...values)
|
|
412
|
-
const points = {}
|
|
413
|
-
for (const key of keys) points[key] = (Array.isArray(rows) ? rows : []).map((row, index) => ({ x: padding.left + (rows.length > 1 ? index * innerWidth / (rows.length - 1) : innerWidth / 2), y: padding.top + innerHeight - ((key === 'total' ? row.total : row.tokens[key] || 0) / max) * innerHeight, value: key === 'total' ? row.total : row.tokens[key] || 0 }))
|
|
414
|
-
return { width, height, padding, max, points }
|
|
415
|
-
}
|
|
416
|
-
function modelParts(row, unknownProvider, unknownModel) {
|
|
417
|
-
const structuredModel = typeof row.actualModel === 'string' && row.actualModel !== '' ? row.actualModel : (typeof row.requestedModel === 'string' && row.requestedModel !== '' ? row.requestedModel : '')
|
|
418
|
-
const displayModel = typeof row.model === 'string' && row.model !== '' ? row.model : unknownModel
|
|
419
|
-
const separator = displayModel.indexOf(' / ')
|
|
420
|
-
const rowProvider = typeof row.provider === 'string' && row.provider !== '' ? row.provider : ''
|
|
421
|
-
const provider = rowProvider || (separator > 0 ? displayModel.slice(0, separator) : unknownProvider)
|
|
422
|
-
const providerPrefix = provider !== unknownProvider ? provider + ' / ' : ''
|
|
423
|
-
const fallbackModel = structuredModel !== '' ? displayModel : providerPrefix !== '' && displayModel.startsWith(providerPrefix) ? displayModel.slice(providerPrefix.length) : separator > 0 ? displayModel.slice(separator + 3) : displayModel
|
|
424
|
-
const model = modelViewKey(structuredModel || fallbackModel) || unknownModel
|
|
425
|
-
return { provider, model }
|
|
426
|
-
}
|
|
427
|
-
function modelOptionLabel(row, unknownProvider, unknownModel) {
|
|
428
|
-
const parts = modelParts(row, unknownProvider, unknownModel)
|
|
429
|
-
const base = parts.provider + ' / ' + parts.model
|
|
430
|
-
return row && row.requestedModel && row.actualModel && row.requestedModel !== row.actualModel ? base + ' ← ' + row.requestedModel : base
|
|
431
|
-
}
|
|
432
|
-
function aggregateModelRows(rows, view, unknownProvider, unknownModel) {
|
|
433
|
-
if (view === 'route') return rows.slice()
|
|
434
|
-
const grouped = new Map()
|
|
435
|
-
for (const row of rows) {
|
|
436
|
-
const parts = modelParts(row, unknownProvider, unknownModel)
|
|
437
|
-
const key = view === 'model' ? parts.model : parts.provider
|
|
438
|
-
let item = grouped.get(key)
|
|
439
|
-
if (item === undefined) { item = { model: key, provider: view === 'provider' ? key : parts.provider, calls: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, cost: emptyCostAggregate() }; grouped.set(key, item) }
|
|
440
|
-
item.calls += row.calls; item.input += row.input; item.output += row.output; item.cacheRead += row.cacheRead; item.cacheWrite += row.cacheWrite; item.reasoning += row.reasoning
|
|
441
|
-
addCostAggregate(item.cost, row.cost)
|
|
442
|
-
}
|
|
443
|
-
return Array.from(grouped.values())
|
|
444
|
-
}
|
|
445
|
-
function streaks(dayMap, utc) {
|
|
446
|
-
const today = new Date()
|
|
447
|
-
let streak = 0
|
|
448
|
-
for (let i = 0; i < 371; i++) {
|
|
449
|
-
const d = shiftCalendarDate(today, -i, utc)
|
|
450
|
-
const day = dayMap.get(fmtDate(d, utc))
|
|
451
|
-
const active = day !== undefined && day.turns > 0
|
|
452
|
-
if (active) streak += 1
|
|
453
|
-
else if (i > 0) break
|
|
454
|
-
}
|
|
455
|
-
let best = 0
|
|
456
|
-
let run = 0
|
|
457
|
-
for (let i = 0; i < 371; i++) {
|
|
458
|
-
const d = shiftCalendarDate(today, -i, utc)
|
|
459
|
-
const day = dayMap.get(fmtDate(d, utc))
|
|
460
|
-
if (day !== undefined && day.turns > 0) {
|
|
461
|
-
run += 1
|
|
462
|
-
if (run > best) best = run
|
|
463
|
-
} else {
|
|
464
|
-
run = 0
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return { streak, best }
|
|
468
|
-
}
|
|
469
|
-
// 数字滚动动画:首次从 0 滚动到目标值,之后直接同步目标值
|
|
470
|
-
function useCountUp(target, timer) {
|
|
471
|
-
const [state, setState] = React.useState({ value: 0, done: false })
|
|
472
|
-
React.useEffect(() => {
|
|
473
|
-
if (typeof target !== 'number' || !Number.isFinite(target) || target <= 0) {
|
|
474
|
-
setState({ value: 0, done: false })
|
|
475
|
-
return undefined
|
|
476
|
-
}
|
|
477
|
-
if (state.done) {
|
|
478
|
-
setState({ value: target, done: true })
|
|
479
|
-
return undefined
|
|
480
|
-
}
|
|
481
|
-
const start = Date.now()
|
|
482
|
-
const duration = 700
|
|
483
|
-
const stop = timer.interval(() => {
|
|
484
|
-
const t = Math.min(1, (Date.now() - start) / duration)
|
|
485
|
-
const eased = 1 - Math.pow(1 - t, 3)
|
|
486
|
-
if (t >= 1) {
|
|
487
|
-
stop()
|
|
488
|
-
setState({ value: target, done: true })
|
|
489
|
-
} else {
|
|
490
|
-
setState({ value: Math.round(target * eased), done: false })
|
|
491
|
-
}
|
|
492
|
-
}, 32)
|
|
493
|
-
return stop
|
|
494
|
-
}, [target])
|
|
495
|
-
return state.value
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function smoothTrendPath(points) {
|
|
499
|
-
if (!Array.isArray(points) || points.length === 0) return ''
|
|
500
|
-
if (points.length === 1) return 'M' + points[0].x.toFixed(2) + ' ' + points[0].y.toFixed(2)
|
|
501
|
-
const slopes = []
|
|
502
|
-
for (let i = 0; i < points.length - 1; i += 1) {
|
|
503
|
-
const dx = points[i + 1].x - points[i].x
|
|
504
|
-
slopes.push(dx === 0 ? 0 : (points[i + 1].y - points[i].y) / dx)
|
|
505
|
-
}
|
|
506
|
-
const tangents = new Array(points.length).fill(0)
|
|
507
|
-
tangents[0] = slopes[0]
|
|
508
|
-
tangents[points.length - 1] = slopes[slopes.length - 1]
|
|
509
|
-
for (let i = 1; i < points.length - 1; i += 1) {
|
|
510
|
-
const before = slopes[i - 1]
|
|
511
|
-
const after = slopes[i]
|
|
512
|
-
tangents[i] = before * after <= 0 ? 0 : (before + after) / 2
|
|
513
|
-
}
|
|
514
|
-
// Fritsch-Carlson limiting keeps the smooth curve monotone between points.
|
|
515
|
-
for (let i = 0; i < slopes.length; i += 1) {
|
|
516
|
-
if (slopes[i] === 0) { tangents[i] = 0; tangents[i + 1] = 0; continue }
|
|
517
|
-
const a = tangents[i] / slopes[i]
|
|
518
|
-
const b = tangents[i + 1] / slopes[i]
|
|
519
|
-
const magnitude = a * a + b * b
|
|
520
|
-
if (magnitude > 9) {
|
|
521
|
-
const scale = 3 / Math.sqrt(magnitude)
|
|
522
|
-
tangents[i] = scale * a * slopes[i]
|
|
523
|
-
tangents[i + 1] = scale * b * slopes[i]
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
let path = 'M' + points[0].x.toFixed(2) + ' ' + points[0].y.toFixed(2)
|
|
527
|
-
for (let i = 0; i < points.length - 1; i += 1) {
|
|
528
|
-
const dx = points[i + 1].x - points[i].x
|
|
529
|
-
const c1x = points[i].x + dx / 3
|
|
530
|
-
const c1y = points[i].y + tangents[i] * dx / 3
|
|
531
|
-
const c2x = points[i + 1].x - dx / 3
|
|
532
|
-
const c2y = points[i + 1].y - tangents[i + 1] * dx / 3
|
|
533
|
-
path += ' C' + c1x.toFixed(2) + ' ' + c1y.toFixed(2) + ' ' + c2x.toFixed(2) + ' ' + c2y.toFixed(2) + ' ' + points[i + 1].x.toFixed(2) + ' ' + points[i + 1].y.toFixed(2)
|
|
534
|
-
}
|
|
535
|
-
return path
|
|
536
|
-
}
|
|
537
|
-
function trendPathLength(points) {
|
|
538
|
-
if (!Array.isArray(points) || points.length < 2) return 1
|
|
539
|
-
let length = 0
|
|
540
|
-
for (let i = 1; i < points.length; i += 1) {
|
|
541
|
-
const dx = points[i].x - points[i - 1].x
|
|
542
|
-
const dy = points[i].y - points[i - 1].y
|
|
543
|
-
length += Math.sqrt(dx * dx + dy * dy)
|
|
544
|
-
}
|
|
545
|
-
return Math.max(1, Math.ceil(length * 1.35 + 2))
|
|
546
|
-
}
|
|
547
|
-
const DONUT_COLORS = ['#0a84ff', '#30d158', '#bf5af2', '#ff9f0a', '#ff375f', '#64d2ff']
|
|
548
|
-
function tokenMagnitude(value, language) {
|
|
549
|
-
const magnitude = chineseMagnitude(value, language)
|
|
550
|
-
return magnitude !== '' ? magnitude : fmtCompact(value)
|
|
551
|
-
}
|
|
552
|
-
function tokenDisplay(value, language) {
|
|
553
|
-
return tokenMagnitude(value, language) + (language === 'en' ? ' tokens' : ' Token')
|
|
554
|
-
}
|
|
555
|
-
function buildDonutSegments(items, otherLabel, limit = 5) {
|
|
556
|
-
const topLimit = Math.max(1, Number.isInteger(limit) ? limit : 5)
|
|
557
|
-
const normalized = (Array.isArray(items) ? items : []).map((item, index) => ({
|
|
558
|
-
label: item && item.label !== undefined ? String(item.label) : '',
|
|
559
|
-
value: Number(item && item.value),
|
|
560
|
-
color: item && typeof item.color === 'string' && item.color !== '' ? item.color : DONUT_COLORS[index % DONUT_COLORS.length],
|
|
561
|
-
cost: costAggregate(item),
|
|
562
|
-
})).filter((item) => item.label !== '' && Number.isFinite(item.value) && item.value > 0).sort((a, b) => b.value - a.value)
|
|
563
|
-
const total = normalized.reduce((sum, item) => sum + item.value, 0)
|
|
564
|
-
if (total <= 0) return { total: 0, segments: [] }
|
|
565
|
-
const segments = normalized.slice(0, topLimit)
|
|
566
|
-
const remainderItems = normalized.slice(topLimit)
|
|
567
|
-
const remainder = remainderItems.reduce((sum, item) => sum + item.value, 0)
|
|
568
|
-
if (remainder > 0) {
|
|
569
|
-
const remainderCost = emptyCostAggregate()
|
|
570
|
-
for (const item of remainderItems) addCostAggregate(remainderCost, item.cost)
|
|
571
|
-
segments.push({ label: otherLabel + ' (' + (normalized.length - topLimit) + ')', value: remainder, color: '#b8c2cf', cost: remainderCost, other: true })
|
|
572
|
-
}
|
|
573
|
-
let angle = -Math.PI / 2
|
|
574
|
-
return {
|
|
575
|
-
total,
|
|
576
|
-
segments: segments.map((item, index) => {
|
|
577
|
-
const sweep = item.value / total * Math.PI * 2
|
|
578
|
-
const gap = segments.length > 1 ? Math.min(.018, sweep / 3) : 0
|
|
579
|
-
const startAngle = angle + gap
|
|
580
|
-
const endAngle = angle + sweep - gap
|
|
581
|
-
angle += sweep
|
|
582
|
-
return { ...item, index, percentage: item.value / total * 100, startAngle: endAngle <= startAngle ? angle - sweep : startAngle, endAngle: endAngle <= startAngle ? angle : endAngle }
|
|
583
|
-
}),
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
function donutArcPath(cx, cy, outerRadius, innerRadius, startAngle, endAngle) {
|
|
587
|
-
const sweep = Math.max(0, endAngle - startAngle)
|
|
588
|
-
const point = (radius, angle) => ({ x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) })
|
|
589
|
-
const outerStart = point(outerRadius, startAngle)
|
|
590
|
-
const innerStart = point(innerRadius, startAngle)
|
|
591
|
-
if (sweep >= Math.PI * 2 - .0001) {
|
|
592
|
-
const outerMid = point(outerRadius, startAngle + Math.PI)
|
|
593
|
-
const innerMid = point(innerRadius, startAngle + Math.PI)
|
|
594
|
-
return 'M' + outerStart.x.toFixed(2) + ' ' + outerStart.y.toFixed(2) + ' A' + outerRadius + ' ' + outerRadius + ' 0 1 1 ' + outerMid.x.toFixed(2) + ' ' + outerMid.y.toFixed(2) + ' A' + outerRadius + ' ' + outerRadius + ' 0 1 1 ' + outerStart.x.toFixed(2) + ' ' + outerStart.y.toFixed(2) + ' L' + innerStart.x.toFixed(2) + ' ' + innerStart.y.toFixed(2) + ' A' + innerRadius + ' ' + innerRadius + ' 0 1 0 ' + innerMid.x.toFixed(2) + ' ' + innerMid.y.toFixed(2) + ' A' + innerRadius + ' ' + innerRadius + ' 0 1 0 ' + innerStart.x.toFixed(2) + ' ' + innerStart.y.toFixed(2) + ' Z'
|
|
595
|
-
}
|
|
596
|
-
const outerEnd = point(outerRadius, endAngle)
|
|
597
|
-
const innerEnd = point(innerRadius, endAngle)
|
|
598
|
-
const largeArc = sweep > Math.PI ? 1 : 0
|
|
599
|
-
return 'M' + outerStart.x.toFixed(2) + ' ' + outerStart.y.toFixed(2) + ' A' + outerRadius + ' ' + outerRadius + ' 0 ' + largeArc + ' 1 ' + outerEnd.x.toFixed(2) + ' ' + outerEnd.y.toFixed(2) + ' L' + innerEnd.x.toFixed(2) + ' ' + innerEnd.y.toFixed(2) + ' A' + innerRadius + ' ' + innerRadius + ' 0 ' + largeArc + ' 0 ' + innerStart.x.toFixed(2) + ' ' + innerStart.y.toFixed(2) + ' Z'
|
|
600
|
-
}
|
|
601
|
-
function donutArcLinePath(cx, cy, radius, startAngle, endAngle) {
|
|
602
|
-
const sweep = Math.max(0, endAngle - startAngle)
|
|
603
|
-
const point = (angle) => ({ x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) })
|
|
604
|
-
const start = point(startAngle)
|
|
605
|
-
if (sweep >= Math.PI * 2 - .0001) {
|
|
606
|
-
const mid = point(startAngle + Math.PI)
|
|
607
|
-
return 'M' + start.x.toFixed(2) + ' ' + start.y.toFixed(2) + ' A' + radius + ' ' + radius + ' 0 1 1 ' + mid.x.toFixed(2) + ' ' + mid.y.toFixed(2) + ' A' + radius + ' ' + radius + ' 0 1 1 ' + start.x.toFixed(2) + ' ' + start.y.toFixed(2)
|
|
608
|
-
}
|
|
609
|
-
const end = point(endAngle)
|
|
610
|
-
return 'M' + start.x.toFixed(2) + ' ' + start.y.toFixed(2) + ' A' + radius + ' ' + radius + ' 0 ' + (sweep > Math.PI ? 1 : 0) + ' 1 ' + end.x.toFixed(2) + ' ' + end.y.toFixed(2)
|
|
611
|
-
}
|
|
612
|
-
function UsageDonutChart(props) {
|
|
613
|
-
const language = props.language === 'en' ? 'en' : 'zh'
|
|
614
|
-
const tr = (zh, en) => language === 'en' ? en : zh
|
|
615
|
-
const [activeIndex, setActiveIndex] = React.useState(null)
|
|
616
|
-
const [tooltipPosition, setTooltipPosition] = React.useState(null)
|
|
617
|
-
const data = buildDonutSegments(props.items, tr('其他', 'Other'))
|
|
618
|
-
const activeSegment = activeIndex === null ? null : (data.segments[activeIndex] || null)
|
|
619
|
-
if (data.total <= 0) return null
|
|
620
|
-
const cx = 130
|
|
621
|
-
const cy = 130
|
|
622
|
-
const outerRadius = 94
|
|
623
|
-
const innerRadius = 61
|
|
624
|
-
const percentText = (value) => (value >= 10 ? Math.round(value) : Math.round(value * 10) / 10) + '%'
|
|
625
|
-
const updatePointer = (event) => {
|
|
626
|
-
const visual = event.currentTarget.ownerSVGElement?.parentElement
|
|
627
|
-
const box = visual?.getBoundingClientRect()
|
|
628
|
-
if (!box) return
|
|
629
|
-
const tooltipWidth = 198
|
|
630
|
-
const tooltipHeight = 82
|
|
631
|
-
setTooltipPosition({ left: Math.max(8, Math.min(Math.max(8, box.width - tooltipWidth), event.clientX - box.left + 14)), top: Math.max(8, Math.min(Math.max(8, box.height - tooltipHeight), event.clientY - box.top + 14)) })
|
|
632
|
-
}
|
|
633
|
-
const clearPointer = () => { setActiveIndex(null); setTooltipPosition(null) }
|
|
634
|
-
return React.createElement('div', { className: 'uh-donut-chart', 'aria-label': props.title },
|
|
635
|
-
React.createElement('div', { className: 'uh-donut-title' }, React.createElement(LineIcon, { name: props.icon || 'chart', size: 16 }), props.title),
|
|
636
|
-
React.createElement('div', { className: 'uh-donut-layout' },
|
|
637
|
-
React.createElement('div', { className: 'uh-donut-visual' },
|
|
638
|
-
React.createElement('svg', { className: 'uh-donut-svg', viewBox: '0 0 260 260', role: 'img', 'aria-label': props.title + ' ' + tokenDisplay(data.total, language) },
|
|
639
|
-
React.createElement('circle', { cx, cy, r: (outerRadius + innerRadius) / 2, className: 'uh-donut-track', fill: 'none', stroke: 'var(--dsw-alias-bg-layer-2)', strokeWidth: outerRadius - innerRadius }),
|
|
640
|
-
data.segments.map((segment) => React.createElement('path', { key: 'donut-' + segment.index, d: donutArcLinePath(cx, cy, (outerRadius + innerRadius) / 2, segment.startAngle, segment.endAngle), className: 'uh-donut-segment' + (activeIndex === segment.index ? ' uh-active' : ''), fill: 'none', stroke: segment.color, strokeWidth: outerRadius - innerRadius, strokeLinecap: 'butt', strokeLinejoin: 'round', pathLength: 1, style: { animationDelay: (segment.index * 90) + 'ms' }, tabIndex: 0, 'aria-label': segment.label + ' ' + tokenDisplay(segment.value, language) + ' ' + percentText(segment.percentage) + ' ' + costDisplay(segment.cost, language), onMouseEnter: (event) => { setActiveIndex(segment.index); updatePointer(event) }, onMouseMove: updatePointer, onMouseLeave: clearPointer, onFocus: () => { setActiveIndex(segment.index); setTooltipPosition({ left: 12, top: 12 }) }, onBlur: clearPointer })),
|
|
641
|
-
),
|
|
642
|
-
activeSegment ? React.createElement('div', { className: 'uh-donut-tooltip', style: tooltipPosition ? { left: tooltipPosition.left, top: tooltipPosition.top } : undefined },
|
|
643
|
-
React.createElement('span', { className: 'uh-donut-dot', style: { background: activeSegment.color } }),
|
|
644
|
-
React.createElement('div', {},
|
|
645
|
-
React.createElement('strong', {}, activeSegment.label),
|
|
646
|
-
React.createElement('span', {}, tokenDisplay(activeSegment.value, language) + ' · ' + percentText(activeSegment.percentage)),
|
|
647
|
-
React.createElement('span', { className: 'uh-donut-tooltip-cost' }, costDisplay(activeSegment.cost, language)),
|
|
648
|
-
),
|
|
649
|
-
) : null,
|
|
650
|
-
React.createElement('div', { className: 'uh-donut-center' },
|
|
651
|
-
React.createElement('strong', {}, tokenMagnitude(data.total, language)),
|
|
652
|
-
React.createElement('span', {}, language === 'en' ? 'tokens' : 'Token'),
|
|
653
|
-
),
|
|
654
|
-
),
|
|
655
|
-
React.createElement('div', { className: 'uh-donut-legend', role: 'list' },
|
|
656
|
-
data.segments.map((segment) => React.createElement('div', { key: 'legend-' + segment.index, className: 'uh-donut-legend-row', role: 'listitem' },
|
|
657
|
-
React.createElement('span', { className: 'uh-donut-dot', style: { background: segment.color } }),
|
|
658
|
-
React.createElement('div', { className: 'uh-donut-legend-copy' },
|
|
659
|
-
React.createElement('strong', { title: segment.label }, segment.label),
|
|
660
|
-
),
|
|
661
|
-
React.createElement('div', { className: 'uh-donut-legend-metrics' },
|
|
662
|
-
React.createElement('span', {}, tokenDisplay(segment.value, language)),
|
|
663
|
-
React.createElement('span', { className: 'uh-donut-cost' }, costDisplay(segment.cost, language)),
|
|
664
|
-
),
|
|
665
|
-
React.createElement('strong', { className: 'uh-donut-percent' }, percentText(segment.percentage)),
|
|
666
|
-
)),
|
|
667
|
-
),
|
|
668
|
-
),
|
|
669
|
-
)
|
|
670
|
-
}
|
|
671
|
-
function trendSeriesLabel(key, language) {
|
|
672
|
-
const labels = {
|
|
673
|
-
total: language === 'en' ? 'Total' : '总处理',
|
|
674
|
-
input: language === 'en' ? 'Input' : '输入',
|
|
675
|
-
cacheRead: language === 'en' ? 'Cache hits' : '缓存命中',
|
|
676
|
-
cacheWrite: language === 'en' ? 'Cache writes' : '缓存写入',
|
|
677
|
-
output: language === 'en' ? 'Output' : '输出',
|
|
678
|
-
reasoning: language === 'en' ? 'Reasoning' : '推理',
|
|
679
|
-
}
|
|
680
|
-
return labels[key] || key
|
|
681
|
-
}
|
|
682
|
-
function trendSeriesValue(row, key) {
|
|
683
|
-
return key === 'total' ? row.total : (row.tokens && Number.isFinite(row.tokens[key]) ? row.tokens[key] : 0)
|
|
684
|
-
}
|
|
685
|
-
function UsageTrendChart(props) {
|
|
686
|
-
const language = props.language === 'en' ? 'en' : 'zh'
|
|
687
|
-
const tr = (zh, en) => language === 'en' ? en : zh
|
|
688
|
-
const rows = Array.isArray(props.rows) ? props.rows : []
|
|
689
|
-
const visible = Array.isArray(props.visible) && props.visible.length > 0 ? props.visible : ['total']
|
|
690
|
-
const [hoverIndex, setHoverIndex] = React.useState(null)
|
|
691
|
-
const [tooltipIndex, setTooltipIndex] = React.useState(null)
|
|
692
|
-
const width = 900
|
|
693
|
-
const height = 280
|
|
694
|
-
const geometry = buildTrendGeometry(rows, visible, width, height)
|
|
695
|
-
const colors = { total: '#f4c542', input: '#5aa9ff', cacheRead: '#44d483', cacheWrite: '#d98bff', output: '#ff8c66', reasoning: '#aab4c4' }
|
|
696
|
-
const pathFor = (points) => smoothTrendPath(points)
|
|
697
|
-
const bottomY = height - geometry.padding.bottom
|
|
698
|
-
const areaPathFor = (points) => {
|
|
699
|
-
if (!Array.isArray(points) || points.length === 0) return ''
|
|
700
|
-
return pathFor(points) + ' L' + points[points.length - 1].x.toFixed(2) + ' ' + bottomY + ' L' + points[0].x.toFixed(2) + ' ' + bottomY + ' Z'
|
|
701
|
-
}
|
|
702
|
-
const gradientOpacity = { total: .20, input: .16, cacheRead: .18, cacheWrite: .14, output: .16, reasoning: .10 }
|
|
703
|
-
const tickIndexes = rows.length <= 1 ? [0] : Array.from(new Set([0, Math.floor((rows.length - 1) / 4), Math.floor((rows.length - 1) / 2), Math.floor((rows.length - 1) * 3 / 4), rows.length - 1]))
|
|
704
|
-
const chartReady = !props.loading && !props.error && rows.length > 0
|
|
705
|
-
const hourly = rows.length > 0 && Number.isFinite(rows[0].time)
|
|
706
|
-
const chartAriaLabel = hourly ? tr('每小时 Token 使用趋势,选择小时查看当天请求日志', 'Hourly Token usage trend; select an hour to view request logs') : tr('每日 Token 使用趋势,选择日期查看请求日志', 'Daily Token usage trend; select a date to view request logs')
|
|
707
|
-
const hovered = hoverIndex === null ? null : (rows[hoverIndex] || null)
|
|
708
|
-
const hoverPoint = hoverIndex === null ? null : ((geometry.points[visible[0]] || [])[hoverIndex] || null)
|
|
709
|
-
const tooltipRow = tooltipIndex === null ? null : (rows[tooltipIndex] || null)
|
|
710
|
-
const tooltipPoint = tooltipIndex === null ? null : ((geometry.points[visible[0]] || [])[tooltipIndex] || null)
|
|
711
|
-
const tooltipVisible = hoverIndex !== null && tooltipRow !== null && tooltipPoint !== null
|
|
712
|
-
const tooltipSide = tooltipPoint !== null && tooltipPoint.x > width * .68 ? ' uh-left' : ' uh-right'
|
|
713
|
-
const tooltipStyle = tooltipPoint === null ? undefined : { left: (tooltipPoint.x / width * 100).toFixed(2) + '%', top: Math.max(23, Math.min(77, tooltipPoint.y / height * 100)).toFixed(2) + '%' }
|
|
714
|
-
const activateHover = (index) => { setHoverIndex(index); setTooltipIndex(index) }
|
|
715
|
-
const toggle = (key) => {
|
|
716
|
-
if (typeof props.onToggle === 'function') props.onToggle(key)
|
|
717
|
-
}
|
|
718
|
-
const chartBody = props.loading
|
|
719
|
-
? React.createElement('div', { className: 'uh-trend-stage uh-trend-loading', role: 'status', 'aria-label': tr('正在加载趋势', 'Loading trend') }, React.createElement('span', { className: 'uh-trend-spinner', 'aria-hidden': true }))
|
|
720
|
-
: props.error
|
|
721
|
-
? React.createElement('div', { className: 'uh-trend-stage uh-trend-message', role: 'alert' }, props.error)
|
|
722
|
-
: rows.length === 0
|
|
723
|
-
? React.createElement('div', { className: 'uh-trend-stage uh-trend-message' }, tr('该范围内暂无趋势数据', 'No trend data in this range'))
|
|
724
|
-
: React.createElement('div', { className: 'uh-trend-chart-wrap' },
|
|
725
|
-
React.createElement('svg', { className: 'uh-trend-svg', viewBox: '0 0 ' + width + ' ' + height, role: 'group', 'aria-label': chartAriaLabel },
|
|
726
|
-
[0, 0.5, 1].map((ratio) => React.createElement(React.Fragment, { key: ratio },
|
|
727
|
-
React.createElement('line', { x1: geometry.padding.left, x2: width - geometry.padding.right, y1: geometry.padding.top + (height - geometry.padding.top - geometry.padding.bottom) * ratio, y2: geometry.padding.top + (height - geometry.padding.top - geometry.padding.bottom) * ratio, className: 'uh-trend-grid' }),
|
|
728
|
-
React.createElement('text', { x: geometry.padding.left - 7, y: geometry.padding.top + (height - geometry.padding.top - geometry.padding.bottom) * ratio + 4, className: 'uh-trend-axis-label', textAnchor: 'end' }, fmtCompact(Math.round(geometry.max * (1 - ratio)))),
|
|
729
|
-
)),
|
|
730
|
-
React.createElement('defs', {},
|
|
731
|
-
visible.map((key) => React.createElement('linearGradient', { key: key, id: 'uh-trend-gradient-' + key, x1: '0', y1: '0', x2: '0', y2: '1' },
|
|
732
|
-
React.createElement('stop', { offset: '4%', stopColor: colors[key] || '#9aa4b2', stopOpacity: gradientOpacity[key] || .12 }),
|
|
733
|
-
React.createElement('stop', { offset: '96%', stopColor: colors[key] || '#9aa4b2', stopOpacity: 0 }),
|
|
734
|
-
)),
|
|
735
|
-
),
|
|
736
|
-
visible.map((key, seriesIndex) => {
|
|
737
|
-
const areaPath = areaPathFor(geometry.points[key] || [])
|
|
738
|
-
return areaPath === '' ? null : React.createElement('path', { key: 'area-' + key, d: areaPath, className: 'uh-trend-area', 'data-series': key, fill: 'url(#uh-trend-gradient-' + key + ')', style: { animationDelay: (80 + seriesIndex * 80) + 'ms' } })
|
|
739
|
-
}),
|
|
740
|
-
visible.map((key) => React.createElement('path', { key: 'line-base-' + key, d: pathFor(geometry.points[key] || []), className: 'uh-trend-line', 'data-series': key, stroke: colors[key] || '#9aa4b2' })),
|
|
741
|
-
visible.map((key, seriesIndex) => {
|
|
742
|
-
const points = geometry.points[key] || []
|
|
743
|
-
const drawLength = trendPathLength(points)
|
|
744
|
-
return React.createElement('path', { key: 'line-draw-' + key, d: pathFor(points), className: 'uh-trend-line-draw', 'data-series': key, stroke: colors[key] || '#9aa4b2', style: { '--uh-draw-length': drawLength + 'px', animationDelay: (seriesIndex * 90) + 'ms' } })
|
|
745
|
-
}),
|
|
746
|
-
visible.map((key) => {
|
|
747
|
-
const points = geometry.points[key] || []
|
|
748
|
-
if (points.length !== 1) return null
|
|
749
|
-
const point = points[0]
|
|
750
|
-
return React.createElement('circle', { key: 'single-point-' + key, cx: point.x, cy: point.y, r: 4, className: 'uh-trend-point', fill: colors[key] || '#9aa4b2' })
|
|
751
|
-
}),
|
|
752
|
-
hoverIndex !== null && hoverPoint ? React.createElement(React.Fragment, { key: 'hover-' + hoverIndex },
|
|
753
|
-
React.createElement('line', { x1: hoverPoint.x, x2: hoverPoint.x, y1: geometry.padding.top, y2: bottomY, className: 'uh-trend-cursor' }),
|
|
754
|
-
visible.map((key) => { const point = (geometry.points[key] || [])[hoverIndex]; return point ? React.createElement('circle', { key: key, cx: point.x, cy: point.y, r: 4, className: 'uh-trend-point', fill: colors[key] || '#9aa4b2' }) : null }),
|
|
755
|
-
) : null,
|
|
756
|
-
rows.map((row, index) => {
|
|
757
|
-
const point = (geometry.points[visible[0]] || [])[index]
|
|
758
|
-
if (!point) return null
|
|
759
|
-
const next = (geometry.points[visible[0]] || [])[index + 1]
|
|
760
|
-
const cellWidth = next ? Math.max(8, next.x - point.x) : (index > 0 ? Math.max(8, point.x - (geometry.points[visible[0]] || [])[index - 1].x) : 24)
|
|
761
|
-
return React.createElement('rect', { key: trendRowKey(row, index), x: Math.max(geometry.padding.left, point.x - cellWidth / 2), y: geometry.padding.top, width: cellWidth, height: height - geometry.padding.top - geometry.padding.bottom, className: 'uh-trend-hit', tabIndex: 0, role: 'button', 'aria-label': trendRowLabel(row, language, true) + ' ' + trendSeriesLabel('total', language) + ' ' + fmtCompact(row.total), onMouseEnter: () => activateHover(index), onMouseLeave: () => setHoverIndex(null), onFocus: () => activateHover(index), onBlur: () => setHoverIndex(null), onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); if (typeof props.onPointClick === 'function') props.onPointClick(trendRowDate(row)) } }, onClick: () => { if (typeof props.onPointClick === 'function') props.onPointClick(trendRowDate(row)) } })
|
|
762
|
-
}),
|
|
763
|
-
tickIndexes.map((index) => {
|
|
764
|
-
const point = (geometry.points[visible[0]] || [])[index]
|
|
765
|
-
const row = rows[index]
|
|
766
|
-
return point && row ? React.createElement('text', { key: trendRowKey(row, index), x: point.x, y: height - 8, className: 'uh-trend-axis-label', textAnchor: index === 0 ? 'start' : index === rows.length - 1 ? 'end' : 'middle' }, trendRowLabel(row, language, false)) : null
|
|
767
|
-
}),
|
|
768
|
-
),
|
|
769
|
-
tooltipRow && tooltipPoint ? React.createElement('div', { className: 'uh-trend-tooltip' + tooltipSide + (tooltipVisible ? ' uh-visible' : ''), style: tooltipStyle, 'aria-hidden': !tooltipVisible },
|
|
770
|
-
React.createElement('strong', { className: 'uh-trend-tooltip-title' }, trendRowLabel(tooltipRow, language, true)),
|
|
771
|
-
visible.map((key) => React.createElement('div', { key, className: 'uh-trend-tooltip-row', style: { color: colors[key] || '#9aa4b2' } },
|
|
772
|
-
React.createElement('span', { className: 'uh-trend-dot', style: { background: colors[key] || '#9aa4b2' } }),
|
|
773
|
-
React.createElement('span', { className: 'uh-trend-tooltip-label' }, trendSeriesLabel(key, language)),
|
|
774
|
-
React.createElement('strong', { className: 'uh-trend-tooltip-value' }, fmtCompact(trendSeriesValue(tooltipRow, key))),
|
|
775
|
-
)),
|
|
776
|
-
) : null,
|
|
777
|
-
)
|
|
778
|
-
return React.createElement('div', { className: 'uh-panel uh-trend-panel' },
|
|
779
|
-
React.createElement('div', { className: 'uh-trend-head' },
|
|
780
|
-
React.createElement('div', {}, React.createElement('h3', { className: 'uh-tbl-title uh-title-with-icon' }, React.createElement(LineIcon, { name: 'chart', size: 16 }), tr('Token 使用趋势', 'Token Usage Trend')), React.createElement('div', { className: 'uh-note' }, props.rangeLabel || '')),
|
|
781
|
-
chartReady ? React.createElement('div', { className: 'uh-note' }, tr('点击数据点查看当日明细', 'Click a point to inspect that day')) : null,
|
|
782
|
-
),
|
|
783
|
-
chartBody,
|
|
784
|
-
chartReady ? React.createElement('div', { className: 'uh-trend-legend' },
|
|
785
|
-
['total', 'input', 'cacheRead', 'cacheWrite', 'output', 'reasoning'].map((key) => React.createElement('button', { key, type: 'button', className: 'uh-trend-legend-item' + (visible.includes(key) ? ' uh-on' : ''), onClick: () => toggle(key), 'aria-pressed': visible.includes(key) }, React.createElement('span', { className: 'uh-trend-dot', style: { background: colors[key] || '#9aa4b2' } }), trendSeriesLabel(key, language))),
|
|
786
|
-
) : null,
|
|
787
|
-
)
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function UsageFilterMenu(props) {
|
|
791
|
-
const options = Array.isArray(props.options) ? props.options : []
|
|
792
|
-
const value = props.value === undefined || props.value === null ? '' : String(props.value)
|
|
793
|
-
const selected = options.find((option) => String(option.value) === value)
|
|
794
|
-
const [open, setOpen] = React.useState(false)
|
|
795
|
-
const menuRef = React.useRef(null)
|
|
796
|
-
React.useEffect(() => {
|
|
797
|
-
if (!open || typeof document === 'undefined') return undefined
|
|
798
|
-
const closeMenu = (event) => {
|
|
799
|
-
if (menuRef.current && !menuRef.current.contains(event.target)) setOpen(false)
|
|
800
|
-
}
|
|
801
|
-
document.addEventListener('pointerdown', closeMenu)
|
|
802
|
-
return () => document.removeEventListener('pointerdown', closeMenu)
|
|
803
|
-
}, [open])
|
|
804
|
-
const choose = (next) => {
|
|
805
|
-
if (typeof props.onChange === 'function') props.onChange(next)
|
|
806
|
-
setOpen(false)
|
|
807
|
-
}
|
|
808
|
-
return React.createElement('div', { className: 'uh-language-menu uh-filter-menu' + (props.className ? ' ' + props.className : '') + (open ? ' uh-open' : ''), ref: menuRef, onKeyDown: (event) => { if (event.key === 'Escape' && open) { event.preventDefault(); event.stopPropagation(); setOpen(false) } } },
|
|
809
|
-
React.createElement('button', {
|
|
810
|
-
type: 'button',
|
|
811
|
-
className: 'uh-language-trigger uh-filter-trigger' + (open ? ' uh-open' : ''),
|
|
812
|
-
title: selected ? selected.label : props.label,
|
|
813
|
-
'aria-label': props.ariaLabel || props.label,
|
|
814
|
-
'aria-haspopup': 'listbox',
|
|
815
|
-
'aria-expanded': open,
|
|
816
|
-
onClick: () => setOpen((current) => !current),
|
|
817
|
-
},
|
|
818
|
-
React.createElement(LineIcon, { name: props.icon || 'chart', size: 14 }),
|
|
819
|
-
React.createElement('span', { className: 'uh-filter-label' }, selected ? selected.label : props.label),
|
|
820
|
-
React.createElement(LineIcon, { name: 'chevron', size: 13, className: 'uh-language-caret' }),
|
|
821
|
-
),
|
|
822
|
-
open ? React.createElement('div', { className: 'uh-language-options uh-filter-options', role: 'listbox', 'aria-label': props.ariaLabel || props.label },
|
|
823
|
-
options.map((option) => {
|
|
824
|
-
const optionValue = String(option.value)
|
|
825
|
-
const active = optionValue === value
|
|
826
|
-
return React.createElement('button', {
|
|
827
|
-
key: optionValue,
|
|
828
|
-
type: 'button',
|
|
829
|
-
role: 'option',
|
|
830
|
-
'aria-selected': active,
|
|
831
|
-
className: 'uh-language-option' + (active ? ' uh-on' : ''),
|
|
832
|
-
onClick: () => choose(optionValue),
|
|
833
|
-
},
|
|
834
|
-
React.createElement(LineIcon, { name: props.icon || 'chart', size: 14 }),
|
|
835
|
-
React.createElement('span', { className: 'uh-filter-option-label' }, option.label),
|
|
836
|
-
active ? React.createElement(LineIcon, { name: 'check', size: 14, className: 'uh-language-option-check' }) : null,
|
|
837
|
-
)
|
|
838
|
-
}),
|
|
839
|
-
) : null,
|
|
840
|
-
)
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const CSS = `
|
|
844
|
-
.uh-page { display:flex; flex-direction:column; gap:14px; padding:2px 2px 28px; font-family:inherit; }
|
|
845
|
-
.uh-head { position:relative; z-index:20; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
|
|
846
|
-
.uh-title { margin:0; font-size:15px; font-weight:600; color:var(--dsw-alias-label-primary); }
|
|
847
|
-
.uh-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
|
|
848
|
-
.uh-language-menu, .uh-filter-menu { position:relative; z-index:12; }
|
|
849
|
-
.uh-filter-menu { flex:0 1 auto; min-width:0; }
|
|
850
|
-
.uh-filter-workspace { width:180px; }
|
|
851
|
-
.uh-filter-provider { width:180px; }
|
|
852
|
-
.uh-filter-model { width:260px; }
|
|
853
|
-
.uh-filter-menu.uh-open { z-index:14; }
|
|
854
|
-
.uh-language-trigger { display:inline-flex; align-items:center; gap:6px; min-height:30px; padding:4px 9px 4px 10px; border:1px solid transparent; border-radius:15px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 7%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; font-weight:600; line-height:1; cursor:pointer; transition:border-color .15s ease, background-color .15s ease, transform .1s ease; }
|
|
855
|
-
.uh-filter-trigger { width:100%; min-width:0; justify-content:flex-start; }
|
|
856
|
-
.uh-language-trigger:hover, .uh-language-trigger.uh-open { border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 58%, var(--dsw-alias-border-l2)); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 13%, var(--dsw-alias-bg-layer-1)); }
|
|
857
|
-
.uh-language-trigger:active { transform:scale(.96); }
|
|
858
|
-
.uh-language-label { min-width:26px; text-align:left; }
|
|
859
|
-
.uh-filter-label { min-width:0; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; }
|
|
860
|
-
.uh-language-caret { color:var(--dsw-alias-label-secondary); transition:transform .18s ease; }
|
|
861
|
-
.uh-language-trigger.uh-open .uh-language-caret { transform:rotate(180deg); }
|
|
862
|
-
.uh-language-menu.uh-open { z-index:30; }
|
|
863
|
-
.uh-language-options { position:absolute; top:calc(100% + 7px); right:0; min-width:142px; padding:5px; border:1px solid var(--dsw-alias-border-l2); border-radius:12px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 14px 28px color-mix(in srgb, #000 24%, transparent); animation:uh-menu-in .16s ease both; }
|
|
864
|
-
.uh-filter-options { left:0; right:auto; min-width:100%; max-width:300px; }
|
|
865
|
-
.uh-language-option { display:flex; align-items:center; gap:8px; width:100%; min-height:32px; padding:6px 8px; border:0; border-radius:8px; background:transparent; color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; text-align:left; cursor:pointer; transition:background-color .14s ease, color .14s ease; }
|
|
866
|
-
.uh-filter-option-label { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
867
|
-
.uh-language-option:hover, .uh-language-option:focus-visible { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, var(--dsw-alias-bg-layer-2)); outline:0; }
|
|
868
|
-
.uh-language-option.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 19%, var(--dsw-alias-bg-layer-2)); color:var(--dsw-alias-label-primary); font-weight:600; }
|
|
869
|
-
.uh-language-option-check { margin-left:auto; color:var(--dsw-alias-brand-primary); }
|
|
870
|
-
.uh-range { display:inline-flex; border:1px solid var(--dsw-alias-border-l2); border-radius:8px; overflow:hidden; }
|
|
871
|
-
.uh-range button { border:0; background:transparent; color:var(--dsw-alias-label-secondary); padding:4px 12px; font-size:12px; cursor:pointer; font-family:inherit; transition:background-color .15s ease, color .15s ease; }
|
|
872
|
-
.uh-range button + button { border-left:1px solid var(--dsw-alias-border-l2); }
|
|
873
|
-
.uh-range button.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, var(--dsw-alias-bg-layer-2)); color:var(--dsw-alias-label-primary); font-weight:600; }
|
|
874
|
-
.uh-custom-range { display:grid; grid-template-columns:minmax(180px, 1fr) auto auto; gap:10px 14px; align-items:end; padding:12px; border:1px solid var(--dsw-alias-border-l1); border-radius:10px; background:var(--dsw-alias-bg-layer-1); }
|
|
875
|
-
.uh-custom-range-meta { min-width:0; }
|
|
876
|
-
.uh-custom-range-title { display:flex; align-items:center; gap:7px; color:var(--dsw-alias-label-primary); font-size:13px; font-weight:600; }
|
|
877
|
-
.uh-custom-range-note { margin-top:3px; color:var(--dsw-alias-label-secondary); font-size:11px; line-height:1.45; }
|
|
878
|
-
.uh-custom-range-fields { display:grid; grid-template-columns:repeat(2, minmax(136px, 1fr)); gap:8px; }
|
|
879
|
-
.uh-custom-range-field { display:flex; flex-direction:column; gap:4px; color:var(--dsw-alias-label-secondary); font-size:11px; }
|
|
880
|
-
.uh-custom-range-field input { min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:3px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; outline:none; }
|
|
881
|
-
.uh-custom-range-field input:focus { border-color:var(--dsw-alias-brand-primary); }
|
|
882
|
-
.uh-custom-range-actions { display:flex; gap:6px; }
|
|
883
|
-
.uh-custom-range-cancel, .uh-custom-range-apply { min-height:30px; border-radius:6px; padding:4px 10px; font:inherit; font-size:12px; cursor:pointer; }
|
|
884
|
-
.uh-custom-range-cancel { border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); }
|
|
885
|
-
.uh-custom-range-apply { border:1px solid var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color:var(--dsw-alias-label-primary); }
|
|
886
|
-
.uh-custom-range-apply:disabled { opacity:.48; cursor:not-allowed; }
|
|
887
|
-
.uh-custom-range-error { grid-column:1 / -1; color:#d92d20; font-size:12px; }
|
|
888
|
-
.uh-refresh { border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); border-radius:8px; padding:4px 12px; font-size:12px; cursor:pointer; font-family:inherit; transition:border-color .15s ease, color .15s ease, transform .1s ease; }
|
|
889
|
-
.uh-refresh:hover { border-color:var(--dsw-alias-brand-primary); }
|
|
890
|
-
.uh-refresh:active, .uh-chip:active, .uh-range button:active { transform:scale(.96); }
|
|
891
|
-
.uh-alias-panel-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; font-size:13px; font-weight:600; color:var(--dsw-alias-label-primary); }
|
|
892
|
-
.uh-alias-close { border:0; background:transparent; color:var(--dsw-alias-label-secondary); font-size:12px; cursor:pointer; font-family:inherit; padding:0; transition:color .15s ease; }
|
|
893
|
-
.uh-alias-close:hover { color:var(--dsw-alias-brand-primary); }
|
|
894
|
-
.uh-alias-list { display:grid; grid-template-columns:repeat(auto-fill, minmax(250px, 1fr)); gap:8px 16px; max-height:240px; overflow-y:auto; }
|
|
895
|
-
.uh-alias-item { display:flex; align-items:center; gap:8px; min-width:0; }
|
|
896
|
-
.uh-alias-folder { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; color:var(--dsw-alias-label-secondary); }
|
|
897
|
-
.uh-alias-input { flex:none; width:150px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); border-radius:6px; padding:3px 8px; font-size:12px; font-family:inherit; outline:none; transition:border-color .15s ease; }
|
|
898
|
-
.uh-alias-input:focus { border-color:var(--dsw-alias-brand-primary); }
|
|
899
|
-
.uh-alias-panel-foot { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:10px; padding-top:10px; border-top:1px solid var(--dsw-alias-border-l1); }
|
|
900
|
-
.uh-alias-ok { border:1px solid var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color:var(--dsw-alias-label-primary); border-radius:6px; font-size:12px; padding:3px 12px; cursor:pointer; font-family:inherit; flex:none; transition:transform .1s ease; }
|
|
901
|
-
.uh-alias-ok:active { transform:scale(.96); }
|
|
902
|
-
.uh-anim-panel { animation:uh-panel-in .28s ease both; }
|
|
903
|
-
.uh-pricing-panel { display:flex; flex-direction:column; gap:12px; }
|
|
904
|
-
.uh-pricing-head, .uh-pricing-toolbar, .uh-pricing-section-head, .uh-pricing-foot { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
|
|
905
|
-
.uh-pricing-note { color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.55; }
|
|
906
|
-
.uh-pricing-toolbar { padding:10px 0; border-top:1px solid var(--dsw-alias-border-l1); border-bottom:1px solid var(--dsw-alias-border-l1); }
|
|
907
|
-
.uh-pricing-switch { display:inline-flex; align-items:center; gap:7px; color:var(--dsw-alias-label-primary); font-size:12px; }
|
|
908
|
-
.uh-pricing-section { display:flex; flex-direction:column; gap:8px; }
|
|
909
|
-
.uh-pricing-table-wrap { max-height:392px; overflow-x:auto; overflow-y:scroll; scrollbar-gutter:stable; scrollbar-width:auto; scrollbar-color:#707780 #1d1f22; border:1px solid var(--dsw-alias-border-l1); border-radius:8px; background:var(--dsw-alias-bg-layer-2); }
|
|
910
|
-
.uh-pricing-model-table { width:100%; min-width:920px; border-collapse:collapse; table-layout:fixed; font-size:11px; }
|
|
911
|
-
.uh-pricing-model-table th, .uh-pricing-model-table td { min-width:0; padding:8px 9px; border-bottom:1px solid var(--dsw-alias-border-l1); text-align:left; vertical-align:middle; }
|
|
912
|
-
.uh-pricing-model-table th { position:sticky; top:0; z-index:1; color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-2); font-weight:650; white-space:nowrap; }
|
|
913
|
-
.uh-pricing-model-table th:nth-child(1) { width:27%; }
|
|
914
|
-
.uh-pricing-model-table th:nth-child(2) { width:88px; }
|
|
915
|
-
.uh-pricing-model-table th:nth-child(3) { width:23%; }
|
|
916
|
-
.uh-pricing-model-table th:nth-child(n+4) { width:105px; text-align:right; }
|
|
917
|
-
.uh-pricing-model-table td:nth-child(n+4) { text-align:right; }
|
|
918
|
-
.uh-pricing-model-table tbody tr:last-child td { border-bottom:0; }
|
|
919
|
-
.uh-pricing-model-table tbody tr:hover { background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 65%, transparent); }
|
|
920
|
-
.uh-pricing-model-name, .uh-pricing-model-target { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-primary); }
|
|
921
|
-
.uh-pricing-model-rate { color:var(--dsw-alias-label-secondary); font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
922
|
-
.uh-pricing-status { display:inline-flex; justify-content:center; padding:3px 6px; border-radius:6px; font-size:10px; font-weight:650; }
|
|
923
|
-
.uh-pricing-status-priced { color:#157347; background:color-mix(in srgb, #30d158 22%, transparent); }
|
|
924
|
-
.uh-pricing-status-unpriced, .uh-pricing-status-ambiguous, .uh-pricing-status-unsupported { color:#9a5b00; background:color-mix(in srgb, #ff9f0a 20%, transparent); }
|
|
925
|
-
.uh-pricing-used-model-picker { position:relative; z-index:2; min-width:0; }
|
|
926
|
-
.uh-pricing-used-model-picker:focus-within { z-index:30; }
|
|
927
|
-
.uh-pricing-used-model-input { box-sizing:border-box; width:100%; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }
|
|
928
|
-
.uh-pricing-used-model-input:focus { border-color:var(--dsw-alias-brand-primary); }
|
|
929
|
-
.uh-pricing-used-model-options { top:calc(100% + 7px); left:0; right:auto; width:100%; min-width:280px; max-height:240px; overflow-y:auto; z-index:40; }
|
|
930
|
-
.uh-pricing-model-search { position:relative; z-index:2; min-width:0; }
|
|
931
|
-
.uh-pricing-model-search:focus-within { z-index:30; }
|
|
932
|
-
.uh-pricing-model-search-input { box-sizing:border-box; width:100%; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }
|
|
933
|
-
.uh-pricing-model-search-input:focus { border-color:var(--dsw-alias-brand-primary); }
|
|
934
|
-
.uh-pricing-model-options { top:calc(100% + 7px); left:0; right:auto; width:100%; min-width:280px; max-height:240px; overflow-y:auto; z-index:40; }
|
|
935
|
-
.uh-pricing-model-option { align-items:flex-start; }
|
|
936
|
-
.uh-pricing-model-option-name { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
937
|
-
.uh-pricing-model-option-id { margin-left:auto; padding-left:10px; color:var(--dsw-alias-label-secondary); font-size:10px; white-space:nowrap; }
|
|
938
|
-
.uh-pricing-edit-row { display:grid; grid-template-columns:minmax(280px,1.35fr) minmax(300px,1.45fr) minmax(78px,.5fr) 32px; gap:10px; align-items:center; min-width:790px; }
|
|
939
|
-
.uh-pricing-price-row { grid-template-columns:repeat(5,minmax(108px,1fr)) 32px; min-width:650px; }
|
|
940
|
-
.uh-pricing-price-head { display:grid; grid-template-columns:repeat(5,minmax(108px,1fr)) 32px; gap:10px; align-items:center; min-width:650px; color:var(--dsw-alias-label-secondary); font-size:10px; }
|
|
941
|
-
.uh-pricing-price-head span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
942
|
-
.uh-pricing-edit-row input, .uh-pricing-edit-row select { box-sizing:border-box; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }
|
|
943
|
-
.uh-pricing-edit-row input:focus, .uh-pricing-edit-row select:focus { border-color:var(--dsw-alias-brand-primary); }
|
|
944
|
-
.uh-pricing-edit-row .uh-refresh { min-height:30px; padding:0; }
|
|
945
|
-
.uh-pricing-error { color:var(--dsw-alias-warning, #a55b00); font-size:12px; line-height:1.45; }
|
|
946
|
-
.uh-pricing-foot { padding-top:4px; }
|
|
947
|
-
.uh-cost-num { color:var(--dsw-alias-label-primary); }
|
|
948
|
-
.uh-progress { font-size:12px; color:var(--dsw-alias-label-secondary); display:flex; align-items:center; gap:10px; }
|
|
949
|
-
.uh-sync-health { margin-top:8px; padding:8px 12px; display:flex; align-items:center; flex-wrap:wrap; gap:6px; border:1px solid var(--dsw-alias-border-l1); border-radius:10px; color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-1); font-size:11px; line-height:1.45; }
|
|
950
|
-
.uh-sync-health.uh-stale { color:var(--dsw-alias-warning, #a55b00); border-color:color-mix(in srgb, var(--dsw-alias-warning, #d9822b) 45%, var(--dsw-alias-border-l1)); }
|
|
951
|
-
.uh-sync-retry { border:0; background:transparent; color:inherit; font:inherit; text-decoration:underline; cursor:pointer; padding:0 2px; }
|
|
952
|
-
.uh-trend-panel { min-height:300px; animation:uh-panel-in .38s ease both; }
|
|
953
|
-
.uh-trend-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:10px; }
|
|
954
|
-
.uh-trend-chart-wrap { position:relative; min-height:250px; width:100%; overflow:hidden; }
|
|
955
|
-
.uh-trend-stage { display:grid; place-items:center; min-height:250px; width:100%; }
|
|
956
|
-
.uh-trend-message { color:var(--dsw-alias-label-secondary); font-size:12px; }
|
|
957
|
-
.uh-trend-spinner { width:24px; height:24px; border:2px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, var(--dsw-alias-border-l2)); border-top-color:var(--dsw-alias-brand-primary); border-radius:50%; animation:uh-spinner-turn .78s linear infinite; }
|
|
958
|
-
.uh-trend-svg { display:block; width:100%; height:auto; min-height:220px; }
|
|
959
|
-
.uh-trend-grid { stroke:var(--dsw-alias-border-l1); stroke-width:1; stroke-dasharray:3 4; opacity:.8; }
|
|
960
|
-
.uh-trend-cursor { stroke:var(--dsw-alias-label-secondary); stroke-width:1; stroke-dasharray:3 4; opacity:.65; pointer-events:none; }
|
|
961
|
-
.uh-trend-point { stroke:var(--dsw-alias-bg-layer-1); stroke-width:2; vector-effect:non-scaling-stroke; pointer-events:none; }
|
|
962
|
-
.uh-trend-axis-label { fill:var(--dsw-alias-label-secondary); font-size:11px; font-family:inherit; }
|
|
963
|
-
.uh-trend-line { fill:none; stroke-width:2.2; vector-effect:non-scaling-stroke; stroke-linecap:round; stroke-linejoin:round; opacity:.22; }
|
|
964
|
-
.uh-trend-line-draw { fill:none; stroke-width:2.2; vector-effect:non-scaling-stroke; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:var(--uh-draw-length); stroke-dashoffset:var(--uh-draw-length); opacity:.96; pointer-events:none; animation:uh-trend-draw .95s cubic-bezier(.22,.61,.36,1) both; }
|
|
965
|
-
.uh-trend-area { opacity:1; animation:uh-trend-fill .8s ease; }
|
|
966
|
-
.uh-trend-hit { fill:transparent; cursor:crosshair; outline:none; }
|
|
967
|
-
.uh-trend-hit:focus { fill:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent); outline:1px solid var(--dsw-alias-brand-primary); outline-offset:2px; }
|
|
968
|
-
.uh-trend-tooltip { position:absolute; z-index:4; min-width:166px; padding:10px 11px; border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 88%, transparent); border-radius:8px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 94%, transparent); box-shadow:0 10px 24px rgba(0,0,0,.22); backdrop-filter:blur(10px); color:var(--dsw-alias-label-primary); font-size:12px; line-height:1.45; pointer-events:none; opacity:0; visibility:hidden; transform:translate(14px,-50%) scale(.985); transform-origin:left center; transition:left .16s cubic-bezier(.22,.61,.36,1), top .16s cubic-bezier(.22,.61,.36,1), opacity .12s ease, transform .16s cubic-bezier(.22,.61,.36,1), visibility 0s linear .16s; }
|
|
969
|
-
.uh-trend-tooltip.uh-left { transform:translate(calc(-100% - 14px),-50%) scale(.985); transform-origin:right center; }
|
|
970
|
-
.uh-trend-tooltip.uh-visible { opacity:1; visibility:visible; transform:translate(14px,-50%) scale(1); transition-delay:0s; }
|
|
971
|
-
.uh-trend-tooltip.uh-left.uh-visible { transform:translate(calc(-100% - 14px),-50%) scale(1); }
|
|
972
|
-
.uh-trend-tooltip-title { display:block; margin-bottom:6px; color:var(--dsw-alias-label-primary); font-size:12px; font-weight:650; }
|
|
973
|
-
.uh-trend-tooltip-row { display:grid; grid-template-columns:8px minmax(0,1fr) auto; align-items:center; gap:7px; min-width:0; margin-top:3px; font-size:11px; }
|
|
974
|
-
.uh-trend-tooltip-row .uh-trend-dot { width:8px; height:8px; margin:0; }
|
|
975
|
-
.uh-trend-tooltip-label { overflow:hidden; font-weight:600; text-overflow:ellipsis; white-space:nowrap; }
|
|
976
|
-
.uh-trend-tooltip-value { color:inherit; font-weight:600; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
977
|
-
.uh-trend-dot { display:inline-block; width:7px; height:7px; margin-right:5px; border-radius:50%; vertical-align:1px; }
|
|
978
|
-
.uh-trend-legend { display:flex; flex-wrap:wrap; gap:5px 8px; margin-top:5px; }
|
|
979
|
-
.uh-trend-legend-item { display:inline-flex; align-items:center; gap:3px; border:0; border-radius:7px; padding:3px 6px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:11px; cursor:pointer; transition:color .15s ease; }
|
|
980
|
-
.uh-trend-legend-item:hover { background:var(--dsw-alias-bg-layer-2); color:var(--dsw-alias-label-primary); }
|
|
981
|
-
.uh-trend-legend-item.uh-on { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); }
|
|
982
|
-
.uh-filter-bar { position:relative; z-index:10; display:flex; align-items:center; flex-wrap:wrap; gap:7px; }
|
|
983
|
-
.uh-filter-clear { border:0; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:11px; cursor:pointer; text-decoration:underline; }
|
|
984
|
-
.uh-query-note { color:var(--dsw-alias-label-secondary); font-size:11px; }
|
|
985
|
-
.uh-detail-tabs { display:flex; align-items:center; flex-wrap:wrap; gap:4px; padding:4px; border:1px solid var(--dsw-alias-border-l1); border-radius:9px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 58%, transparent); }
|
|
986
|
-
.uh-detail-tab { display:inline-flex; align-items:center; gap:6px; min-height:32px; padding:5px 11px; border:0; border-radius:7px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:12px; cursor:pointer; transition:background-color .15s ease, color .15s ease, transform .12s ease; }
|
|
987
|
-
.uh-detail-tab:hover { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-2); }
|
|
988
|
-
.uh-detail-tab.uh-on { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 3px rgba(0,0,0,.14); }
|
|
989
|
-
.uh-records-panel { animation:uh-panel-in .28s ease both; }
|
|
990
|
-
.uh-records-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:7px; }
|
|
991
|
-
.uh-records-note { margin:8px 0 10px; color:var(--dsw-alias-label-secondary); font-size:11px; line-height:1.45; }
|
|
992
|
-
.uh-records-error { margin:7px 0; color:var(--dsw-alias-warning, #a55b00); font-size:11px; }
|
|
993
|
-
.uh-records-scroll { overflow:auto; border:1px solid var(--dsw-alias-border-l1); border-radius:8px; }
|
|
994
|
-
.uh-record-grid { display:grid; grid-template-columns:112px minmax(190px,1.45fr) 78px repeat(4,minmax(76px,.72fr)) 96px 82px; gap:0; min-width:900px; align-items:center; }
|
|
995
|
-
.uh-record-grid > div { min-width:0; padding:8px 7px; border-bottom:1px solid var(--dsw-alias-border-l1); font-size:11px; }
|
|
996
|
-
.uh-record-header { color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-2); font-weight:600; }
|
|
997
|
-
.uh-record-header > div { white-space:nowrap; }
|
|
998
|
-
.uh-record-row { color:var(--dsw-alias-label-primary); cursor:pointer; outline:none; transition:background-color .14s ease, box-shadow .14s ease; }
|
|
999
|
-
.uh-record-row:hover { background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 68%, transparent); }
|
|
1000
|
-
.uh-record-row.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, var(--dsw-alias-bg-layer-1)); box-shadow:inset 3px 0 var(--dsw-alias-brand-primary); }
|
|
1001
|
-
.uh-record-row:focus-visible { box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary); }
|
|
1002
|
-
.uh-record-row:last-child > div { border-bottom:0; }
|
|
1003
|
-
.uh-record-time, .uh-record-num { color:var(--dsw-alias-label-secondary); font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
1004
|
-
.uh-record-num { text-align:right; }
|
|
1005
|
-
.uh-record-model { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:550; }
|
|
1006
|
-
.uh-record-model small { display:block; overflow:hidden; color:var(--dsw-alias-label-secondary); font-size:10px; font-weight:400; text-overflow:ellipsis; white-space:nowrap; }
|
|
1007
|
-
.uh-record-source { color:var(--dsw-alias-label-secondary); white-space:nowrap; }
|
|
1008
|
-
.uh-records-footer { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:9px; }
|
|
1009
|
-
.uh-record-detail { margin-top:12px; padding:10px 11px; border-top:1px solid var(--dsw-alias-border-l2); background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 42%, transparent); animation:uh-detail-in .24s ease both; }
|
|
1010
|
-
.uh-record-detail-head, .uh-record-detail-meta { display:flex; align-items:center; flex-wrap:wrap; gap:7px 14px; }
|
|
1011
|
-
.uh-record-detail-head { justify-content:space-between; margin-bottom:5px; color:var(--dsw-alias-label-primary); font-size:12px; }
|
|
1012
|
-
.uh-record-detail-meta { color:var(--dsw-alias-label-secondary); font-size:11px; }
|
|
1013
|
-
.uh-record-token-strip { display:grid; grid-template-columns:repeat(5,minmax(72px,1fr)) repeat(2,minmax(82px,1.1fr)); gap:6px; margin-top:9px; }
|
|
1014
|
-
.uh-record-token-strip > div { display:flex; flex-direction:column; gap:2px; min-width:0; padding:6px 7px; border-radius:6px; background:var(--dsw-alias-bg-layer-2); }
|
|
1015
|
-
.uh-record-token-strip span { color:var(--dsw-alias-label-secondary); font-size:10px; }
|
|
1016
|
-
.uh-record-token-strip strong { color:var(--dsw-alias-label-primary); font-size:12px; font-variant-numeric:tabular-nums; }
|
|
1017
|
-
.uh-record-token-total { border:1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 38%, var(--dsw-alias-border-l1)) !important; }
|
|
1018
|
-
@keyframes uh-detail-in { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }
|
|
1019
|
-
@media (max-width:640px) { .uh-trend-head { flex-direction:column; } .uh-filter-menu { flex:1 1 130px; width:auto; } .uh-filter-trigger { max-width:100%; } .uh-trend-tooltip { min-width:116px; } .uh-records-head { flex-direction:column; } .uh-record-token-strip { grid-template-columns:repeat(2,minmax(0,1fr)); } .uh-record-token-total { grid-column:1 / -1; } }
|
|
1020
|
-
.uh-bar { flex:1; height:6px; border-radius:3px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; max-width:340px; }
|
|
1021
|
-
.uh-fill { height:100%; background:var(--dsw-alias-brand-primary); border-radius:3px; transition:width .3s ease; }
|
|
1022
|
-
.uh-cards { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px; }
|
|
1023
|
-
.uh-card { background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l1); border-radius:12px; padding:12px 14px; display:flex; flex-direction:column; gap:6px; min-height:86px; animation:uh-card-in .45s ease both; transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
|
|
1024
|
-
.uh-card:hover { transform:translateY(-2px); border-color:var(--dsw-alias-border-l2); box-shadow:0 6px 18px rgba(0,0,0,.10); }
|
|
1025
|
-
.uh-card-label { font-size:12px; color:var(--dsw-alias-label-secondary); }
|
|
1026
|
-
.uh-card-value { font-size:20px; font-weight:650; color:var(--dsw-alias-label-primary); line-height:1.2; }
|
|
1027
|
-
.uh-card-sub { font-size:11px; color:var(--dsw-alias-label-secondary); line-height:1.55; }
|
|
1028
|
-
.uh-wsbars { display:flex; flex-direction:column; gap:6px; margin-top:2px; }
|
|
1029
|
-
.uh-wsbar { display:flex; flex-direction:column; gap:3px; cursor:pointer; padding:2px 6px; margin:0 -6px; border-radius:8px; transition:background-color .15s ease; }
|
|
1030
|
-
.uh-wsbar:hover { background:var(--dsw-alias-bg-layer-2); }
|
|
1031
|
-
.uh-wsbar.uh-sel { outline:1px solid var(--dsw-alias-brand-primary); }
|
|
1032
|
-
.uh-wsbar-top { display:flex; align-items:center; gap:6px; min-width:0; }
|
|
1033
|
-
.uh-wsbar-title { font-size:12px; color:var(--dsw-alias-label-primary); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; min-width:0; }
|
|
1034
|
-
.uh-wsbar-num { font-size:11px; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-secondary); flex:none; }
|
|
1035
|
-
.uh-panel { background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l1); border-radius:12px; padding:14px; }
|
|
1036
|
-
.uh-hm-head { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:10px; }
|
|
1037
|
-
.uh-chips { display:flex; flex-wrap:wrap; gap:6px; }
|
|
1038
|
-
.uh-chip { display:inline-flex; align-items:center; gap:6px; border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); border-radius:999px; padding:2px 10px; font-size:11px; cursor:pointer; font-family:inherit; max-width:190px; transition:border-color .15s ease, background-color .15s ease, color .15s ease, transform .1s ease; }
|
|
1039
|
-
.uh-chip .uh-chip-title { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1040
|
-
.uh-chip.uh-on { border-color:var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); }
|
|
1041
|
-
.uh-dot { width:8px; height:8px; border-radius:50%; flex:none; }
|
|
1042
|
-
.uh-legend { display:flex; align-items:center; gap:4px; font-size:11px; color:var(--dsw-alias-label-secondary); }
|
|
1043
|
-
.uh-legend .uh-cell { width:10px; height:10px; border-radius:2px; animation:none; }
|
|
1044
|
-
.uh-hm-scroll { overflow-x:auto; padding-bottom:2px; }
|
|
1045
|
-
.uh-months { position:relative; height:16px; margin-left:30px; width:calc(100% - 30px); min-width:686px; font-size:10px; color:var(--dsw-alias-label-secondary); }
|
|
1046
|
-
.uh-months span { position:absolute; top:0; }
|
|
1047
|
-
.uh-hm-body { display:flex; gap:6px; min-width:0; }
|
|
1048
|
-
.uh-wdays { display:grid; grid-template-rows:repeat(7,10px); gap:3px; font-size:10px; color:var(--dsw-alias-label-secondary); text-align:right; width:24px; }
|
|
1049
|
-
.uh-wdays span { line-height:10px; }
|
|
1050
|
-
.uh-grid { flex:1 1 auto; min-width:686px; display:grid; grid-auto-flow:column; grid-template-columns:repeat(53,minmax(10px,1fr)); grid-template-rows:repeat(7,minmax(10px,auto)); gap:3px; }
|
|
1051
|
-
.uh-cell { width:100%; height:auto; min-width:10px; aspect-ratio:1; border-radius:2px; background:var(--dsw-alias-bg-layer-2); animation:uh-cell-in .45s ease both; transition:transform .12s ease, box-shadow .12s ease; }
|
|
1052
|
-
.uh-cell:hover { transform:scale(1.35); box-shadow:0 1px 6px rgba(0,0,0,.28); position:relative; z-index:2; }
|
|
1053
|
-
.uh-tip { position:fixed; z-index:1200; background:var(--dsw-alias-bg-overlay); border:1px solid var(--dsw-alias-border-l2); border-radius:10px; padding:10px 12px; box-shadow:0 8px 24px rgba(0,0,0,.18); pointer-events:auto; min-width:200px; max-width:290px; animation:uh-tip-in .16s ease both; }
|
|
1054
|
-
.uh-tip-date { font-size:12px; font-weight:600; color:var(--dsw-alias-label-primary); margin-bottom:6px; }
|
|
1055
|
-
.uh-tip-row { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--dsw-alias-label-primary); padding:3px 6px; margin:0 -6px; border-radius:6px; cursor:pointer; transition:background-color .12s ease; }
|
|
1056
|
-
.uh-tip-row:hover { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); }
|
|
1057
|
-
.uh-tip-row .uh-n { margin-left:auto; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-secondary); }
|
|
1058
|
-
.uh-tip-tokens { font-size:11px; color:var(--dsw-alias-label-secondary); margin-top:6px; border-top:1px solid var(--dsw-alias-border-l1); padding-top:6px; }
|
|
1059
|
-
.uh-tbl-title { font-size:13px; font-weight:600; color:var(--dsw-alias-label-primary); margin:0 0 10px; }
|
|
1060
|
-
.uh-tbl-scroll { overflow-x:auto; }
|
|
1061
|
-
.uh-hrow, .uh-row { display:grid; grid-template-columns:minmax(160px,2.2fr) .7fr .9fr .9fr .9fr .9fr 1.1fr .9fr .8fr 1fr; gap:8px; align-items:center; min-width:900px; padding:7px 10px; border-radius:8px; font-size:12px; }
|
|
1062
|
-
.uh-model-hrow, .uh-model-row { display:grid; grid-template-columns:minmax(190px,2.2fr) .7fr .9fr .9fr .9fr .9fr 1.1fr .9fr .8fr; gap:8px; align-items:center; min-width:860px; padding:7px 10px; border-radius:8px; font-size:12px; }
|
|
1063
|
-
.uh-hrow { color:var(--dsw-alias-label-secondary); font-size:11px; }
|
|
1064
|
-
.uh-row { cursor:pointer; border:1px solid transparent; transition:background-color .15s ease, border-color .15s ease; }
|
|
1065
|
-
.uh-row:hover { background:var(--dsw-alias-bg-layer-2); }
|
|
1066
|
-
.uh-row.uh-sel { border-color:var(--dsw-alias-brand-primary); }
|
|
1067
|
-
.uh-num { text-align:right; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-primary); }
|
|
1068
|
-
.uh-hrow .uh-num { color:var(--dsw-alias-label-secondary); }
|
|
1069
|
-
.uh-ws-title { color:var(--dsw-alias-label-primary); font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1070
|
-
.uh-row-title-wrap { min-width:0; }
|
|
1071
|
-
.uh-ws-path { color:var(--dsw-alias-label-secondary); font-size:11px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1072
|
-
.uh-barwrap { height:5px; border-radius:3px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; margin-top:3px; }
|
|
1073
|
-
.uh-barwrap.uh-bar-thin { height:3px; margin-top:1px; }
|
|
1074
|
-
.uh-barfill { height:100%; border-radius:3px; transform-origin:left center; animation:uh-bar-grow .7s cubic-bezier(.22,.61,.36,1) both; transition:width .5s cubic-bezier(.22,.61,.36,1); }
|
|
1075
|
-
.uh-empty { color:var(--dsw-alias-label-secondary); font-size:12px; text-align:center; padding:26px 0; }
|
|
1076
|
-
.uh-note { font-size:11px; color:var(--dsw-alias-label-secondary); line-height:1.6; }
|
|
1077
|
-
.uh-side-entry { width:100%; border:0; background:transparent; color:var(--dsw-alias-label-secondary); border-radius:8px; min-height:36px; padding:7px 10px; display:flex; align-items:center; gap:9px; font:inherit; font-size:13px; cursor:pointer; text-align:left; }
|
|
1078
|
-
.uh-side-entry:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); }
|
|
1079
|
-
.uh-side-entry-icon { width:18px; text-align:center; flex:none; font-size:15px; }
|
|
1080
|
-
.uh-side-entry-label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1081
|
-
.uh-boundary-fallback { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px; min-height:360px; padding:24px; border:1px solid var(--dsw-alias-border-l1); border-radius:12px; background:var(--dsw-alias-bg-layer-1); text-align:center; }
|
|
1082
|
-
.uh-boundary-title { color:var(--dsw-alias-label-primary); font-size:15px; font-weight:650; }
|
|
1083
|
-
.uh-boundary-note { max-width:420px; color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.6; }
|
|
1084
|
-
.uh-side-modal { position:fixed; inset:0; z-index:1100; background:color-mix(in srgb, #000 44%, transparent); display:flex; align-items:stretch; justify-content:center; padding:26px; }
|
|
1085
|
-
.uh-side-dialog { width:min(1120px, 100%); overflow-x:hidden; overflow-y:scroll; scrollbar-gutter:stable; scrollbar-width:auto; scrollbar-color:#707780 #1d1f22; background:var(--dsw-alias-bg-base); border:1px solid var(--dsw-alias-border-l2); border-radius:14px; box-shadow:0 18px 52px rgba(0,0,0,.35); padding:18px; }
|
|
1086
|
-
.uh-side-dialog::-webkit-scrollbar, .uh-pricing-table-wrap::-webkit-scrollbar { width:12px; height:12px; }
|
|
1087
|
-
.uh-side-dialog::-webkit-scrollbar-track, .uh-pricing-table-wrap::-webkit-scrollbar-track { background:#1d1f22; border-left:1px solid #363a40; }
|
|
1088
|
-
.uh-side-dialog::-webkit-scrollbar-thumb, .uh-pricing-table-wrap::-webkit-scrollbar-thumb { background:#707780; border:3px solid #1d1f22; border-radius:6px; }
|
|
1089
|
-
.uh-side-dialog::-webkit-scrollbar-thumb:hover, .uh-pricing-table-wrap::-webkit-scrollbar-thumb:hover { background:#9aa1aa; }
|
|
1090
|
-
.uh-side-dialog-head { display:flex; justify-content:flex-end; margin-bottom:8px; }
|
|
1091
|
-
@media (max-width: 640px) { .uh-side-modal { padding:0; } .uh-side-dialog { border-radius:0; border:0; padding:14px; } }
|
|
1092
|
-
/* iOS-style dashboard: grouped surfaces, tactile controls, and an elevated sheet. */
|
|
1093
|
-
.uh-page { gap:18px; max-width:1160px; margin:0 auto; padding:4px 2px 34px; font-family:-apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif; }
|
|
1094
|
-
.uh-head { position:sticky; top:-18px; z-index:20; margin:0 -2px; padding:18px 2px 14px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 88%, transparent); backdrop-filter:blur(18px) saturate(150%); border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 76%, transparent); }
|
|
1095
|
-
.uh-title { font-size:22px; line-height:1.2; font-weight:700; letter-spacing:0; }
|
|
1096
|
-
.uh-actions { gap:8px; }
|
|
1097
|
-
.uh-range { padding:2px; gap:2px; border:0; border-radius:9px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent); overflow:visible; }
|
|
1098
|
-
.uh-range button, .uh-range button + button { min-height:28px; border:0; border-radius:7px; padding:4px 10px; }
|
|
1099
|
-
.uh-range button.uh-on { background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 3px rgba(0,0,0,.16); }
|
|
1100
|
-
.uh-refresh { min-height:30px; border:0; border-radius:15px; padding:5px 12px; display:inline-flex; align-items:center; justify-content:center; gap:6px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-brand-primary); font-weight:600; }
|
|
1101
|
-
.uh-line-icon { flex:none; }
|
|
1102
|
-
.uh-icon-button { width:30px; padding:0; }
|
|
1103
|
-
.uh-refresh:hover { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, var(--dsw-alias-bg-layer-1)); }
|
|
1104
|
-
.uh-progress { padding:10px 12px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 9%, var(--dsw-alias-bg-layer-1)); border:0; border-radius:12px; }
|
|
1105
|
-
.uh-cards { grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px; border:0; border-radius:0; overflow:visible; background:transparent; }
|
|
1106
|
-
.uh-card { min-height:84px; padding:12px 14px; gap:4px; border:0; border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.07), 0 8px 22px rgba(0,0,0,.05); animation:none; }
|
|
1107
|
-
.uh-card:first-child { border:0; background:color-mix(in srgb, #0a84ff 15%, var(--dsw-alias-bg-layer-1)); }
|
|
1108
|
-
.uh-card:nth-child(2) { background:color-mix(in srgb, #30d158 13%, var(--dsw-alias-bg-layer-1)); }
|
|
1109
|
-
.uh-card:nth-child(3) { background:color-mix(in srgb, #ff9f0a 14%, var(--dsw-alias-bg-layer-1)); }
|
|
1110
|
-
.uh-card:hover { transform:translateY(-2px); box-shadow:0 12px 28px rgba(0,0,0,.12); }
|
|
1111
|
-
.uh-card-label { display:flex; align-items:center; gap:6px; font-size:12px; font-weight:600; letter-spacing:0; }
|
|
1112
|
-
.uh-ios-summary-label, .uh-section-title, .uh-title-with-icon { display:flex; align-items:center; gap:7px; }
|
|
1113
|
-
.uh-section-title { margin-bottom:12px; color:var(--dsw-alias-label-primary); font-size:14px; font-weight:650; }
|
|
1114
|
-
/* Raise the complete reading scale without changing the data grid geometry. */
|
|
1115
|
-
.uh-page { font-size:14px; }
|
|
1116
|
-
.uh-range button, .uh-refresh { font-size:13px; }
|
|
1117
|
-
.uh-card-label, .uh-ios-summary-label { font-size:13px; }
|
|
1118
|
-
.uh-card-sub, .uh-ios-summary-caption, .uh-note { font-size:12px; }
|
|
1119
|
-
.uh-hrow, .uh-row, .uh-model-hrow, .uh-model-row { font-size:13px; }
|
|
1120
|
-
.uh-tbl-title { font-size:15px; }
|
|
1121
|
-
.uh-num { font-variant-numeric:tabular-nums; }
|
|
1122
|
-
.uh-card-value { font-size:23px; font-weight:700; letter-spacing:0; }
|
|
1123
|
-
.uh-card-sub { font-size:11px; line-height:1.45; }
|
|
1124
|
-
.uh-panel { padding:16px; border:0; border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 6px 18px rgba(0,0,0,.04); }
|
|
1125
|
-
.uh-hm-head { margin-bottom:12px; }
|
|
1126
|
-
.uh-chip { border:0; border-radius:14px; padding:5px 10px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 7%, transparent); }
|
|
1127
|
-
.uh-chip.uh-on { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent); color:var(--dsw-alias-brand-primary); }
|
|
1128
|
-
.uh-hrow, .uh-row, .uh-model-hrow, .uh-model-row { border-radius:10px; }
|
|
1129
|
-
.uh-hrow, .uh-model-hrow { position:sticky; top:66px; z-index:2; background:var(--dsw-alias-bg-layer-1); border-bottom:1px solid var(--dsw-alias-border-l1); }
|
|
1130
|
-
.uh-row, .uh-model-row { padding-top:9px; padding-bottom:9px; }
|
|
1131
|
-
.uh-row:nth-child(even) { background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 52%, transparent); }
|
|
1132
|
-
.uh-side-entry { min-height:40px; border:0; border-radius:12px; padding:8px 10px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 9%, transparent); color:var(--dsw-alias-brand-primary); font-weight:600; }
|
|
1133
|
-
.uh-side-entry:hover { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 17%, transparent); }
|
|
1134
|
-
.uh-side-entry-icon { color:var(--dsw-alias-brand-primary); font-weight:700; }
|
|
1135
|
-
.uh-side-modal { align-items:flex-end; padding:0; background:rgba(0,0,0,.34); backdrop-filter:blur(8px); }
|
|
1136
|
-
.uh-side-dialog { width:min(1260px, 100%); max-height:calc(100vh - 44px); border:0; border-radius:24px 24px 0 0; padding:22px 24px 28px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); box-shadow:0 -10px 44px rgba(0,0,0,.25); }
|
|
1137
|
-
.uh-side-dialog-head { position:sticky; top:-22px; z-index:8; justify-content:center; height:22px; margin:-22px -24px 8px; padding:8px 24px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); border:0; }
|
|
1138
|
-
.uh-side-dialog-head::before { content:""; width:36px; height:5px; border-radius:3px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent); }
|
|
1139
|
-
.uh-side-dialog-head .uh-refresh { position:absolute; right:20px; top:7px; min-height:28px; background:transparent; }
|
|
1140
|
-
.uh-close-button { width:30px; padding:0; font-size:22px; line-height:1; color:var(--dsw-alias-label-secondary); }
|
|
1141
|
-
.uh-close-button:hover { color:var(--dsw-alias-label-primary); background:color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent); }
|
|
1142
|
-
@media (max-width:640px) { .uh-page { gap:14px; padding-bottom:20px; } .uh-head { position:static; padding:4px 0 10px; } .uh-title { font-size:20px; } .uh-custom-range { grid-template-columns:1fr; align-items:stretch; } .uh-custom-range-fields { grid-template-columns:repeat(2, minmax(0, 1fr)); } .uh-custom-range-actions { justify-content:flex-end; } .uh-side-dialog { max-height:calc(100vh - 8px); border-radius:20px 20px 0 0; padding:18px 14px 24px; } .uh-side-dialog-head { top:-18px; margin:-18px -14px 8px; padding:7px 14px; } .uh-card-value { font-size:22px; } }
|
|
1143
|
-
/* Navigation separates the dashboard into three focused iOS-style surfaces. */
|
|
1144
|
-
.uh-ios-tabs { display:grid; grid-template-columns:repeat(3, 1fr); gap:4px; padding:4px; border-radius:14px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent); }
|
|
1145
|
-
.uh-ios-tab { min-height:32px; border:0; border-radius:10px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:13px; font-weight:600; cursor:pointer; }
|
|
1146
|
-
.uh-ios-tab.uh-on { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 4px rgba(0,0,0,.16); }
|
|
1147
|
-
.uh-ios-summary { display:flex; flex-direction:column; gap:12px; background:transparent; box-shadow:none; }
|
|
1148
|
-
.uh-ios-summary-hero { display:grid; grid-template-columns:minmax(0,1fr) minmax(320px,.48fr); min-height:142px; padding:20px 22px; border:1px solid var(--dsw-alias-border-l1); border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 8px 22px rgba(0,0,0,.06); }
|
|
1149
|
-
.uh-ios-summary-total { min-width:0; min-height:0; padding:0; border-radius:0; display:flex; align-items:center; justify-content:flex-start; gap:16px; background:transparent; box-shadow:none; }
|
|
1150
|
-
.uh-ios-summary-total-icon { display:grid; place-items:center; flex:none; width:54px; height:54px; border-radius:16px; background:color-mix(in srgb,#0a84ff 18%,var(--dsw-alias-bg-layer-2)); color:#0a84ff; }
|
|
1151
|
-
.uh-ios-summary-total-copy { min-width:0; }
|
|
1152
|
-
.uh-ios-summary-label { font-size:13px; font-weight:600; color:var(--dsw-alias-label-secondary); }
|
|
1153
|
-
.uh-ios-summary-total .uh-ios-summary-label { font-size:14px; }
|
|
1154
|
-
.uh-ios-summary-value { margin-top:7px; font-size:40px; line-height:1; font-weight:750; letter-spacing:0; color:var(--dsw-alias-label-primary); }
|
|
1155
|
-
.uh-unit { margin-left:6px; color:var(--dsw-alias-label-secondary); font-size:.4em; font-weight:650; white-space:nowrap; vertical-align:baseline; }
|
|
1156
|
-
.uh-wsbar-num .uh-unit { font-size:.78em; margin-left:3px; }
|
|
1157
|
-
.uh-ios-summary-caption { margin-top:8px; font-size:12px; color:var(--dsw-alias-label-secondary); }
|
|
1158
|
-
.uh-ios-summary-meta { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); align-items:center; min-width:0; gap:0; padding:0 0 0 22px; border-left:1px solid var(--dsw-alias-border-l1); background:transparent; font-size:12px; color:var(--dsw-alias-label-secondary); }
|
|
1159
|
-
.uh-ios-summary-meta-stat { min-width:0; padding:4px 22px; }
|
|
1160
|
-
.uh-ios-summary-meta-stat + .uh-ios-summary-meta-stat { border-left:1px solid var(--dsw-alias-border-l1); }
|
|
1161
|
-
.uh-ios-summary-meta-label { display:flex; align-items:center; gap:7px; color:var(--dsw-alias-label-secondary); font-size:12px; font-weight:600; white-space:nowrap; }
|
|
1162
|
-
.uh-ios-summary-meta-value { margin-top:7px; color:var(--dsw-alias-label-primary); font-size:24px; line-height:1; font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
1163
|
-
.uh-ios-summary-meta-cost .uh-ios-summary-meta-value { color:#30d158; }
|
|
1164
|
-
.uh-ios-summary-meta-caption { margin-top:7px; color:var(--dsw-alias-label-secondary); font-size:11px; white-space:nowrap; }
|
|
1165
|
-
.uh-ios-metrics { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:10px; }
|
|
1166
|
-
.uh-ios-metric { min-width:0; min-height:108px; padding:16px 18px; border:1px solid var(--dsw-alias-border-l1); border-radius:18px; display:flex; flex-direction:column; justify-content:center; gap:10px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 8px 20px rgba(0,0,0,.05); animation:uh-card-in .35s ease both; }
|
|
1167
|
-
.uh-ios-metrics > .uh-card { min-width:0; min-height:141px; padding:16px 18px; gap:4px; border:0; border-radius:18px; }
|
|
1168
|
-
.uh-ios-metrics > .uh-card .uh-card-value { min-width:0; font-size:23px; line-height:1.2; font-weight:700; white-space:nowrap; }
|
|
1169
|
-
.uh-ios-metrics > .uh-card .uh-card-sub { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1170
|
-
.uh-ios-metrics > .uh-card:nth-child(-n+3) { justify-content:center; }
|
|
1171
|
-
.uh-ios-metric-label { display:flex; align-items:center; gap:8px; min-width:0; color:var(--dsw-alias-label-secondary); font-size:13px; font-weight:600; white-space:nowrap; }
|
|
1172
|
-
.uh-ios-metric-label .uh-line-icon { flex:none; }
|
|
1173
|
-
.uh-ios-metric-value { min-width:0; color:var(--dsw-alias-label-primary); font-size:26px; line-height:1.05; font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }
|
|
1174
|
-
.uh-ios-metric-input { background:color-mix(in srgb,#0a84ff 11%,var(--dsw-alias-bg-layer-1)); }
|
|
1175
|
-
.uh-ios-metric-input .uh-line-icon { color:#0a84ff; }
|
|
1176
|
-
.uh-ios-metric-output { background:color-mix(in srgb,#bf5af2 10%,var(--dsw-alias-bg-layer-1)); }
|
|
1177
|
-
.uh-ios-metric-output .uh-line-icon { color:#bf5af2; }
|
|
1178
|
-
.uh-ios-metric-write { background:color-mix(in srgb,#ff9f0a 11%,var(--dsw-alias-bg-layer-1)); }
|
|
1179
|
-
.uh-ios-metric-write .uh-line-icon { color:#ff9f0a; }
|
|
1180
|
-
.uh-ios-metric-read { background:color-mix(in srgb,#30d158 11%,var(--dsw-alias-bg-layer-1)); }
|
|
1181
|
-
.uh-ios-metric-read .uh-line-icon { color:#30d158; }
|
|
1182
|
-
.uh-ios-metric-rate { background:var(--dsw-alias-bg-layer-1); }
|
|
1183
|
-
.uh-ios-metric-rate .uh-line-icon { color:#30d158; }
|
|
1184
|
-
.uh-ios-metric-rate-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px; min-width:0; }
|
|
1185
|
-
.uh-ios-metric-rate-value { flex:none; color:#30d158; font-size:24px; line-height:1; font-weight:700; font-variant-numeric:tabular-nums; }
|
|
1186
|
-
.uh-ios-metric-rate-detail { min-width:0; overflow:hidden; color:var(--dsw-alias-label-secondary); font-size:14px; line-height:1.2; font-weight:600; font-variant-numeric:tabular-nums; text-overflow:ellipsis; white-space:nowrap; }
|
|
1187
|
-
.uh-ios-metric-bar { height:7px; border-radius:4px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; }
|
|
1188
|
-
.uh-ios-metric-fill { height:100%; border-radius:inherit; background:#30d158; transition:width .35s ease; }
|
|
1189
|
-
.uh-token-semantics { display:flex; align-items:flex-start; gap:8px; padding:10px 12px; border-radius:12px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.55; }
|
|
1190
|
-
.uh-token-semantics .uh-line-icon { margin-top:1px; color:var(--dsw-alias-brand-primary); }
|
|
1191
|
-
.uh-ios-list-panel { min-height:360px; }
|
|
1192
|
-
.uh-donut-chart { margin:0 0 18px; }
|
|
1193
|
-
.uh-donut-title { display:flex; align-items:center; gap:7px; margin-bottom:12px; color:var(--dsw-alias-label-primary); font-size:14px; font-weight:650; }
|
|
1194
|
-
.uh-donut-layout { display:grid; grid-template-columns:minmax(220px,300px) minmax(0,1fr); gap:24px; align-items:center; }
|
|
1195
|
-
.uh-donut-visual { position:relative; width:min(100%,280px); aspect-ratio:1; margin:0 auto; }
|
|
1196
|
-
.uh-donut-svg { display:block; width:100%; height:100%; overflow:visible; }
|
|
1197
|
-
.uh-donut-track { opacity:.78; }
|
|
1198
|
-
.uh-donut-segment { fill:none; stroke-dasharray:1; stroke-dashoffset:1; animation:uh-donut-draw .95s cubic-bezier(.22,.61,.36,1) both; cursor:pointer; outline:none; transition:filter .15s ease, opacity .15s ease; }
|
|
1199
|
-
.uh-donut-segment:hover, .uh-donut-segment:focus-visible, .uh-donut-segment.uh-active { filter:brightness(1.12); }
|
|
1200
|
-
.uh-donut-tooltip { position:absolute; top:0; left:0; z-index:3; display:flex; align-items:flex-start; gap:8px; max-width:190px; padding:9px 10px; border:1px solid var(--dsw-alias-border-l2); border-radius:8px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); box-shadow:0 10px 24px rgba(0,0,0,.22); backdrop-filter:blur(10px); color:var(--dsw-alias-label-primary); pointer-events:none; font-size:12px; line-height:1.4; transition:left .12s cubic-bezier(.22,.61,.36,1), top .12s cubic-bezier(.22,.61,.36,1); }
|
|
1201
|
-
.uh-donut-tooltip > div { min-width:0; display:flex; flex-direction:column; gap:4px; }
|
|
1202
|
-
.uh-donut-tooltip strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }
|
|
1203
|
-
.uh-donut-tooltip span:not(.uh-donut-dot) { color:var(--dsw-alias-label-secondary); font-size:11px; }
|
|
1204
|
-
.uh-donut-tooltip-cost { color:var(--dsw-alias-label-primary) !important; font-variant-numeric:tabular-nums; }
|
|
1205
|
-
.uh-donut-center { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; pointer-events:none; }
|
|
1206
|
-
.uh-donut-center strong { color:var(--dsw-alias-label-primary); font-size:28px; line-height:1; font-weight:750; font-variant-numeric:tabular-nums; }
|
|
1207
|
-
.uh-donut-center span { margin-top:5px; color:var(--dsw-alias-label-secondary); font-size:13px; }
|
|
1208
|
-
.uh-donut-legend { min-width:0; }
|
|
1209
|
-
.uh-donut-legend-row { display:grid; grid-template-columns:12px minmax(180px,1fr) minmax(250px,.8fr) 54px; gap:10px; align-items:center; min-height:58px; padding:8px 0; border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 78%, transparent); }
|
|
1210
|
-
.uh-donut-legend-row:last-child { border-bottom:0; }
|
|
1211
|
-
.uh-donut-dot { width:12px; height:12px; border-radius:50%; }
|
|
1212
|
-
.uh-donut-legend-copy { min-width:0; display:flex; flex-direction:column; gap:5px; }
|
|
1213
|
-
.uh-donut-legend-copy strong { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-primary); font-size:13px; font-weight:650; }
|
|
1214
|
-
.uh-donut-legend-metrics { display:grid; grid-template-columns:minmax(120px,1fr) minmax(92px,auto); align-items:center; gap:14px; min-width:0; }
|
|
1215
|
-
.uh-donut-legend-metrics span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-secondary); font-size:12px; font-variant-numeric:tabular-nums; text-align:right; }
|
|
1216
|
-
.uh-donut-legend-metrics .uh-donut-cost { color:var(--dsw-alias-label-secondary); font-size:12px; }
|
|
1217
|
-
.uh-donut-percent { min-width:48px; color:var(--dsw-alias-label-secondary); font-size:12px; font-variant-numeric:tabular-nums; text-align:right; }
|
|
1218
|
-
/* Each detail table owns its scrolling and sticky header; sections must not overlap in the page scroll. */
|
|
1219
|
-
.uh-tbl-scroll { max-height:360px; overflow:auto; border-radius:12px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent); }
|
|
1220
|
-
.uh-hrow, .uh-model-hrow { position:sticky; top:0; z-index:3; border-bottom:1px solid var(--dsw-alias-border-l1); box-shadow:0 1px 0 color-mix(in srgb, var(--dsw-alias-bg-base) 70%, transparent); }
|
|
1221
|
-
.uh-row, .uh-model-row { min-height:48px; border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 72%, transparent); }
|
|
1222
|
-
.uh-row:last-child, .uh-model-row:last-child { border-bottom:0; }
|
|
1223
|
-
@media (max-width:640px) { .uh-tbl-scroll { max-height:300px; border-radius:10px; } }
|
|
1224
|
-
@media (max-width:640px) { .uh-hm-body { min-width:720px; } .uh-donut-legend-row { grid-template-columns:12px minmax(0,1fr) 48px; gap:8px; } .uh-donut-legend-copy { grid-column:2; grid-row:1; } .uh-donut-legend-metrics { grid-column:2 / -1; grid-row:2; grid-template-columns:minmax(0,1fr) minmax(0,auto); gap:8px; } .uh-donut-percent { grid-column:3; grid-row:1; } .uh-ios-summary-hero { grid-template-columns:1fr; min-height:0; gap:18px; padding:18px; } .uh-ios-summary-total { align-items:flex-start; } .uh-ios-summary-meta { grid-template-columns:repeat(2,minmax(0,1fr)); padding:16px 0 0; border-left:0; border-top:1px solid var(--dsw-alias-border-l1); } .uh-ios-summary-meta-stat { padding:0 12px; } .uh-ios-summary-meta-stat:first-child { padding-left:0; } .uh-ios-summary-meta-stat:last-child { padding-right:0; } .uh-ios-summary-value { font-size:31px; } .uh-ios-metrics { grid-template-columns:repeat(2,minmax(0,1fr)); } .uh-ios-metric:last-child { grid-column:1 / -1; } .uh-ios-metric-value { font-size:24px; } .uh-donut-layout { grid-template-columns:1fr; gap:12px; } .uh-donut-visual { width:min(100%,250px); } }
|
|
1225
|
-
@keyframes uh-cell-in { from { opacity:0; transform:scale(.4); } to { opacity:1; transform:scale(1); } }
|
|
1226
|
-
@keyframes uh-glow { 0% { box-shadow:0 0 0 0 rgba(46,160,67,.5); } 70% { box-shadow:0 0 0 5px rgba(46,160,67,0); } 100% { box-shadow:0 0 0 0 rgba(46,160,67,0); } }
|
|
1227
|
-
@keyframes uh-card-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
|
|
1228
|
-
@keyframes uh-bar-grow { from { transform:scaleX(0); } to { transform:scaleX(1); } }
|
|
1229
|
-
@keyframes uh-panel-in { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
|
|
1230
|
-
@keyframes uh-trend-draw { from { stroke-dashoffset:var(--uh-draw-length); opacity:.2; } to { stroke-dashoffset:0; opacity:1; } }
|
|
1231
|
-
@keyframes uh-trend-fill { from { opacity:0; } to { opacity:1; } }
|
|
1232
|
-
@keyframes uh-donut-draw { from { stroke-dashoffset:1; opacity:.25; } to { stroke-dashoffset:0; opacity:1; } }
|
|
1233
|
-
@keyframes uh-spinner-turn { to { transform:rotate(360deg); } }
|
|
1234
|
-
@keyframes uh-menu-in { from { opacity:0; transform:translateY(-4px) scale(.97); } to { opacity:1; transform:translateY(0) scale(1); } }
|
|
1235
|
-
@keyframes uh-tip-in { from { opacity:0; } to { opacity:1; } }
|
|
1236
|
-
@media (prefers-reduced-motion: reduce) {
|
|
1237
|
-
.uh-cell, .uh-card, .uh-ios-metric, .uh-barfill, .uh-anim-panel, .uh-trend-panel, .uh-trend-line-draw, .uh-trend-area, .uh-trend-point, .uh-trend-spinner, .uh-donut-segment, .uh-records-panel, .uh-record-detail, .uh-tip, .uh-language-options { animation:none !important; stroke-dashoffset:0 !important; opacity:1 !important; }
|
|
1238
|
-
.uh-card, .uh-cell, .uh-ios-metric-fill, .uh-barfill, .uh-fill, .uh-refresh, .uh-chip, .uh-row, .uh-tip-row, .uh-trend-tooltip, .uh-language-trigger, .uh-language-caret, .uh-language-option { transition:none !important; }
|
|
1239
|
-
}
|
|
1240
|
-
`
|
|
1241
|
-
const cssTagId = "dsh-all-usage/styles.css"
|
|
1242
|
-
if (typeof document !== "undefined") {
|
|
1243
|
-
let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(cssTagId) + "]")
|
|
1244
|
-
if (tag === null) {
|
|
1245
|
-
tag = document.createElement("style")
|
|
1246
|
-
tag.dataset.plugin = "dsh-all-usage"
|
|
1247
|
-
tag.dataset.pluginCss = cssTagId
|
|
1248
|
-
document.head.appendChild(tag)
|
|
1249
|
-
}
|
|
1250
|
-
tag.textContent = CSS
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// 与 Host 半的数据接口(webServer 路由)
|
|
1254
|
-
const getStats = () => fetch('/api/all-usage', { headers: { accept: 'application/json' } }).then((r) => {
|
|
1255
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1256
|
-
return r.json()
|
|
1257
|
-
})
|
|
1258
|
-
const getStatus = () => fetch('/api/all-usage/status', { headers: { accept: 'application/json' } }).then((r) => {
|
|
1259
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1260
|
-
return r.json()
|
|
1261
|
-
})
|
|
1262
|
-
const getUsageQuery = (scope) => {
|
|
1263
|
-
const params = new URLSearchParams({ start: scope.start, end: scope.end, utc: scope.utc ? '1' : '0' })
|
|
1264
|
-
if (scope.workspaceId) params.set('workspaceId', scope.workspaceId)
|
|
1265
|
-
if (scope.provider) params.set('provider', scope.provider)
|
|
1266
|
-
if (scope.modelKey) params.set('modelKey', scope.modelKey)
|
|
1267
|
-
return fetch('/api/all-usage/query?' + params.toString(), { headers: { accept: 'application/json' } }).then((r) => {
|
|
1268
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1269
|
-
return r.json()
|
|
1270
|
-
})
|
|
1271
|
-
}
|
|
1272
|
-
const getUsageRecords = (scope, cursor, limit) => {
|
|
1273
|
-
const params = new URLSearchParams({ start: scope.start, end: scope.end, utc: scope.utc ? '1' : '0', limit: String(limit || 100) })
|
|
1274
|
-
if (scope.workspaceId) params.set('workspaceId', scope.workspaceId)
|
|
1275
|
-
if (scope.provider) params.set('provider', scope.provider)
|
|
1276
|
-
if (scope.modelKey) params.set('modelKey', scope.modelKey)
|
|
1277
|
-
if (cursor) params.set('cursor', cursor)
|
|
1278
|
-
return fetch('/api/all-usage/records?' + params.toString(), { headers: { accept: 'application/json' } }).then((r) => {
|
|
1279
|
-
if (!r.ok) { const error = new Error('HTTP ' + r.status); error.status = r.status; throw error }
|
|
1280
|
-
return r.json()
|
|
1281
|
-
})
|
|
1282
|
-
}
|
|
1283
|
-
const getBalance = (force, requestToken) => fetch('/api/all-usage/balance' + (force ? '?force=1' : ''), { headers: { accept: 'application/json', 'x-all-usage-request-token': requestToken } }).then((r) => {
|
|
1284
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1285
|
-
return r.json()
|
|
1286
|
-
})
|
|
1287
|
-
const setAliasRpc = (workspaceId, alias, writeToken) => fetch('/api/all-usage/alias', {
|
|
1288
|
-
method: 'POST',
|
|
1289
|
-
headers: { 'content-type': 'application/json', 'x-all-usage-request-token': writeToken },
|
|
1290
|
-
body: JSON.stringify({ workspaceId, alias }),
|
|
1291
|
-
}).then((r) => {
|
|
1292
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1293
|
-
return r.json()
|
|
1294
|
-
})
|
|
1295
|
-
const getPricing = () => fetch('/api/all-usage/pricing', { headers: { accept: 'application/json' } }).then((r) => {
|
|
1296
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1297
|
-
return r.json()
|
|
1298
|
-
})
|
|
1299
|
-
const getPricingModels = (query) => {
|
|
1300
|
-
const params = new URLSearchParams({ q: String(query || '').slice(0, 120), limit: '30' })
|
|
1301
|
-
return fetch('/api/all-usage/pricing/models?' + params.toString(), { headers: { accept: 'application/json' } }).then((r) => {
|
|
1302
|
-
if (!r.ok) throw new Error('HTTP ' + r.status)
|
|
1303
|
-
return r.json()
|
|
1304
|
-
})
|
|
1305
|
-
}
|
|
1306
|
-
const setPricingRpc = (pricing, backfill, writeToken) => fetch('/api/all-usage/pricing', {
|
|
1307
|
-
method: 'POST',
|
|
1308
|
-
headers: { 'content-type': 'application/json', 'x-all-usage-request-token': writeToken },
|
|
1309
|
-
body: JSON.stringify({ pricing, backfill: backfill === true }),
|
|
1310
|
-
}).then((r) => {
|
|
1311
|
-
if (!r.ok) { const error = new Error('HTTP ' + r.status); error.status = r.status; throw error }
|
|
1312
|
-
return r.json()
|
|
1313
|
-
})
|
|
1314
|
-
const syncPricingRpc = (writeToken) => fetch('/api/all-usage/pricing/sync', {
|
|
1315
|
-
method: 'POST',
|
|
1316
|
-
headers: { 'content-type': 'application/json', 'x-all-usage-request-token': writeToken },
|
|
1317
|
-
body: '{}',
|
|
1318
|
-
}).then((r) => {
|
|
1319
|
-
if (!r.ok) { const error = new Error('HTTP ' + r.status); error.status = r.status; throw error }
|
|
1320
|
-
return r.json()
|
|
1321
|
-
})
|
|
1322
|
-
|
|
1323
|
-
const LANGUAGE_STORAGE_KEY = 'dsh-all-usage.language'
|
|
1324
|
-
function storedLanguage() {
|
|
1325
|
-
try { return window.localStorage.getItem(LANGUAGE_STORAGE_KEY) === 'en' ? 'en' : 'zh' } catch (_) { return 'zh' }
|
|
1326
|
-
}
|
|
1327
|
-
function persistLanguage(language) {
|
|
1328
|
-
try { window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language) } catch (_) {}
|
|
1329
|
-
}
|
|
1330
|
-
const USAGE_UI_STATE_KEY = 'dsh-all-usage.ui-state'
|
|
1331
|
-
function storedUsageUiState() {
|
|
1332
|
-
try {
|
|
1333
|
-
const raw = window.localStorage.getItem(USAGE_UI_STATE_KEY)
|
|
1334
|
-
const value = raw ? JSON.parse(raw) : {}
|
|
1335
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
|
|
1336
|
-
const state = {}
|
|
1337
|
-
if (['logs', 'model', 'workspace'].includes(value.detailView)) state.detailView = value.detailView
|
|
1338
|
-
if (['route', 'model', 'provider'].includes(value.modelView)) state.modelView = value.modelView
|
|
1339
|
-
if (['today', '30d', '90d', 'all'].includes(value.range)) state.range = value.range
|
|
1340
|
-
if (typeof value.pricingAutoSync === 'boolean') state.pricingAutoSync = value.pricingAutoSync
|
|
1341
|
-
return state
|
|
1342
|
-
} catch (_) { return {} }
|
|
1343
|
-
}
|
|
1344
|
-
function persistUsageUiState(patch) {
|
|
1345
|
-
try {
|
|
1346
|
-
const current = storedUsageUiState()
|
|
1347
|
-
window.localStorage.setItem(USAGE_UI_STATE_KEY, JSON.stringify(Object.assign({}, current, patch)))
|
|
1348
|
-
} catch (_) {}
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
function UsagePage(props) {
|
|
1352
|
-
const timer = props.timerCtx
|
|
1353
|
-
const language = props.language === 'en' ? 'en' : 'zh'
|
|
1354
|
-
const tr = (zh, en) => language === 'en' ? en : zh
|
|
1355
|
-
const useUtc = language === 'en'
|
|
1356
|
-
const usageUiStateRef = React.useRef(null)
|
|
1357
|
-
if (usageUiStateRef.current === null) usageUiStateRef.current = storedUsageUiState()
|
|
1358
|
-
const usageUiState = usageUiStateRef.current
|
|
1359
|
-
const calendarNow = new Date()
|
|
1360
|
-
const latestCalendarDate = fmtDate(calendarNow, useUtc)
|
|
1361
|
-
const [stats, setStats] = React.useState(null)
|
|
1362
|
-
const [status, setStatus] = React.useState(null)
|
|
1363
|
-
const [statsError, setStatsError] = React.useState('')
|
|
1364
|
-
const [lastStatsAt, setLastStatsAt] = React.useState(0)
|
|
1365
|
-
const [balance, setBalance] = React.useState(null)
|
|
1366
|
-
const [range, setRange] = React.useState(() => usageUiState.range || 'today')
|
|
1367
|
-
const [customRange, setCustomRange] = React.useState({ start: '', end: '' })
|
|
1368
|
-
const [customDraft, setCustomDraft] = React.useState({ start: '', end: '' })
|
|
1369
|
-
const [customRangeOpen, setCustomRangeOpen] = React.useState(false)
|
|
1370
|
-
const [modelView, setModelView] = React.useState(() => usageUiState.modelView || 'route')
|
|
1371
|
-
const [wsFilter, setWsFilter] = React.useState(null)
|
|
1372
|
-
const [providerFilter, setProviderFilter] = React.useState(null)
|
|
1373
|
-
const [modelFilter, setModelFilter] = React.useState(null)
|
|
1374
|
-
const [queryResult, setQueryResult] = React.useState(null)
|
|
1375
|
-
const [queryResultKey, setQueryResultKey] = React.useState('')
|
|
1376
|
-
const [queryLoading, setQueryLoading] = React.useState(false)
|
|
1377
|
-
const [queryError, setQueryError] = React.useState('')
|
|
1378
|
-
const [trendVisible, setTrendVisible] = React.useState(['total', 'input', 'cacheRead', 'output'])
|
|
1379
|
-
const [detailView, setDetailView] = React.useState(() => usageUiState.detailView || 'logs')
|
|
1380
|
-
const [detailSelection, setDetailSelection] = React.useState(null)
|
|
1381
|
-
const [auditSelectedId, setAuditSelectedId] = React.useState(null)
|
|
1382
|
-
const [auditRows, setAuditRows] = React.useState([])
|
|
1383
|
-
const [auditCursor, setAuditCursor] = React.useState(null)
|
|
1384
|
-
const [auditHasMore, setAuditHasMore] = React.useState(false)
|
|
1385
|
-
const [auditReload, setAuditReload] = React.useState(0)
|
|
1386
|
-
const [auditLoading, setAuditLoading] = React.useState(false)
|
|
1387
|
-
const [auditExporting, setAuditExporting] = React.useState(false)
|
|
1388
|
-
const [auditError, setAuditError] = React.useState('')
|
|
1389
|
-
const [hover, setHover] = React.useState(null)
|
|
1390
|
-
const [aliasOpen, setAliasOpen] = React.useState(false)
|
|
1391
|
-
const [aliasDrafts, setAliasDrafts] = React.useState({})
|
|
1392
|
-
const [pricingOpen, setPricingOpen] = React.useState(false)
|
|
1393
|
-
const [pricingDraft, setPricingDraft] = React.useState(null)
|
|
1394
|
-
const [pricingSaving, setPricingSaving] = React.useState(false)
|
|
1395
|
-
const [pricingSyncing, setPricingSyncing] = React.useState(false)
|
|
1396
|
-
const [pricingSyncSaving, setPricingSyncSaving] = React.useState(false)
|
|
1397
|
-
const [pricingError, setPricingError] = React.useState('')
|
|
1398
|
-
const [pricingModelSearchOptions, setPricingModelSearchOptions] = React.useState({})
|
|
1399
|
-
const [pricingModelSearchOpen, setPricingModelSearchOpen] = React.useState(null)
|
|
1400
|
-
const [pricingUsedModelSearchText, setPricingUsedModelSearchText] = React.useState({})
|
|
1401
|
-
const [pricingUsedModelOpen, setPricingUsedModelOpen] = React.useState(null)
|
|
1402
|
-
const [pricingOverrideSearchText, setPricingOverrideSearchText] = React.useState({})
|
|
1403
|
-
const [pricingOverrideOpen, setPricingOverrideOpen] = React.useState(null)
|
|
1404
|
-
const pricingModelSearchSeqRef = React.useRef({})
|
|
1405
|
-
const pricingModelSearchTimerRef = React.useRef({})
|
|
1406
|
-
const [languageMenuOpen, setLanguageMenuOpen] = React.useState(false)
|
|
1407
|
-
const languageMenuRef = React.useRef(null)
|
|
1408
|
-
const recordsPanelRef = React.useRef(null)
|
|
1409
|
-
const statsGateRef = React.useRef(null)
|
|
1410
|
-
if (statsGateRef.current === null) statsGateRef.current = createRequestGate()
|
|
1411
|
-
const statusGateRef = React.useRef(null)
|
|
1412
|
-
if (statusGateRef.current === null) statusGateRef.current = createRequestGate()
|
|
1413
|
-
const balanceGateRef = React.useRef(null)
|
|
1414
|
-
if (balanceGateRef.current === null) balanceGateRef.current = createRequestGate()
|
|
1415
|
-
const queryGateRef = React.useRef(null)
|
|
1416
|
-
if (queryGateRef.current === null) queryGateRef.current = createRequestGate()
|
|
1417
|
-
const recordsGateRef = React.useRef(null)
|
|
1418
|
-
if (recordsGateRef.current === null) recordsGateRef.current = createRequestGate()
|
|
1419
|
-
const statsGate = statsGateRef.current
|
|
1420
|
-
const statusGate = statusGateRef.current
|
|
1421
|
-
const balanceGate = balanceGateRef.current
|
|
1422
|
-
const queryGate = queryGateRef.current
|
|
1423
|
-
const recordsGate = recordsGateRef.current
|
|
1424
|
-
const refreshRef = React.useRef(() => {})
|
|
1425
|
-
const setLanguage = (next) => { if (typeof props.onLanguageChange === 'function') props.onLanguageChange(next === 'en' ? 'en' : 'zh') }
|
|
1426
|
-
const chooseLanguage = (next) => { setLanguage(next); setLanguageMenuOpen(false) }
|
|
1427
|
-
React.useEffect(() => { persistUsageUiState({ detailView }) }, [detailView])
|
|
1428
|
-
React.useEffect(() => { persistUsageUiState({ modelView }) }, [modelView])
|
|
1429
|
-
React.useEffect(() => { if (range !== 'custom') persistUsageUiState({ range }) }, [range])
|
|
1430
|
-
|
|
1431
|
-
const queryScope = stats === null ? null : makeUsageScope(stats, range, useUtc, customRange, wsFilter, providerFilter, modelFilter)
|
|
1432
|
-
const queryKey = usageScopeKey(queryScope)
|
|
1433
|
-
const selectedDetailScope = detailSelection !== null && detailSelection.baseKey === queryKey ? detailSelection.scope : queryScope
|
|
1434
|
-
const detailKey = usageScopeKey(selectedDetailScope)
|
|
1435
|
-
const previousQueryKeyRef = React.useRef(queryKey)
|
|
1436
|
-
React.useEffect(() => {
|
|
1437
|
-
if (previousQueryKeyRef.current !== queryKey) {
|
|
1438
|
-
previousQueryKeyRef.current = queryKey
|
|
1439
|
-
setDetailSelection(null)
|
|
1440
|
-
setAuditSelectedId(null)
|
|
1441
|
-
}
|
|
1442
|
-
}, [queryKey])
|
|
1443
|
-
|
|
1444
|
-
React.useEffect(() => {
|
|
1445
|
-
let alive = true
|
|
1446
|
-
let scanDone = false
|
|
1447
|
-
let requestToken = ''
|
|
1448
|
-
let appliedSnapshot = null
|
|
1449
|
-
let fullFailures = 0
|
|
1450
|
-
let statusFailures = 0
|
|
1451
|
-
let retryTimer = null
|
|
1452
|
-
const clearRetry = () => {
|
|
1453
|
-
if (retryTimer !== null) {
|
|
1454
|
-
clearTimeout(retryTimer)
|
|
1455
|
-
retryTimer = null
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
const refreshBalance = (force) => {
|
|
1459
|
-
if (requestToken === '') return
|
|
1460
|
-
const seq = balanceGate.next()
|
|
1461
|
-
getBalance(force === true, requestToken).then((data) => {
|
|
1462
|
-
if (!alive || !balanceGate.isCurrent(seq)) return
|
|
1463
|
-
if (data) setBalance(data)
|
|
1464
|
-
}, () => {})
|
|
1465
|
-
}
|
|
1466
|
-
const scheduleRetry = (kind) => {
|
|
1467
|
-
if (!alive || retryTimer !== null) return
|
|
1468
|
-
const failures = kind === 'full' ? (fullFailures += 1) : (statusFailures += 1)
|
|
1469
|
-
retryTimer = setTimeout(() => {
|
|
1470
|
-
retryTimer = null
|
|
1471
|
-
if (!alive) return
|
|
1472
|
-
if (kind === 'full') refreshStats()
|
|
1473
|
-
else refreshStatus()
|
|
1474
|
-
}, retryDelayFor(failures))
|
|
1475
|
-
}
|
|
1476
|
-
const refreshStats = () => {
|
|
1477
|
-
statusGate.next()
|
|
1478
|
-
const seq = statsGate.next()
|
|
1479
|
-
getStats().then((data) => {
|
|
1480
|
-
if (!alive || !statsGate.isCurrent(seq)) return
|
|
1481
|
-
if (data === null || typeof data !== 'object') {
|
|
1482
|
-
setStatsError('full')
|
|
1483
|
-
scheduleRetry('full')
|
|
1484
|
-
return
|
|
1485
|
-
}
|
|
1486
|
-
appliedSnapshot = data
|
|
1487
|
-
if (data.scan) scanDone = !!data.scan.done
|
|
1488
|
-
const nextToken = typeof data.requestToken === 'string' ? data.requestToken : ''
|
|
1489
|
-
const tokenChanged = nextToken !== '' && nextToken !== requestToken
|
|
1490
|
-
requestToken = nextToken
|
|
1491
|
-
fullFailures = 0
|
|
1492
|
-
clearRetry()
|
|
1493
|
-
setStatsError('')
|
|
1494
|
-
setLastStatsAt(Date.now())
|
|
1495
|
-
setStatus(data)
|
|
1496
|
-
setStats(data)
|
|
1497
|
-
if (tokenChanged) refreshBalance(false)
|
|
1498
|
-
}, () => {
|
|
1499
|
-
if (!alive || !statsGate.isCurrent(seq)) return
|
|
1500
|
-
setStatsError('full')
|
|
1501
|
-
scheduleRetry('full')
|
|
1502
|
-
})
|
|
1503
|
-
}
|
|
1504
|
-
const refreshStatus = () => {
|
|
1505
|
-
if (appliedSnapshot === null) { refreshStats(); return }
|
|
1506
|
-
const seq = statusGate.next()
|
|
1507
|
-
getStatus().then((data) => {
|
|
1508
|
-
if (!alive || !statusGate.isCurrent(seq)) return
|
|
1509
|
-
if (data === null || typeof data !== 'object') {
|
|
1510
|
-
setStatsError('status')
|
|
1511
|
-
scheduleRetry('status')
|
|
1512
|
-
return
|
|
1513
|
-
}
|
|
1514
|
-
statusFailures = 0
|
|
1515
|
-
const requiresFullSnapshot = statusRequiresFullSnapshot(data, appliedSnapshot)
|
|
1516
|
-
if (data.scan) scanDone = !!data.scan.done
|
|
1517
|
-
// Do not render a newer status beside an older full snapshot. A full
|
|
1518
|
-
// fetch owns the state transition when instance/revision diverges.
|
|
1519
|
-
if (requiresFullSnapshot) {
|
|
1520
|
-
refreshStats()
|
|
1521
|
-
return
|
|
1522
|
-
}
|
|
1523
|
-
setStatus(data)
|
|
1524
|
-
clearRetry()
|
|
1525
|
-
setStatsError('')
|
|
1526
|
-
}, () => {
|
|
1527
|
-
if (!alive || !statusGate.isCurrent(seq)) return
|
|
1528
|
-
setStatsError('status')
|
|
1529
|
-
scheduleRetry('status')
|
|
1530
|
-
})
|
|
1531
|
-
}
|
|
1532
|
-
refreshStats()
|
|
1533
|
-
const fast = timer.interval(() => { if (!scanDone && retryTimer === null) refreshStats() }, 2000)
|
|
1534
|
-
const slow = timer.interval(() => { if (scanDone && retryTimer === null) refreshStatus() }, 15000)
|
|
1535
|
-
const bal = timer.interval(() => { refreshBalance(false) }, 60000)
|
|
1536
|
-
refreshRef.current = () => {
|
|
1537
|
-
clearRetry()
|
|
1538
|
-
fullFailures = 0
|
|
1539
|
-
statusFailures = 0
|
|
1540
|
-
refreshStats()
|
|
1541
|
-
refreshBalance(true)
|
|
1542
|
-
}
|
|
1543
|
-
return () => {
|
|
1544
|
-
alive = false
|
|
1545
|
-
clearRetry()
|
|
1546
|
-
fast(); slow(); bal()
|
|
1547
|
-
for (const timerId of Object.values(pricingModelSearchTimerRef.current)) clearTimeout(timerId)
|
|
1548
|
-
pricingModelSearchTimerRef.current = {}
|
|
1549
|
-
}
|
|
1550
|
-
}, [])
|
|
1551
|
-
|
|
1552
|
-
React.useEffect(() => {
|
|
1553
|
-
if (!languageMenuOpen || typeof document === 'undefined') return undefined
|
|
1554
|
-
const closeLanguageMenu = (event) => {
|
|
1555
|
-
if (languageMenuRef.current && !languageMenuRef.current.contains(event.target)) setLanguageMenuOpen(false)
|
|
1556
|
-
}
|
|
1557
|
-
document.addEventListener('pointerdown', closeLanguageMenu)
|
|
1558
|
-
return () => document.removeEventListener('pointerdown', closeLanguageMenu)
|
|
1559
|
-
}, [languageMenuOpen])
|
|
1560
|
-
|
|
1561
|
-
React.useEffect(() => {
|
|
1562
|
-
if (queryScope === null || queryKey === '') return undefined
|
|
1563
|
-
const seq = queryGate.next()
|
|
1564
|
-
setQueryLoading(true)
|
|
1565
|
-
setQueryError('')
|
|
1566
|
-
getUsageQuery(queryScope).then((data) => {
|
|
1567
|
-
if (!queryGate.isCurrent(seq)) return
|
|
1568
|
-
if (data === null || typeof data !== 'object' || data.revision !== (stats && stats.revision)) {
|
|
1569
|
-
setQueryError('stale')
|
|
1570
|
-
setQueryLoading(false)
|
|
1571
|
-
return
|
|
1572
|
-
}
|
|
1573
|
-
setQueryResult(data)
|
|
1574
|
-
setQueryResultKey(queryKey)
|
|
1575
|
-
setQueryLoading(false)
|
|
1576
|
-
setQueryError('')
|
|
1577
|
-
}, () => {
|
|
1578
|
-
if (!queryGate.isCurrent(seq)) return
|
|
1579
|
-
setQueryLoading(false)
|
|
1580
|
-
setQueryError('query')
|
|
1581
|
-
})
|
|
1582
|
-
return undefined
|
|
1583
|
-
}, [queryKey, stats && stats.revision])
|
|
1584
|
-
|
|
1585
|
-
const openAuditForScope = (scope) => {
|
|
1586
|
-
if (scope === null || queryKey === '') return
|
|
1587
|
-
setDetailSelection({ baseKey: queryKey, scope: { ...scope } })
|
|
1588
|
-
setDetailView('logs')
|
|
1589
|
-
setAuditSelectedId(null)
|
|
1590
|
-
setAuditError('')
|
|
1591
|
-
}
|
|
1592
|
-
const openAuditForDate = (date) => {
|
|
1593
|
-
if (queryScope === null || typeof date !== 'string') return
|
|
1594
|
-
openAuditForScope({ ...queryScope, start: date, end: date })
|
|
1595
|
-
}
|
|
1596
|
-
React.useEffect(() => {
|
|
1597
|
-
if (selectedDetailScope === null || detailKey === '') return undefined
|
|
1598
|
-
const seq = recordsGate.next()
|
|
1599
|
-
setAuditLoading(true)
|
|
1600
|
-
setAuditError('')
|
|
1601
|
-
setAuditRows([])
|
|
1602
|
-
setAuditSelectedId(null)
|
|
1603
|
-
setAuditCursor(null)
|
|
1604
|
-
setAuditHasMore(false)
|
|
1605
|
-
getUsageRecords(selectedDetailScope, null, 20).then((data) => {
|
|
1606
|
-
if (!recordsGate.isCurrent(seq)) return
|
|
1607
|
-
if (data === null || typeof data !== 'object' || !Array.isArray(data.items)) {
|
|
1608
|
-
setAuditError('audit')
|
|
1609
|
-
setAuditLoading(false)
|
|
1610
|
-
return
|
|
1611
|
-
}
|
|
1612
|
-
setAuditRows(data.items)
|
|
1613
|
-
setAuditSelectedId(data.items[0] ? data.items[0].id : null)
|
|
1614
|
-
setAuditCursor(data.nextCursor || null)
|
|
1615
|
-
setAuditHasMore(data.hasMore === true)
|
|
1616
|
-
setAuditLoading(false)
|
|
1617
|
-
setAuditError('')
|
|
1618
|
-
}, (reason) => {
|
|
1619
|
-
if (!recordsGate.isCurrent(seq)) return
|
|
1620
|
-
if (reason && reason.status === 409) {
|
|
1621
|
-
setAuditError('stale')
|
|
1622
|
-
setAuditReload((value) => value + 1)
|
|
1623
|
-
return
|
|
1624
|
-
}
|
|
1625
|
-
setAuditLoading(false)
|
|
1626
|
-
setAuditError('audit')
|
|
1627
|
-
})
|
|
1628
|
-
return undefined
|
|
1629
|
-
}, [detailKey, stats && stats.revision, auditReload])
|
|
1630
|
-
const loadMoreAudit = () => {
|
|
1631
|
-
if (selectedDetailScope === null || auditCursor === null || auditLoading) return
|
|
1632
|
-
const seq = recordsGate.next()
|
|
1633
|
-
setAuditLoading(true)
|
|
1634
|
-
getUsageRecords(selectedDetailScope, auditCursor, 20).then((data) => {
|
|
1635
|
-
if (!recordsGate.isCurrent(seq)) return
|
|
1636
|
-
if (data === null || typeof data !== 'object' || !Array.isArray(data.items)) {
|
|
1637
|
-
setAuditError('audit')
|
|
1638
|
-
setAuditLoading(false)
|
|
1639
|
-
return
|
|
1640
|
-
}
|
|
1641
|
-
setAuditRows((prev) => prev.concat(data.items))
|
|
1642
|
-
setAuditCursor(data.nextCursor || null)
|
|
1643
|
-
setAuditHasMore(data.hasMore === true)
|
|
1644
|
-
setAuditLoading(false)
|
|
1645
|
-
setAuditError('')
|
|
1646
|
-
}, (reason) => {
|
|
1647
|
-
if (!recordsGate.isCurrent(seq)) return
|
|
1648
|
-
if (reason && reason.status === 409) {
|
|
1649
|
-
setAuditRows([])
|
|
1650
|
-
setAuditSelectedId(null)
|
|
1651
|
-
setAuditCursor(null)
|
|
1652
|
-
setAuditHasMore(false)
|
|
1653
|
-
setAuditError('stale')
|
|
1654
|
-
setAuditReload((value) => value + 1)
|
|
1655
|
-
return
|
|
1656
|
-
}
|
|
1657
|
-
setAuditLoading(false)
|
|
1658
|
-
setAuditError('audit')
|
|
1659
|
-
})
|
|
1660
|
-
}
|
|
1661
|
-
React.useEffect(() => {
|
|
1662
|
-
if (detailSelection === null || detailView !== 'logs' || recordsPanelRef.current === null) return undefined
|
|
1663
|
-
recordsPanelRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
1664
|
-
return undefined
|
|
1665
|
-
}, [detailSelection && usageScopeKey(detailSelection.scope), detailView])
|
|
1666
|
-
const onRefresh = () => { refreshRef.current() }
|
|
1667
|
-
const toggleFilter = (id) => {
|
|
1668
|
-
setWsFilter((prev) => (prev === id ? null : id))
|
|
1669
|
-
}
|
|
1670
|
-
const clearFilters = () => {
|
|
1671
|
-
setWsFilter(null)
|
|
1672
|
-
setProviderFilter(null)
|
|
1673
|
-
setModelFilter(null)
|
|
1674
|
-
}
|
|
1675
|
-
const chooseProvider = (value) => {
|
|
1676
|
-
setProviderFilter(value || null)
|
|
1677
|
-
}
|
|
1678
|
-
const chooseModel = (value) => {
|
|
1679
|
-
setModelFilter(value || null)
|
|
1680
|
-
}
|
|
1681
|
-
const activeDayRows = useUtc && Array.isArray(stats && stats.byDayUtc) ? stats.byDayUtc : (Array.isArray(stats && stats.byDay) ? stats.byDay : [])
|
|
1682
|
-
const availableDateRange = availableDateBounds(activeDayRows, latestCalendarDate)
|
|
1683
|
-
const earliestAvailableDate = availableDateRange.min
|
|
1684
|
-
const activeCustomRange = normalizeCustomRange(customRange, useUtc)
|
|
1685
|
-
const customDraftIssue = customRangeIssue(customDraft, earliestAvailableDate, latestCalendarDate, useUtc)
|
|
1686
|
-
const openCustomRange = () => {
|
|
1687
|
-
const current = normalizeCustomRange(customRange, useUtc)
|
|
1688
|
-
const defaultStart = fmtDate(shiftCalendarDate(calendarNow, -89, useUtc), useUtc)
|
|
1689
|
-
setCustomDraft(current || { start: defaultStart < earliestAvailableDate ? earliestAvailableDate : defaultStart, end: latestCalendarDate })
|
|
1690
|
-
setCustomRangeOpen(true)
|
|
1691
|
-
}
|
|
1692
|
-
const applyCustomRange = () => {
|
|
1693
|
-
if (customDraftIssue !== '') return
|
|
1694
|
-
const next = normalizeCustomRange(customDraft, useUtc)
|
|
1695
|
-
if (next === null) return
|
|
1696
|
-
setCustomRange(next)
|
|
1697
|
-
setRange('custom')
|
|
1698
|
-
setCustomRangeOpen(false)
|
|
1699
|
-
}
|
|
1700
|
-
const chooseRange = (next) => {
|
|
1701
|
-
setRange(next)
|
|
1702
|
-
setCustomRangeOpen(false)
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
const queryReady = queryResult !== null && queryResultKey === queryKey && queryResult.revision === (stats && stats.revision)
|
|
1706
|
-
const displayedDays = queryReady && Array.isArray(queryResult.daily) ? queryResult.daily : activeDayRows
|
|
1707
|
-
const displayedHeatmap = queryReady && Array.isArray(queryResult.heatmap) ? queryResult.heatmap : activeDayRows
|
|
1708
|
-
const activeCustomRangeKey = activeCustomRange === null ? '' : activeCustomRange.start + ':' + activeCustomRange.end
|
|
1709
|
-
const rangeOnlyAgg = React.useMemo(() => rangeAgg(stats, range, useUtc, activeCustomRange), [stats, range, useUtc, activeCustomRangeKey])
|
|
1710
|
-
const agg = React.useMemo(() => queryReady
|
|
1711
|
-
? { totals: queryResult.totals, perWs: queryResult.perWorkspace || [], perModel: queryResult.perModel || [] }
|
|
1712
|
-
: rangeOnlyAgg, [queryReady, queryResult, rangeOnlyAgg])
|
|
1713
|
-
const animatedTotal = useCountUp(agg.totals.input + agg.totals.output + agg.totals.cacheRead + agg.totals.cacheWrite + agg.totals.reasoning, timer)
|
|
1714
|
-
const animatedRate = useCountUp(Math.round(rateOf(agg.totals.input, agg.totals.cacheRead) * 10), timer)
|
|
1715
|
-
const scopedCountIsCalls = providerFilter !== null || modelFilter !== null
|
|
1716
|
-
const requestCount = Number.isFinite(agg.totals.calls) && agg.totals.calls > 0 ? agg.totals.calls : agg.totals.turns
|
|
1717
|
-
const displayedCount = scopedCountIsCalls ? requestCount : agg.totals.turns
|
|
1718
|
-
const animatedTurns = useCountUp(displayedCount, timer)
|
|
1719
|
-
const animatedRequests = useCountUp(requestCount, timer)
|
|
1720
|
-
const wsTotal = (w) => w.input + w.output + w.cacheRead + w.cacheWrite + w.reasoning
|
|
1721
|
-
const rows = React.useMemo(() => (Array.isArray(agg.perWs) ? agg.perWs : []).slice().sort((a, b) => wsTotal(b) - wsTotal(a)), [agg.perWs])
|
|
1722
|
-
const modelRows = React.useMemo(() => aggregateModelRows(agg.perModel || [], modelView, tr('未知供应商', 'Unknown provider'), tr('未知模型', 'Unknown model')).sort((a, b) => wsTotal(b) - wsTotal(a)), [agg.perModel, modelView, language])
|
|
1723
|
-
const rangeModelOptions = Array.isArray(rangeOnlyAgg.perModel) ? rangeOnlyAgg.perModel.filter((row) => row && typeof row === 'object') : []
|
|
1724
|
-
const providerOptions = Array.from(new Set(rangeModelOptions.map((row) => typeof row.provider === 'string' && row.provider !== '' ? row.provider : null).filter((value) => value !== null))).sort()
|
|
1725
|
-
const modelFilterValue = (row) => {
|
|
1726
|
-
const structured = typeof row.actualModel === 'string' && row.actualModel !== '' ? row.actualModel : typeof row.requestedModel === 'string' && row.requestedModel !== '' ? row.requestedModel : null
|
|
1727
|
-
if (structured !== null) return structured
|
|
1728
|
-
const display = typeof row.model === 'string' && row.model !== '' ? row.model : tr('未知模型', 'Unknown model')
|
|
1729
|
-
const separator = display.indexOf(' / ')
|
|
1730
|
-
const legacyProvider = separator > 0 ? display.slice(0, separator) : ''
|
|
1731
|
-
return separator > 0 && providerOptions.includes(legacyProvider) ? display.slice(separator + 3) : display
|
|
1732
|
-
}
|
|
1733
|
-
const modelOptions = Array.from(new Set(rangeModelOptions.map(modelFilterValue))).sort((a, b) => a.localeCompare(b))
|
|
1734
|
-
const workspaces = stats && Array.isArray(stats.workspaces) ? stats.workspaces : []
|
|
1735
|
-
const rangeWorkspaceTotals = new Map((Array.isArray(rangeOnlyAgg.perWs) ? rangeOnlyAgg.perWs : []).map((row) => [row.workspaceId, row]))
|
|
1736
|
-
const workspaceHasUsage = (row) => row !== undefined && (Number(row.turns) > 0 || Number(row.calls) > 0 || Number(row.input) > 0 || Number(row.output) > 0 || Number(row.cacheRead) > 0 || Number(row.cacheWrite) > 0 || Number(row.reasoning) > 0)
|
|
1737
|
-
const rangeWorkspaceOptions = workspaces.filter((workspace) => workspaceHasUsage(rangeWorkspaceTotals.get(workspace.id)))
|
|
1738
|
-
const rangeWorkspaceIds = new Set(rangeWorkspaceOptions.map((workspace) => workspace.id))
|
|
1739
|
-
React.useEffect(() => {
|
|
1740
|
-
if (wsFilter !== null && !rangeWorkspaceIds.has(wsFilter)) setWsFilter(null)
|
|
1741
|
-
if (providerFilter !== null && !providerOptions.includes(providerFilter)) setProviderFilter(null)
|
|
1742
|
-
if (modelFilter !== null && !modelOptions.includes(modelFilter)) setModelFilter(null)
|
|
1743
|
-
}, [wsFilter, providerFilter, modelFilter, Array.from(rangeWorkspaceIds).sort().join('\0'), providerOptions.join('\0'), modelOptions.join('\0')])
|
|
1744
|
-
|
|
1745
|
-
if (stats === null) {
|
|
1746
|
-
const failed = statsError !== ''
|
|
1747
|
-
return React.createElement('div', { className: 'uh-page' },
|
|
1748
|
-
React.createElement('div', { className: 'uh-panel' },
|
|
1749
|
-
React.createElement('div', { className: 'uh-empty' }, failed
|
|
1750
|
-
? tr('无法加载用量统计。请重试。', 'Unable to load usage statistics. Please retry.')
|
|
1751
|
-
: tr('正在加载用量统计…', 'Loading usage statistics…'),
|
|
1752
|
-
),
|
|
1753
|
-
failed ? React.createElement('div', { style: { textAlign: 'center' } },
|
|
1754
|
-
React.createElement('button', { className: 'uh-refresh', onClick: onRefresh }, React.createElement(LineIcon, { name: 'refresh', size: 14 }), tr('重试', 'Retry')),
|
|
1755
|
-
) : null,
|
|
1756
|
-
),
|
|
1757
|
-
)
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
const statusPayload = status !== null && typeof status === 'object' ? status : stats
|
|
1761
|
-
const scan = statusPayload && statusPayload.scan ? statusPayload.scan : (stats.scan || { done: true, started: true, scanned: 0, total: 0, failed: 0 })
|
|
1762
|
-
const sync = statusPayload && statusPayload.sync ? statusPayload.sync : (stats.sync || {})
|
|
1763
|
-
const aliases = stats.aliases && typeof stats.aliases === 'object' ? stats.aliases : {}
|
|
1764
|
-
const wsById = new Map()
|
|
1765
|
-
const wsIndex = new Map()
|
|
1766
|
-
workspaces.forEach((w, i) => { wsById.set(w.id, w); wsIndex.set(w.id, i) })
|
|
1767
|
-
const dayRows = displayedDays
|
|
1768
|
-
const heatmapMap = new Map()
|
|
1769
|
-
for (const d of displayedHeatmap) heatmapMap.set(d.date, d)
|
|
1770
|
-
const dayMap = new Map()
|
|
1771
|
-
for (const d of activeDayRows) dayMap.set(d.date, d)
|
|
1772
|
-
const wsTitle = (id) => {
|
|
1773
|
-
const alias = aliases[id]
|
|
1774
|
-
if (typeof alias === 'string' && alias !== '') return alias
|
|
1775
|
-
const meta = wsById.get(id)
|
|
1776
|
-
return meta ? meta.title : tr('未知工作区', 'Unknown workspace')
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
const saveAlias = (wsId, value) => {
|
|
1780
|
-
const requestToken = typeof stats.requestToken === 'string' ? stats.requestToken : ''
|
|
1781
|
-
if (requestToken === '') return
|
|
1782
|
-
setAliasRpc(wsId, String(value === undefined ? '' : value).trim(), requestToken).then((res) => {
|
|
1783
|
-
if (res && res.ok && res.aliases) {
|
|
1784
|
-
setStats((prev) => (prev === null ? prev : Object.assign({}, prev, { aliases: res.aliases })))
|
|
1785
|
-
}
|
|
1786
|
-
}, () => {})
|
|
1787
|
-
}
|
|
1788
|
-
const openAliasPanel = () => {
|
|
1789
|
-
const drafts = {}
|
|
1790
|
-
workspaces.forEach((w) => { drafts[w.id] = typeof aliases[w.id] === 'string' ? aliases[w.id] : '' })
|
|
1791
|
-
setAliasDrafts(drafts)
|
|
1792
|
-
setAliasOpen(true)
|
|
1793
|
-
}
|
|
1794
|
-
const saveAllAliases = () => {
|
|
1795
|
-
for (const id of Object.keys(aliasDrafts)) {
|
|
1796
|
-
const current = typeof aliases[id] === 'string' ? aliases[id] : ''
|
|
1797
|
-
if (aliasDrafts[id] !== current) saveAlias(id, aliasDrafts[id])
|
|
1798
|
-
}
|
|
1799
|
-
setAliasOpen(false)
|
|
1800
|
-
}
|
|
1801
|
-
const currentPricing = stats.pricing && typeof stats.pricing === 'object' ? stats.pricing : {}
|
|
1802
|
-
const pricingUsedModels = Array.isArray(currentPricing.usedModels) ? currentPricing.usedModels : []
|
|
1803
|
-
const pricingUsedModelOptions = pricingUsedModels.slice().sort((left, right) => {
|
|
1804
|
-
const rank = { unpriced: 0, ambiguous: 1, unsupported: 2, priced: 3 }
|
|
1805
|
-
return (rank[left.status] === undefined ? 9 : rank[left.status]) - (rank[right.status] === undefined ? 9 : rank[right.status]) || String(left.model || '').localeCompare(String(right.model || ''))
|
|
1806
|
-
}).map((model) => ({
|
|
1807
|
-
value: String(model.identityKey || model.model || ''),
|
|
1808
|
-
label: (model.model || tr('未知模型', 'Unknown model')) + ' · ' + pricingStatusLabel(model.status, language),
|
|
1809
|
-
model: pricingModelKey(model.actualModel || model.requestedModel || model.pricingModel),
|
|
1810
|
-
officialModel: pricingModelKey(model.pricingModel),
|
|
1811
|
-
})).filter((option) => option.value !== '')
|
|
1812
|
-
const openPricingPanel = () => {
|
|
1813
|
-
const draft = pricingDraftOf(currentPricing)
|
|
1814
|
-
const uiState = storedUsageUiState()
|
|
1815
|
-
if (typeof uiState.pricingAutoSync === 'boolean') draft.sync.autoEnabled = uiState.pricingAutoSync
|
|
1816
|
-
setPricingDraft(draft)
|
|
1817
|
-
setPricingUsedModelSearchText({})
|
|
1818
|
-
setPricingOverrideSearchText({})
|
|
1819
|
-
setPricingUsedModelOpen(null)
|
|
1820
|
-
setPricingOverrideOpen(null)
|
|
1821
|
-
setPricingModelSearchOptions({})
|
|
1822
|
-
setPricingModelSearchOpen(null)
|
|
1823
|
-
setPricingError('')
|
|
1824
|
-
setPricingOpen(true)
|
|
1825
|
-
setAliasOpen(false)
|
|
1826
|
-
}
|
|
1827
|
-
const savePricingSettings = (backfill) => {
|
|
1828
|
-
if (pricingDraft === null || pricingSaving || pricingSyncing || pricingSyncSaving) return
|
|
1829
|
-
const requestToken = typeof stats.requestToken === 'string' ? stats.requestToken : ''
|
|
1830
|
-
if (requestToken === '') { setPricingError('token'); return }
|
|
1831
|
-
setPricingSaving(true)
|
|
1832
|
-
setPricingError('')
|
|
1833
|
-
setPricingRpc(pricingDraft, backfill, requestToken).then((data) => {
|
|
1834
|
-
if (!data || data.ok !== true || !data.pricing) { setPricingError('save'); return }
|
|
1835
|
-
setStats((prev) => prev === null ? prev : Object.assign({}, prev, { pricing: data.pricing }))
|
|
1836
|
-
setPricingDraft(pricingDraftOf(data.pricing))
|
|
1837
|
-
setPricingUsedModelSearchText({})
|
|
1838
|
-
setPricingOverrideSearchText({})
|
|
1839
|
-
setPricingUsedModelOpen(null)
|
|
1840
|
-
setPricingOverrideOpen(null)
|
|
1841
|
-
setPricingOpen(false)
|
|
1842
|
-
refreshRef.current()
|
|
1843
|
-
}, (reason) => { setPricingError(reason && reason.status === 403 ? 'forbidden' : 'save') }).finally(() => setPricingSaving(false))
|
|
1844
|
-
}
|
|
1845
|
-
const syncPricingNow = () => {
|
|
1846
|
-
if (pricingSaving || pricingSyncing || pricingSyncSaving) return
|
|
1847
|
-
const requestToken = typeof stats.requestToken === 'string' ? stats.requestToken : ''
|
|
1848
|
-
if (requestToken === '') { setPricingError('token'); return }
|
|
1849
|
-
setPricingSyncing(true)
|
|
1850
|
-
setPricingError('')
|
|
1851
|
-
syncPricingRpc(requestToken).then((data) => {
|
|
1852
|
-
if (!data || data.ok !== true || !data.pricing) { setPricingError('sync'); return }
|
|
1853
|
-
setStats((prev) => prev === null ? prev : Object.assign({}, prev, { pricing: data.pricing }))
|
|
1854
|
-
setPricingDraft(pricingDraftOf(data.pricing))
|
|
1855
|
-
setPricingUsedModelSearchText({})
|
|
1856
|
-
setPricingOverrideSearchText({})
|
|
1857
|
-
setPricingUsedModelOpen(null)
|
|
1858
|
-
setPricingOverrideOpen(null)
|
|
1859
|
-
refreshRef.current()
|
|
1860
|
-
}, (reason) => { setPricingError(reason && reason.status === 403 ? 'forbidden' : 'sync') }).finally(() => setPricingSyncing(false))
|
|
1861
|
-
}
|
|
1862
|
-
const updatePricingSync = (enabled) => {
|
|
1863
|
-
if (pricingDraft === null || pricingSyncSaving || pricingSaving || pricingSyncing) return
|
|
1864
|
-
const nextEnabled = enabled === true
|
|
1865
|
-
const previousEnabled = pricingDraft.sync && pricingDraft.sync.autoEnabled === true
|
|
1866
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { sync: Object.assign({}, prev.sync, { autoEnabled: nextEnabled }) }))
|
|
1867
|
-
persistUsageUiState({ pricingAutoSync: nextEnabled })
|
|
1868
|
-
const requestToken = typeof stats.requestToken === 'string' ? stats.requestToken : ''
|
|
1869
|
-
if (requestToken === '') {
|
|
1870
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { sync: Object.assign({}, prev.sync, { autoEnabled: previousEnabled }) }))
|
|
1871
|
-
persistUsageUiState({ pricingAutoSync: previousEnabled })
|
|
1872
|
-
setPricingError('token')
|
|
1873
|
-
return
|
|
1874
|
-
}
|
|
1875
|
-
const rollback = (error) => {
|
|
1876
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { sync: Object.assign({}, prev.sync, { autoEnabled: previousEnabled }) }))
|
|
1877
|
-
persistUsageUiState({ pricingAutoSync: previousEnabled })
|
|
1878
|
-
setPricingError(error)
|
|
1879
|
-
}
|
|
1880
|
-
setPricingSyncSaving(true)
|
|
1881
|
-
setPricingError('')
|
|
1882
|
-
setPricingRpc({ sync: { autoEnabled: nextEnabled } }, false, requestToken).then((data) => {
|
|
1883
|
-
if (!data || data.ok !== true || !data.pricing) { rollback('save'); return }
|
|
1884
|
-
const savedEnabled = data.pricing.sync && data.pricing.sync.autoEnabled === true
|
|
1885
|
-
setStats((prev) => prev === null ? prev : Object.assign({}, prev, { pricing: data.pricing }))
|
|
1886
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { sync: Object.assign({}, prev.sync, { autoEnabled: savedEnabled, intervalMs: data.pricing.sync && data.pricing.sync.intervalMs }) }))
|
|
1887
|
-
persistUsageUiState({ pricingAutoSync: savedEnabled })
|
|
1888
|
-
}, (reason) => rollback(reason && reason.status === 403 ? 'forbidden' : 'save')).finally(() => setPricingSyncSaving(false))
|
|
1889
|
-
}
|
|
1890
|
-
const updatePricingMapping = (index, field, value) => {
|
|
1891
|
-
setPricingDraft((prev) => {
|
|
1892
|
-
if (prev === null || !Array.isArray(prev.mappings) || !prev.mappings[index]) return prev
|
|
1893
|
-
const mappings = prev.mappings.slice()
|
|
1894
|
-
mappings[index] = Object.assign({}, mappings[index], { [field]: value })
|
|
1895
|
-
return Object.assign({}, prev, { mappings })
|
|
1896
|
-
})
|
|
1897
|
-
}
|
|
1898
|
-
const selectPricingUsedModel = (index, value) => {
|
|
1899
|
-
const selected = pricingUsedModels.find((model) => String(model.identityKey || model.model || '') === String(value))
|
|
1900
|
-
if (!selected) return
|
|
1901
|
-
const modelAlias = selected.actualModel || selected.requestedModel || selected.pricingModel || ''
|
|
1902
|
-
const officialModel = selected.status === 'priced' ? (selected.pricingModel || '') : ''
|
|
1903
|
-
setPricingDraft((prev) => {
|
|
1904
|
-
if (prev === null || !Array.isArray(prev.mappings) || !prev.mappings[index]) return prev
|
|
1905
|
-
const mappings = prev.mappings.slice()
|
|
1906
|
-
mappings[index] = Object.assign({}, mappings[index], { usageIdentityKey: value, model: modelAlias, catalogModelId: officialModel, catalogProviderId: selected.providerId || '' })
|
|
1907
|
-
return Object.assign({}, prev, { mappings })
|
|
1908
|
-
})
|
|
1909
|
-
setPricingUsedModelSearchText((prev) => Object.assign({}, prev, { [index]: selected.model || modelAlias }))
|
|
1910
|
-
setPricingUsedModelOpen(null)
|
|
1911
|
-
setPricingModelSearchOpen(null)
|
|
1912
|
-
}
|
|
1913
|
-
const searchUsedModels = (index, value) => {
|
|
1914
|
-
setPricingUsedModelSearchText((prev) => Object.assign({}, prev, { [index]: value }))
|
|
1915
|
-
setPricingUsedModelOpen(index)
|
|
1916
|
-
setPricingDraft((prev) => {
|
|
1917
|
-
if (prev === null || !Array.isArray(prev.mappings) || !prev.mappings[index]) return prev
|
|
1918
|
-
const mappings = prev.mappings.slice()
|
|
1919
|
-
mappings[index] = Object.assign({}, mappings[index], { usageIdentityKey: '', model: '', catalogModelId: '', catalogProviderId: '' })
|
|
1920
|
-
return Object.assign({}, prev, { mappings })
|
|
1921
|
-
})
|
|
1922
|
-
}
|
|
1923
|
-
const searchOfficialModels = (index, value) => {
|
|
1924
|
-
updatePricingMapping(index, 'catalogModelId', value)
|
|
1925
|
-
setPricingModelSearchOpen(index)
|
|
1926
|
-
const previousTimer = pricingModelSearchTimerRef.current[index]
|
|
1927
|
-
if (previousTimer !== undefined) {
|
|
1928
|
-
clearTimeout(previousTimer)
|
|
1929
|
-
delete pricingModelSearchTimerRef.current[index]
|
|
1930
|
-
}
|
|
1931
|
-
const nextSeq = (pricingModelSearchSeqRef.current[index] || 0) + 1
|
|
1932
|
-
pricingModelSearchSeqRef.current[index] = nextSeq
|
|
1933
|
-
if (String(value || '').trim() === '') {
|
|
1934
|
-
setPricingModelSearchOptions((prev) => Object.assign({}, prev, { [index]: [] }))
|
|
1935
|
-
return
|
|
1936
|
-
}
|
|
1937
|
-
const timerId = setTimeout(() => {
|
|
1938
|
-
delete pricingModelSearchTimerRef.current[index]
|
|
1939
|
-
getPricingModels(value).then((data) => {
|
|
1940
|
-
if (pricingModelSearchSeqRef.current[index] !== nextSeq) return
|
|
1941
|
-
setPricingModelSearchOptions((prev) => Object.assign({}, prev, { [index]: Array.isArray(data && data.items) ? data.items : [] }))
|
|
1942
|
-
}, () => {
|
|
1943
|
-
if (pricingModelSearchSeqRef.current[index] === nextSeq) setPricingModelSearchOptions((prev) => Object.assign({}, prev, { [index]: [] }))
|
|
1944
|
-
})
|
|
1945
|
-
}, 180)
|
|
1946
|
-
pricingModelSearchTimerRef.current[index] = timerId
|
|
1947
|
-
}
|
|
1948
|
-
const chooseOfficialModel = (index, option) => {
|
|
1949
|
-
if (!option || typeof option.value !== 'string') return
|
|
1950
|
-
const pendingTimer = pricingModelSearchTimerRef.current[index]
|
|
1951
|
-
if (pendingTimer !== undefined) {
|
|
1952
|
-
clearTimeout(pendingTimer)
|
|
1953
|
-
delete pricingModelSearchTimerRef.current[index]
|
|
1954
|
-
}
|
|
1955
|
-
pricingModelSearchSeqRef.current[index] = (pricingModelSearchSeqRef.current[index] || 0) + 1
|
|
1956
|
-
setPricingDraft((prev) => {
|
|
1957
|
-
if (prev === null || !Array.isArray(prev.mappings) || !prev.mappings[index]) return prev
|
|
1958
|
-
const mappings = prev.mappings.slice()
|
|
1959
|
-
mappings[index] = Object.assign({}, mappings[index], { catalogModelId: option.value, catalogProviderId: option.providerId || '' })
|
|
1960
|
-
return Object.assign({}, prev, { mappings })
|
|
1961
|
-
})
|
|
1962
|
-
setPricingModelSearchOpen(null)
|
|
1963
|
-
}
|
|
1964
|
-
const addPricingMapping = () => {
|
|
1965
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { mappings: prev.mappings.concat([{ usageIdentityKey: '', model: '', catalogProviderId: '', catalogModelId: '', inputTokenSemantics: 'fresh', multiplier: '1' }]) }))
|
|
1966
|
-
}
|
|
1967
|
-
const removePricingMapping = (index) => {
|
|
1968
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { mappings: prev.mappings.filter((_, itemIndex) => itemIndex !== index) }))
|
|
1969
|
-
}
|
|
1970
|
-
const updatePricingOverride = (index, field, value) => {
|
|
1971
|
-
setPricingDraft((prev) => {
|
|
1972
|
-
if (prev === null || !Array.isArray(prev.overrides) || !prev.overrides[index]) return prev
|
|
1973
|
-
const overrides = prev.overrides.slice()
|
|
1974
|
-
overrides[index] = Object.assign({}, overrides[index], { [field]: value })
|
|
1975
|
-
return Object.assign({}, prev, { overrides })
|
|
1976
|
-
})
|
|
1977
|
-
}
|
|
1978
|
-
const selectPricingOverrideModel = (index, value) => {
|
|
1979
|
-
const selected = pricingUsedModels.find((model) => String(model.identityKey || model.model || '') === String(value))
|
|
1980
|
-
if (!selected) return
|
|
1981
|
-
const modelId = selected.pricingModel || selected.actualModel || selected.requestedModel || ''
|
|
1982
|
-
setPricingDraft((prev) => {
|
|
1983
|
-
if (prev === null || !Array.isArray(prev.overrides) || !prev.overrides[index]) return prev
|
|
1984
|
-
const overrides = prev.overrides.slice()
|
|
1985
|
-
overrides[index] = Object.assign({}, overrides[index], { modelId })
|
|
1986
|
-
return Object.assign({}, prev, { overrides })
|
|
1987
|
-
})
|
|
1988
|
-
setPricingOverrideSearchText((prev) => Object.assign({}, prev, { [index]: modelId }))
|
|
1989
|
-
setPricingOverrideOpen(null)
|
|
1990
|
-
}
|
|
1991
|
-
const searchPricingOverrideModels = (index, value) => {
|
|
1992
|
-
setPricingOverrideSearchText((prev) => Object.assign({}, prev, { [index]: value }))
|
|
1993
|
-
setPricingOverrideOpen(index)
|
|
1994
|
-
updatePricingOverride(index, 'modelId', value)
|
|
1995
|
-
}
|
|
1996
|
-
const addPricingOverride = () => {
|
|
1997
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { overrides: prev.overrides.concat([{ providerId: '', modelId: '', displayName: '', input: '', output: '', cacheRead: '', cacheWrite: '' }]) }))
|
|
1998
|
-
}
|
|
1999
|
-
const removePricingOverride = (index) => {
|
|
2000
|
-
setPricingDraft((prev) => prev === null ? prev : Object.assign({}, prev, { overrides: prev.overrides.filter((_, itemIndex) => itemIndex !== index) }))
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
const totalTokens = agg.totals.input + agg.totals.output + agg.totals.cacheRead + agg.totals.cacheWrite + agg.totals.reasoning
|
|
2004
|
-
const cacheRate = rateOf(agg.totals.input, agg.totals.cacheRead)
|
|
2005
|
-
const scopedCost = costAggregate(agg.totals)
|
|
2006
|
-
const costValue = costDisplay(agg.totals, language)
|
|
2007
|
-
const costCoverage = costCoverageLabel(agg.totals, language)
|
|
2008
|
-
const st = streaks(dayMap, useUtc)
|
|
2009
|
-
|
|
2010
|
-
const today = calendarNow
|
|
2011
|
-
const todayKey = latestCalendarDate
|
|
2012
|
-
const todayWeekday = useUtc ? today.getUTCDay() : today.getDay()
|
|
2013
|
-
const sunday = shiftCalendarDate(today, -todayWeekday, useUtc)
|
|
2014
|
-
const start = shiftCalendarDate(sunday, -52 * 7, useUtc)
|
|
2015
|
-
const cells = []
|
|
2016
|
-
for (let i = 0; i < 53 * 7; i++) {
|
|
2017
|
-
const d = shiftCalendarDate(start, i, useUtc)
|
|
2018
|
-
cells.push({ date: fmtDate(d, useUtc), month: useUtc ? d.getUTCMonth() : d.getMonth(), year: useUtc ? d.getUTCFullYear() : d.getFullYear() })
|
|
2019
|
-
}
|
|
2020
|
-
const monthLabels = []
|
|
2021
|
-
for (let j = 0; j < 53; j++) {
|
|
2022
|
-
const first = cells[j * 7]
|
|
2023
|
-
const prev = j > 0 ? cells[(j - 1) * 7] : null
|
|
2024
|
-
if (prev === null || first.month !== prev.month) {
|
|
2025
|
-
monthLabels.push({ left: (j * 100 / 53) + '%', text: monthLabel(first.year, first.month, language) })
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
const weekdayLabels = language === 'en' ? ['', 'Mon', '', 'Wed', '', 'Fri', ''] : ['', '周一', '', '周三', '', '周五', '']
|
|
2029
|
-
|
|
2030
|
-
const onEnter = (cell, ev) => {
|
|
2031
|
-
setHover({ date: cell.date, x: ev.clientX, y: ev.clientY, day: heatmapMap.get(cell.date) })
|
|
2032
|
-
}
|
|
2033
|
-
const onMove = (cell, ev) => {
|
|
2034
|
-
setHover((prev) => (prev !== null && prev.date === cell.date ? { date: prev.date, x: ev.clientX, y: ev.clientY, day: prev.day } : prev))
|
|
2035
|
-
}
|
|
2036
|
-
const onLeave = () => setHover(null)
|
|
2037
|
-
|
|
2038
|
-
const cellElements = cells.map((cell, i) => {
|
|
2039
|
-
const day = heatmapMap.get(cell.date)
|
|
2040
|
-
let count = 0
|
|
2041
|
-
if (day !== undefined) {
|
|
2042
|
-
if (queryReady || wsFilter === null) count = day.turns
|
|
2043
|
-
else {
|
|
2044
|
-
const w = Array.isArray(day.perWorkspace) ? day.perWorkspace.find((x) => x.workspaceId === wsFilter) : undefined
|
|
2045
|
-
if (w !== undefined) count = w.turns
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
const level = levelOf(count)
|
|
2049
|
-
const dim = wsFilter !== null && day !== undefined && day.turns > 0 && count === 0
|
|
2050
|
-
const isToday = cell.date === todayKey
|
|
2051
|
-
const style = {
|
|
2052
|
-
background: cellBg(level),
|
|
2053
|
-
opacity: dim ? 0.22 : 1,
|
|
2054
|
-
animationDelay: (i * 1.2) + 'ms',
|
|
2055
|
-
}
|
|
2056
|
-
if (isToday) style.animation = 'uh-cell-in .45s ease both, uh-glow 3s ease-in-out .7s infinite'
|
|
2057
|
-
return React.createElement('div', {
|
|
2058
|
-
key: cell.date,
|
|
2059
|
-
className: 'uh-cell',
|
|
2060
|
-
style,
|
|
2061
|
-
onMouseEnter: (ev) => onEnter(cell, ev),
|
|
2062
|
-
onMouseMove: (ev) => onMove(cell, ev),
|
|
2063
|
-
onMouseLeave: onLeave,
|
|
2064
|
-
onClick: () => openAuditForDate(cell.date),
|
|
2065
|
-
})
|
|
2066
|
-
})
|
|
2067
|
-
|
|
2068
|
-
let balanceValue = '—'
|
|
2069
|
-
let balanceSub = tr('查询中…', 'Checking…')
|
|
2070
|
-
if (balance !== null && balance !== undefined) {
|
|
2071
|
-
if (balance.status === 'missing-key') {
|
|
2072
|
-
balanceValue = tr('未配置', 'Not configured')
|
|
2073
|
-
balanceSub = tr('在 设置 → 模型 中填写 DeepSeek API Key 后可见', 'Available after you enter a DeepSeek API key in Settings → Models')
|
|
2074
|
-
} else if (balance.status === 'unavailable') {
|
|
2075
|
-
balanceValue = tr('不可用', 'Unavailable')
|
|
2076
|
-
balanceSub = balance.message || tr('DeepSeek 接口返回余额不可用', 'The DeepSeek API reported that balance information is unavailable')
|
|
2077
|
-
} else if (balance.status === 'error') {
|
|
2078
|
-
balanceValue = tr('查询失败', 'Lookup failed')
|
|
2079
|
-
const detail = balance.detail ? (language === 'en' ? ' (' + String(balance.detail).slice(0, 90) + ')' : '(' + String(balance.detail).slice(0, 90) + ')') : ''
|
|
2080
|
-
balanceSub = (balance.message || '') + detail + tr(' 点“刷新”重试', ' Click Refresh to try again')
|
|
2081
|
-
} else if (balance.status === 'ok' && Array.isArray(balance.currencies) && balance.currencies.length > 0) {
|
|
2082
|
-
const list = balance.currencies
|
|
2083
|
-
const primary = list.find((c) => c.currency === 'CNY') || list[0]
|
|
2084
|
-
const others = list.filter((c) => c !== primary)
|
|
2085
|
-
balanceValue = money(primary.currency, primary.total, language)
|
|
2086
|
-
let sub = primary.total !== null ? tr('赠送 ', 'Granted ') + money(primary.currency, primary.granted, language) + ' · ' + tr('充值 ', 'Top-up ') + money(primary.currency, primary.toppedUp, language) : ''
|
|
2087
|
-
if (others.length > 0) sub += (sub ? ' | ' : '') + others.map((c) => money(c.currency, c.total, language)).join(' ')
|
|
2088
|
-
balanceSub = sub
|
|
2089
|
-
} else {
|
|
2090
|
-
balanceValue = tr('无数据', 'No data')
|
|
2091
|
-
balanceSub = ''
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
const card = (label, value, sub, delay, icon) => React.createElement('div', { className: 'uh-card', style: { animationDelay: (delay * 70) + 'ms' } },
|
|
2096
|
-
React.createElement('div', { className: 'uh-card-label' }, icon ? React.createElement(LineIcon, { name: icon, size: 14 }) : null, label),
|
|
2097
|
-
React.createElement('div', { className: 'uh-card-value' }, value),
|
|
2098
|
-
React.createElement('div', { className: 'uh-card-sub' }, sub),
|
|
2099
|
-
)
|
|
2100
|
-
const summaryRateMetric = React.createElement('div', { className: 'uh-ios-metric uh-ios-metric-rate', style: { animationDelay: '280ms' } },
|
|
2101
|
-
React.createElement('div', { className: 'uh-ios-metric-rate-head' },
|
|
2102
|
-
React.createElement('div', { className: 'uh-ios-metric-label' }, React.createElement(LineIcon, { name: 'cache', size: 18 }), tr('缓存命中率', 'Cache Hit Rate')),
|
|
2103
|
-
React.createElement('div', { className: 'uh-ios-metric-rate-value' }, (cacheRate).toFixed(1) + '%'),
|
|
2104
|
-
),
|
|
2105
|
-
React.createElement('div', { className: 'uh-ios-metric-bar' }, React.createElement('div', { className: 'uh-ios-metric-fill', style: { width: Math.max(0, Math.min(100, cacheRate)) + '%' } })),
|
|
2106
|
-
React.createElement('div', { className: 'uh-ios-metric-rate-detail' }, language === 'en' ? 'Context reused ' + fmtCompact(agg.totals.cacheRead) + ' tokens' : '复用上下文 ' + fmtCompact(agg.totals.cacheRead) + ' Token'),
|
|
2107
|
-
)
|
|
2108
|
-
|
|
2109
|
-
const maxTotal = rows.length > 0 ? wsTotal(rows[0]) : 0
|
|
2110
|
-
|
|
2111
|
-
const tokenCardRows = rows.slice(0, 3).map((w) => {
|
|
2112
|
-
const total = wsTotal(w)
|
|
2113
|
-
const idx = wsIndex.get(w.workspaceId)
|
|
2114
|
-
const color = wsColor(idx === undefined ? 0 : idx)
|
|
2115
|
-
const selected = wsFilter === w.workspaceId
|
|
2116
|
-
return React.createElement('div', {
|
|
2117
|
-
key: w.workspaceId,
|
|
2118
|
-
className: 'uh-wsbar' + (selected ? ' uh-sel' : ''),
|
|
2119
|
-
onClick: () => toggleFilter(w.workspaceId),
|
|
2120
|
-
},
|
|
2121
|
-
React.createElement('div', { className: 'uh-wsbar-top' },
|
|
2122
|
-
React.createElement('span', { className: 'uh-dot', style: { background: color } }),
|
|
2123
|
-
React.createElement('span', { className: 'uh-wsbar-title' }, wsTitle(w.workspaceId)),
|
|
2124
|
-
React.createElement('span', { className: 'uh-wsbar-num' }, valueWithMagnitude(fmtCompact(total), total, language)),
|
|
2125
|
-
),
|
|
2126
|
-
React.createElement('div', { className: 'uh-barwrap uh-bar-thin' },
|
|
2127
|
-
React.createElement('div', { className: 'uh-barfill', style: { width: maxTotal > 0 ? Math.max(2, (total / maxTotal) * 100) + '%' : '0%', background: color } }),
|
|
2128
|
-
),
|
|
2129
|
-
)
|
|
2130
|
-
})
|
|
2131
|
-
const tokenCard = React.createElement('div', { className: 'uh-card', style: { animationDelay: '210ms' } },
|
|
2132
|
-
React.createElement('div', { className: 'uh-card-label' }, React.createElement(LineIcon, { name: 'folder', size: 14 }), tr('各工作区总处理量', 'Total Tokens Processed by Workspace')),
|
|
2133
|
-
rows.length === 0
|
|
2134
|
-
? React.createElement('div', { className: 'uh-empty', style: { padding: '8px 0' } }, tr('暂无数据', 'No data yet'))
|
|
2135
|
-
: React.createElement('div', { className: 'uh-wsbars' },
|
|
2136
|
-
tokenCardRows,
|
|
2137
|
-
rows.length > 3 ? React.createElement('div', { className: 'uh-card-sub' }, language === 'en' ? 'See the details table for the other ' + (rows.length - 3) + ' workspaces' : '其余 ' + (rows.length - 3) + ' 个工作区见明细表') : null,
|
|
2138
|
-
),
|
|
2139
|
-
)
|
|
2140
|
-
|
|
2141
|
-
const rowElements = rows.map((w) => {
|
|
2142
|
-
const meta = wsById.get(w.workspaceId)
|
|
2143
|
-
const alias = typeof aliases[w.workspaceId] === 'string' ? aliases[w.workspaceId] : ''
|
|
2144
|
-
const folderTitle = meta ? meta.title : tr('未知工作区', 'Unknown workspace')
|
|
2145
|
-
const path = meta ? meta.path : ''
|
|
2146
|
-
const title = alias !== '' ? alias : folderTitle
|
|
2147
|
-
const subText = alias !== '' ? folderTitle + ' · ' + path : path
|
|
2148
|
-
const total = wsTotal(w)
|
|
2149
|
-
const rate = rateOf(w.input, w.cacheRead)
|
|
2150
|
-
const idx = wsIndex.get(w.workspaceId)
|
|
2151
|
-
const color = wsColor(idx === undefined ? 0 : idx)
|
|
2152
|
-
const selected = wsFilter === w.workspaceId
|
|
2153
|
-
return React.createElement('div', {
|
|
2154
|
-
key: w.workspaceId,
|
|
2155
|
-
className: 'uh-row' + (selected ? ' uh-sel' : ''),
|
|
2156
|
-
onClick: () => toggleFilter(w.workspaceId),
|
|
2157
|
-
},
|
|
2158
|
-
React.createElement('div', { className: 'uh-row-title-wrap' },
|
|
2159
|
-
React.createElement('div', { className: 'uh-ws-title' }, title),
|
|
2160
|
-
React.createElement('div', { className: 'uh-ws-path' }, subText),
|
|
2161
|
-
),
|
|
2162
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(w.turns)),
|
|
2163
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(w.input)),
|
|
2164
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(w.cacheRead)),
|
|
2165
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(w.output)),
|
|
2166
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(w.reasoning)),
|
|
2167
|
-
React.createElement('div', {},
|
|
2168
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(total)),
|
|
2169
|
-
React.createElement('div', { className: 'uh-barwrap' },
|
|
2170
|
-
React.createElement('div', { className: 'uh-barfill', style: { width: maxTotal > 0 ? Math.max(2, (total / maxTotal) * 100) + '%' : '0%', background: color } }),
|
|
2171
|
-
),
|
|
2172
|
-
),
|
|
2173
|
-
React.createElement('div', { className: 'uh-num uh-cost-num' }, costDisplay(w, language)),
|
|
2174
|
-
React.createElement('div', { className: 'uh-num' }, rate.toFixed(1) + '%'),
|
|
2175
|
-
React.createElement('div', { className: 'uh-num' }, maxTotal > 0 ? ((total / maxTotal) * 100).toFixed(0) + '%' : '0%'),
|
|
2176
|
-
)
|
|
2177
|
-
})
|
|
2178
|
-
|
|
2179
|
-
const modelViewLabel = modelView === 'route' ? tr('混合查看', 'Combined View') : modelView === 'model' ? tr('按模型合并', 'Grouped by Model') : tr('按供应商汇总', 'Grouped by Provider')
|
|
2180
|
-
const modelColumnLabel = modelView === 'route' ? tr('供应商 / 模型', 'Provider / Model') : modelView === 'model' ? tr('模型', 'Model') : tr('供应商', 'Provider')
|
|
2181
|
-
const modelDonutChart = detailView !== 'model' || modelRows.length === 0 ? null : React.createElement(UsageDonutChart, {
|
|
2182
|
-
key: 'model-donut-' + detailView + ':' + queryKey + ':' + stats.revision + ':' + modelView,
|
|
2183
|
-
title: modelView === 'provider' ? tr('供应商用量', 'Provider Usage') : tr('模型用量', 'Model Usage'),
|
|
2184
|
-
icon: 'chart',
|
|
2185
|
-
language,
|
|
2186
|
-
items: modelRows.map((row, index) => ({ label: row.model, value: wsTotal(row), cost: row.cost, color: DONUT_COLORS[index % DONUT_COLORS.length] })),
|
|
2187
|
-
})
|
|
2188
|
-
const workspaceDonutChart = detailView !== 'workspace' || rows.length === 0 ? null : React.createElement(UsageDonutChart, {
|
|
2189
|
-
key: 'workspace-donut-' + detailView + ':' + queryKey + ':' + stats.revision,
|
|
2190
|
-
title: tr('工作区用量', 'Workspace Usage'),
|
|
2191
|
-
icon: 'folder',
|
|
2192
|
-
language,
|
|
2193
|
-
items: rows.map((row, index) => ({ label: wsTitle(row.workspaceId), value: wsTotal(row), cost: row.cost, color: DONUT_COLORS[index % DONUT_COLORS.length] })),
|
|
2194
|
-
})
|
|
2195
|
-
const exportCsv = () => {
|
|
2196
|
-
const quote = (value) => '"' + String(value === undefined || value === null ? '' : value).replace(/"/g, '""') + '"'
|
|
2197
|
-
const line = (values) => values.map(quote).join(',')
|
|
2198
|
-
const allTokens = (entry) => entry.input + entry.output + entry.cacheRead + entry.cacheWrite + entry.reasoning
|
|
2199
|
-
const tokenHeaders = [tr('输入 Token', 'Input Tokens'), tr('缓存命中 Token', 'Cache-Hit Tokens'), tr('缓存写入 Token', 'Cache-Write Tokens'), tr('输出 Token', 'Output Tokens'), tr('推理 Token', 'Reasoning Tokens'), tr('总处理 Token', 'Total Tokens Processed'), tr('成本', 'Cost'), tr('缓存命中率', 'Cache Hit Rate')]
|
|
2200
|
-
const output = [
|
|
2201
|
-
line([tr('DSH 用量统计导出', 'DSH Usage Statistics Export')]),
|
|
2202
|
-
line([tr('导出时间', 'Exported At'), useUtc ? new Date().toLocaleString('en-US', { timeZone: 'UTC', timeZoneName: 'short' }) : new Date().toLocaleString('zh-CN')]),
|
|
2203
|
-
line([tr('时间范围', 'Time Range'), rangeLabel]),
|
|
2204
|
-
line([tr('时区', 'Timezone'), useUtc ? 'UTC' : tr('本地', 'Local')]),
|
|
2205
|
-
line([tr('工作区筛选', 'Workspace Filter'), wsFilter || tr('全部', 'All')]),
|
|
2206
|
-
line([tr('供应商筛选', 'Provider Filter'), providerFilter || tr('全部', 'All')]),
|
|
2207
|
-
line([tr('模型筛选', 'Model Filter'), modelFilter || tr('全部', 'All')]),
|
|
2208
|
-
line([tr('统计 revision', 'Stats Revision'), stats.revision || '']),
|
|
2209
|
-
line([tr('模型查看模式', 'Model View Mode'), modelViewLabel]),
|
|
2210
|
-
'',
|
|
2211
|
-
line([tr('汇总', 'Summary')]),
|
|
2212
|
-
line([tr('回合', 'Turns'), tr('会话', 'Sessions'), ...tokenHeaders]),
|
|
2213
|
-
line([agg.totals.turns, agg.totals.sessions, agg.totals.input, agg.totals.cacheRead, agg.totals.cacheWrite, agg.totals.output, agg.totals.reasoning, allTokens(agg.totals), costDisplay(agg.totals, language), rateOf(agg.totals.input, agg.totals.cacheRead).toFixed(2) + '%']),
|
|
2214
|
-
'',
|
|
2215
|
-
line([tr('模型用量明细', 'Model Usage Details')]),
|
|
2216
|
-
line([modelColumnLabel, tr('调用', 'Calls'), ...tokenHeaders]),
|
|
2217
|
-
...modelRows.map((m) => line([m.model, m.calls, m.input, m.cacheRead, m.cacheWrite, m.output, m.reasoning, allTokens(m), costDisplay(m, language), rateOf(m.input, m.cacheRead).toFixed(2) + '%'])),
|
|
2218
|
-
'',
|
|
2219
|
-
line([tr('工作区明细', 'Workspace Details')]),
|
|
2220
|
-
line([tr('工作区', 'Workspace'), tr('路径', 'Path'), tr('回合', 'Turns'), ...tokenHeaders]),
|
|
2221
|
-
...rows.map((w) => { const meta = wsById.get(w.workspaceId); return line([wsTitle(w.workspaceId), meta ? meta.path : '', w.turns, w.input, w.cacheRead, w.cacheWrite, w.output, w.reasoning, allTokens(w), costDisplay(w, language), rateOf(w.input, w.cacheRead).toFixed(2) + '%']) }),
|
|
2222
|
-
]
|
|
2223
|
-
const blob = new Blob(['\uFEFF' + output.join('\r\n')], { type: 'text/csv;charset=utf-8' })
|
|
2224
|
-
const url = URL.createObjectURL(blob)
|
|
2225
|
-
const anchor = document.createElement('a')
|
|
2226
|
-
anchor.href = url
|
|
2227
|
-
anchor.download = 'dsh-all-usage-' + rangeFilePart + '-' + modelView + '-' + fmtDate(new Date(), useUtc) + '.csv'
|
|
2228
|
-
document.body.appendChild(anchor); anchor.click(); anchor.remove()
|
|
2229
|
-
URL.revokeObjectURL(url)
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
|
-
const modelElements = detailView === 'model' ? modelRows.map((m) => {
|
|
2233
|
-
const total = wsTotal(m)
|
|
2234
|
-
const rate = rateOf(m.input, m.cacheRead)
|
|
2235
|
-
return React.createElement('div', { key: m.identityKey || m.model, className: 'uh-model-row uh-row' },
|
|
2236
|
-
React.createElement('div', { className: 'uh-row-title-wrap' },
|
|
2237
|
-
React.createElement('div', { className: 'uh-ws-title', title: m.model }, m.model),
|
|
2238
|
-
),
|
|
2239
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(m.calls)),
|
|
2240
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(m.input)),
|
|
2241
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(m.cacheRead)),
|
|
2242
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(m.output)),
|
|
2243
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(m.reasoning)),
|
|
2244
|
-
React.createElement('div', { className: 'uh-num' }, fmtCompact(total)),
|
|
2245
|
-
React.createElement('div', { className: 'uh-num uh-cost-num' }, costDisplay(m, language)),
|
|
2246
|
-
React.createElement('div', { className: 'uh-num' }, rate.toFixed(1) + '%'),
|
|
2247
|
-
)
|
|
2248
|
-
}) : []
|
|
2249
|
-
|
|
2250
|
-
const aliasPanel = aliasOpen
|
|
2251
|
-
? React.createElement('div', { className: 'uh-panel uh-anim-panel' },
|
|
2252
|
-
React.createElement('div', { className: 'uh-alias-panel-head' },
|
|
2253
|
-
React.createElement('span', {}, tr('工作区别名', 'Workspace Aliases')),
|
|
2254
|
-
React.createElement('button', { className: 'uh-alias-close', onClick: () => setAliasOpen(false) }, tr('关闭', 'Close')),
|
|
2255
|
-
),
|
|
2256
|
-
workspaces.length === 0
|
|
2257
|
-
? React.createElement('div', { className: 'uh-empty', style: { padding: '10px 0' } }, tr('暂无工作区', 'No workspaces yet'))
|
|
2258
|
-
: React.createElement('div', { className: 'uh-alias-list' },
|
|
2259
|
-
workspaces.map((w, i) => React.createElement('div', { key: w.id, className: 'uh-alias-item' },
|
|
2260
|
-
React.createElement('span', { className: 'uh-dot', style: { background: wsColor(i) } }),
|
|
2261
|
-
React.createElement('span', { className: 'uh-alias-folder', title: w.path }, w.title || w.path),
|
|
2262
|
-
React.createElement('input', {
|
|
2263
|
-
className: 'uh-alias-input',
|
|
2264
|
-
value: aliasDrafts[w.id] !== undefined ? aliasDrafts[w.id] : '',
|
|
2265
|
-
placeholder: tr('项目别名', 'Project alias'),
|
|
2266
|
-
onChange: (e) => setAliasDrafts((prev) => Object.assign({}, prev, { [w.id]: e.target.value })),
|
|
2267
|
-
onKeyDown: (e) => { if (e.key === 'Enter') saveAlias(w.id, e.target.value) },
|
|
2268
|
-
}),
|
|
2269
|
-
)),
|
|
2270
|
-
),
|
|
2271
|
-
React.createElement('div', { className: 'uh-alias-panel-foot' },
|
|
2272
|
-
React.createElement('span', { className: 'uh-note' }, tr('回车保存单个;清空别名还原文件夹名', 'Press Enter to save one; clear an alias to restore the folder name')),
|
|
2273
|
-
React.createElement('button', { className: 'uh-alias-ok', onClick: saveAllAliases }, tr('全部保存', 'Save All')),
|
|
2274
|
-
),
|
|
2275
|
-
)
|
|
2276
|
-
: null
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
const pricingSync = currentPricing.sync && typeof currentPricing.sync === 'object' ? currentPricing.sync : {}
|
|
2280
|
-
const pricingPanel = pricingOpen && pricingDraft !== null ? React.createElement('div', { className: 'uh-panel uh-pricing-panel uh-anim-panel' },
|
|
2281
|
-
React.createElement('div', { className: 'uh-pricing-head' },
|
|
2282
|
-
React.createElement('div', { className: 'uh-title-with-icon' }, React.createElement(LineIcon, { name: 'wallet', size: 16 }), React.createElement('strong', {}, tr('成本统计设置', 'Cost Statistics'))),
|
|
2283
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh uh-icon-button', title: tr('关闭成本设置', 'Close cost settings'), 'aria-label': tr('关闭成本设置', 'Close cost settings'), onClick: () => setPricingOpen(false) }, React.createElement(LineIcon, { name: 'close', size: 16 })),
|
|
2284
|
-
),
|
|
2285
|
-
React.createElement('div', { className: 'uh-pricing-note' }, tr('价格来自 models.dev 的公开目录,单位为 USD / 1M Token。已保存的历史成本不会因目录更新重算;同步只回填当前未计价的调用。', 'Prices come from the public models.dev catalog in USD per 1M tokens. Saved historical costs are not recalculated; sync only backfills currently unpriced calls.')),
|
|
2286
|
-
React.createElement('div', { className: 'uh-pricing-toolbar' },
|
|
2287
|
-
React.createElement('label', { className: 'uh-pricing-switch' },
|
|
2288
|
-
React.createElement('input', { type: 'checkbox', checked: pricingDraft.sync.autoEnabled === true, disabled: pricingSaving || pricingSyncing || pricingSyncSaving, onChange: (event) => updatePricingSync(event.target.checked) }),
|
|
2289
|
-
React.createElement('span', {}, tr('启用 6 小时自动同步', 'Enable 6-hour automatic sync')),
|
|
2290
|
-
),
|
|
2291
|
-
React.createElement('span', { className: 'uh-note' }, pricingSyncSaving ? tr('保存中…', 'Saving…') : (pricingSync.lastSuccessAt > 0 ? tr('上次成功:', 'Last success: ') + new Date(pricingSync.lastSuccessAt).toLocaleString() : tr('尚未同步', 'Not synced yet'))),
|
|
2292
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: syncPricingNow, disabled: pricingSyncing || pricingSaving || pricingSyncSaving }, React.createElement(LineIcon, { name: 'refresh', size: 14 }), pricingSyncing ? tr('同步中…', 'Syncing…') : tr('立即同步', 'Sync now')),
|
|
2293
|
-
),
|
|
2294
|
-
pricingSync.lastError ? React.createElement('div', { className: 'uh-pricing-error', role: 'alert' }, tr('上次同步失败:', 'Last sync failed: ') + pricingSync.lastError) : null,
|
|
2295
|
-
React.createElement('div', { className: 'uh-pricing-section' },
|
|
2296
|
-
React.createElement('div', { className: 'uh-pricing-section-head' }, React.createElement('strong', {}, tr('当前用量匹配', 'Usage matches')), React.createElement('span', { className: 'uh-note' }, pricingUsedModels.length + ' ' + tr('个模型', 'models'))),
|
|
2297
|
-
pricingUsedModels.length === 0 ? React.createElement('div', { className: 'uh-empty', style: { padding: '12px 0' } }, tr('暂无模型用量', 'No model usage yet')) : React.createElement('div', { className: 'uh-pricing-table-wrap' },
|
|
2298
|
-
React.createElement('table', { className: 'uh-pricing-model-table' },
|
|
2299
|
-
React.createElement('thead', {}, React.createElement('tr', {},
|
|
2300
|
-
React.createElement('th', { scope: 'col' }, tr('当前模型', 'Usage model')),
|
|
2301
|
-
React.createElement('th', { scope: 'col' }, tr('状态', 'Status')),
|
|
2302
|
-
React.createElement('th', { scope: 'col' }, tr('官方模型', 'Official model')),
|
|
2303
|
-
React.createElement('th', { scope: 'col', title: tr('输入价格(USD / 1M)', 'Input price (USD / 1M)') }, tr('输入', 'Input')),
|
|
2304
|
-
React.createElement('th', { scope: 'col', title: tr('输出价格(USD / 1M)', 'Output price (USD / 1M)') }, tr('输出', 'Output')),
|
|
2305
|
-
React.createElement('th', { scope: 'col', title: tr('缓存读取价格(USD / 1M)', 'Cache read price (USD / 1M)') }, tr('缓存读', 'Cache read')),
|
|
2306
|
-
React.createElement('th', { scope: 'col', title: tr('缓存写入价格(USD / 1M)', 'Cache write price (USD / 1M)') }, tr('缓存写', 'Cache write')),
|
|
2307
|
-
)),
|
|
2308
|
-
React.createElement('tbody', {}, pricingUsedModels.map((model) => React.createElement('tr', { key: model.identityKey },
|
|
2309
|
-
React.createElement('td', { className: 'uh-pricing-model-name', title: model.model }, model.model || tr('未知模型', 'Unknown model')),
|
|
2310
|
-
React.createElement('td', { title: model.reason || '' }, React.createElement('span', { className: 'uh-pricing-status uh-pricing-status-' + (model.status || 'unpriced') }, pricingStatusLabel(model.status || 'unpriced', language))),
|
|
2311
|
-
React.createElement('td', { className: 'uh-pricing-model-target', title: model.pricingModel || '' }, model.pricingModel || tr('未匹配', 'No match')),
|
|
2312
|
-
React.createElement('td', { className: 'uh-pricing-model-rate' }, model.status === 'priced' && model.rates ? model.rates.input : '—'),
|
|
2313
|
-
React.createElement('td', { className: 'uh-pricing-model-rate' }, model.status === 'priced' && model.rates ? model.rates.output : '—'),
|
|
2314
|
-
React.createElement('td', { className: 'uh-pricing-model-rate' }, model.status === 'priced' && model.rates ? model.rates.cacheRead : '—'),
|
|
2315
|
-
React.createElement('td', { className: 'uh-pricing-model-rate' }, model.status === 'priced' && model.rates ? model.rates.cacheWrite : '—'),
|
|
2316
|
-
)),
|
|
2317
|
-
),
|
|
2318
|
-
),
|
|
2319
|
-
),
|
|
2320
|
-
),
|
|
2321
|
-
React.createElement('div', { className: 'uh-pricing-section' },
|
|
2322
|
-
React.createElement('div', { className: 'uh-pricing-section-head' }, React.createElement('strong', {}, tr('模型映射', 'Model mappings')), React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: addPricingMapping }, React.createElement(LineIcon, { name: 'plus', size: 13 }), tr('添加映射', 'Add mapping'))),
|
|
2323
|
-
pricingDraft.mappings.length === 0 ? React.createElement('div', { className: 'uh-empty', style: { padding: '12px 0' } }, tr('选择当前模型后,再指定对应的官方模型。DSH Provider 不参与计价。', 'Select a used model, then choose its official model. The DSH provider is ignored.')) : pricingDraft.mappings.map((mapping, index) => {
|
|
2324
|
-
const usedModelQuery = String(pricingUsedModelSearchText[index] || '').trim().toLowerCase()
|
|
2325
|
-
const mappingModelKey = pricingModelKey(mapping.model)
|
|
2326
|
-
const mappingOfficialModelKey = pricingModelKey(mapping.catalogModelId)
|
|
2327
|
-
const selectedUsedModel = pricingUsedModelOptions.find((option) => option.value === String(mapping.usageIdentityKey || '')) || pricingUsedModelOptions.find((option) => mappingModelKey !== '' && option.model === mappingModelKey) || pricingUsedModelOptions.find((option) => mappingOfficialModelKey !== '' && option.officialModel === mappingOfficialModelKey)
|
|
2328
|
-
const usedModelOptions = pricingUsedModelOptions.filter((option) => usedModelQuery === '' || option.label.toLowerCase().includes(usedModelQuery))
|
|
2329
|
-
return React.createElement('div', { key: index, className: 'uh-pricing-edit-row' },
|
|
2330
|
-
React.createElement('div', { className: 'uh-pricing-used-model-picker' },
|
|
2331
|
-
React.createElement('input', { type: 'text', className: 'uh-pricing-used-model-input', placeholder: tr('选择当前用过的模型', 'Select a used model'), value: pricingUsedModelSearchText[index] !== undefined ? pricingUsedModelSearchText[index] : (selectedUsedModel ? selectedUsedModel.label : ''), 'aria-label': tr('当前用过的模型', 'Used model'), 'aria-haspopup': 'listbox', 'aria-expanded': pricingUsedModelOpen === index, onFocus: () => { setPricingUsedModelOpen(index); setPricingUsedModelSearchText((prev) => Object.assign({}, prev, { [index]: '' })) }, onClick: () => setPricingUsedModelOpen(index), onBlur: () => setTimeout(() => { setPricingUsedModelOpen((current) => current === index ? null : current); if (selectedUsedModel) setPricingUsedModelSearchText((prev) => Object.assign({}, prev, { [index]: selectedUsedModel.label })) }, 120), onKeyDown: (event) => { if (event.key === 'Escape') setPricingUsedModelOpen(null) }, onChange: (event) => searchUsedModels(index, event.target.value) }),
|
|
2332
|
-
pricingUsedModelOpen === index && usedModelOptions.length > 0 ? React.createElement('div', { className: 'uh-language-options uh-pricing-used-model-options', role: 'listbox', 'aria-label': tr('当前用过的模型', 'Used models') },
|
|
2333
|
-
usedModelOptions.map((option) => React.createElement('button', { key: option.value, type: 'button', role: 'option', className: 'uh-language-option uh-pricing-model-option', onMouseDown: (event) => event.preventDefault(), onClick: () => selectPricingUsedModel(index, option.value) },
|
|
2334
|
-
React.createElement(LineIcon, { name: 'list', size: 14 }),
|
|
2335
|
-
React.createElement('span', { className: 'uh-pricing-model-option-name' }, option.label),
|
|
2336
|
-
)),
|
|
2337
|
-
) : null,
|
|
2338
|
-
),
|
|
2339
|
-
React.createElement('div', { className: 'uh-pricing-model-search' },
|
|
2340
|
-
React.createElement('input', { type: 'text', className: 'uh-pricing-model-search-input', placeholder: tr('输入官方模型 ID 检索', 'Type official model ID to search'), value: mapping.catalogModelId || '', 'aria-label': tr('官方模型 ID', 'Official model ID'), 'aria-autocomplete': 'list', onFocus: () => setPricingModelSearchOpen(index), onBlur: () => setTimeout(() => setPricingModelSearchOpen((current) => current === index ? null : current), 120), onKeyDown: (event) => { if (event.key === 'Escape') setPricingModelSearchOpen(null) }, onChange: (event) => searchOfficialModels(index, event.target.value) }),
|
|
2341
|
-
pricingModelSearchOpen === index && Array.isArray(pricingModelSearchOptions[index]) && pricingModelSearchOptions[index].length > 0 ? React.createElement('div', { className: 'uh-language-options uh-pricing-model-options', role: 'listbox', 'aria-label': tr('官方模型匹配结果', 'Official model matches') },
|
|
2342
|
-
pricingModelSearchOptions[index].map((option) => React.createElement('button', { key: option.value, type: 'button', role: 'option', className: 'uh-language-option uh-pricing-model-option', onMouseDown: (event) => event.preventDefault(), onClick: () => chooseOfficialModel(index, option) },
|
|
2343
|
-
React.createElement(LineIcon, { name: 'list', size: 14 }),
|
|
2344
|
-
React.createElement('span', { className: 'uh-pricing-model-option-name' }, option.label || option.value),
|
|
2345
|
-
React.createElement('span', { className: 'uh-pricing-model-option-id' }, option.value),
|
|
2346
|
-
)),
|
|
2347
|
-
) : null,
|
|
2348
|
-
),
|
|
2349
|
-
React.createElement('input', { type: 'number', min: '0', step: 'any', title: tr('成本倍率', 'Cost multiplier'), 'aria-label': tr('成本倍率', 'Cost multiplier'), value: mapping.multiplier || '1', onChange: (event) => updatePricingMapping(index, 'multiplier', event.target.value) }),
|
|
2350
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh uh-icon-button', title: tr('删除映射', 'Remove mapping'), 'aria-label': tr('删除映射', 'Remove mapping'), onClick: () => removePricingMapping(index) }, React.createElement(LineIcon, { name: 'close', size: 14 })),
|
|
2351
|
-
)
|
|
2352
|
-
}),
|
|
2353
|
-
),
|
|
2354
|
-
React.createElement('div', { className: 'uh-pricing-section' },
|
|
2355
|
-
React.createElement('div', { className: 'uh-pricing-section-head' }, React.createElement('strong', {}, tr('显式价格覆盖', 'Explicit price overrides')), React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: addPricingOverride }, React.createElement(LineIcon, { name: 'plus', size: 13 }), tr('添加价格', 'Add price'))),
|
|
2356
|
-
pricingDraft.overrides.length === 0 ? React.createElement('div', { className: 'uh-empty', style: { padding: '12px 0' } }, tr('仅在官方目录未覆盖或有明确官方账单时添加;价格按模型 ID 生效。', 'Add an override only when the official catalog lacks the model or you have an authoritative official price. It applies by model ID.')) : React.createElement(React.Fragment, null,
|
|
2357
|
-
React.createElement('div', { className: 'uh-pricing-price-head' },
|
|
2358
|
-
React.createElement('span', {}, tr('官方模型 ID', 'Official model ID')),
|
|
2359
|
-
React.createElement('span', {}, tr('输入价 / 1M', 'Input / 1M')),
|
|
2360
|
-
React.createElement('span', {}, tr('输出价 / 1M', 'Output / 1M')),
|
|
2361
|
-
React.createElement('span', {}, tr('缓存读 / 1M', 'Cache read / 1M')),
|
|
2362
|
-
React.createElement('span', {}, tr('缓存写 / 1M', 'Cache write / 1M')),
|
|
2363
|
-
React.createElement('span', {}, ''),
|
|
2364
|
-
),
|
|
2365
|
-
pricingDraft.overrides.map((entry, index) => {
|
|
2366
|
-
const overrideModelQuery = String(pricingOverrideSearchText[index] || '').trim().toLowerCase()
|
|
2367
|
-
const overrideModelOptions = pricingUsedModelOptions.filter((option) => overrideModelQuery === '' || option.label.toLowerCase().includes(overrideModelQuery))
|
|
2368
|
-
return React.createElement('div', { key: index, className: 'uh-pricing-edit-row uh-pricing-price-row' },
|
|
2369
|
-
React.createElement('div', { className: 'uh-pricing-used-model-picker' },
|
|
2370
|
-
React.createElement('input', { type: 'text', className: 'uh-pricing-used-model-input', placeholder: tr('选择当前用过的模型', 'Select a used model'), value: pricingOverrideSearchText[index] !== undefined ? pricingOverrideSearchText[index] : (entry.modelId || ''), 'aria-label': tr('覆盖模型 ID', 'Override model ID'), 'aria-haspopup': 'listbox', 'aria-expanded': pricingOverrideOpen === index, onFocus: () => { setPricingOverrideOpen(index); setPricingOverrideSearchText((prev) => Object.assign({}, prev, { [index]: '' })) }, onClick: () => setPricingOverrideOpen(index), onBlur: () => setTimeout(() => { setPricingOverrideOpen((current) => current === index ? null : current); if (entry.modelId) setPricingOverrideSearchText((prev) => Object.assign({}, prev, { [index]: entry.modelId })) }, 120), onKeyDown: (event) => { if (event.key === 'Escape') setPricingOverrideOpen(null) }, onChange: (event) => searchPricingOverrideModels(index, event.target.value) }),
|
|
2371
|
-
pricingOverrideOpen === index && overrideModelOptions.length > 0 ? React.createElement('div', { className: 'uh-language-options uh-pricing-used-model-options', role: 'listbox', 'aria-label': tr('当前用过的模型', 'Used models') },
|
|
2372
|
-
overrideModelOptions.map((option) => React.createElement('button', { key: option.value, type: 'button', role: 'option', className: 'uh-language-option uh-pricing-model-option', onMouseDown: (event) => event.preventDefault(), onClick: () => selectPricingOverrideModel(index, option.value) },
|
|
2373
|
-
React.createElement(LineIcon, { name: 'list', size: 14 }),
|
|
2374
|
-
React.createElement('span', { className: 'uh-pricing-model-option-name' }, option.label),
|
|
2375
|
-
)),
|
|
2376
|
-
) : null,
|
|
2377
|
-
),
|
|
2378
|
-
React.createElement('input', { type: 'number', min: '0', step: 'any', placeholder: tr('输入价 / 1M', 'Input / 1M'), title: tr('输入价格,美元 / 100 万 Token', 'Input price, USD / 1M tokens'), 'aria-label': tr('输入价格 / 1M', 'Input price / 1M'), value: entry.input === undefined ? '' : entry.input, onChange: (event) => updatePricingOverride(index, 'input', event.target.value) }),
|
|
2379
|
-
React.createElement('input', { type: 'number', min: '0', step: 'any', placeholder: tr('输出价 / 1M', 'Output / 1M'), title: tr('输出价格,美元 / 100 万 Token', 'Output price, USD / 1M tokens'), 'aria-label': tr('输出价格 / 1M', 'Output price / 1M'), value: entry.output === undefined ? '' : entry.output, onChange: (event) => updatePricingOverride(index, 'output', event.target.value) }),
|
|
2380
|
-
React.createElement('input', { type: 'number', min: '0', step: 'any', placeholder: tr('缓存读 / 1M', 'Cache read / 1M'), title: tr('缓存读取价格,美元 / 100 万 Token', 'Cache read price, USD / 1M tokens'), 'aria-label': tr('缓存读取价格 / 1M', 'Cache read price / 1M'), value: entry.cacheRead === undefined ? '' : entry.cacheRead, onChange: (event) => updatePricingOverride(index, 'cacheRead', event.target.value) }),
|
|
2381
|
-
React.createElement('input', { type: 'number', min: '0', step: 'any', placeholder: tr('缓存写 / 1M', 'Cache write / 1M'), title: tr('缓存写入价格,美元 / 100 万 Token', 'Cache write price, USD / 1M tokens'), 'aria-label': tr('缓存写入价格 / 1M', 'Cache write price / 1M'), value: entry.cacheWrite === undefined ? '' : entry.cacheWrite, onChange: (event) => updatePricingOverride(index, 'cacheWrite', event.target.value) }),
|
|
2382
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh uh-icon-button', title: tr('删除价格覆盖', 'Remove price override'), 'aria-label': tr('删除价格覆盖', 'Remove price override'), onClick: () => removePricingOverride(index) }, React.createElement(LineIcon, { name: 'close', size: 14 })),
|
|
2383
|
-
)
|
|
2384
|
-
}),
|
|
2385
|
-
),
|
|
2386
|
-
),
|
|
2387
|
-
pricingError !== '' ? React.createElement('div', { className: 'uh-pricing-error', role: 'alert' }, pricingError === 'forbidden' ? tr('没有权限保存成本设置', 'Not allowed to save cost settings') : pricingError === 'token' ? tr('当前进程令牌不可用,请刷新看板', 'The process capability is unavailable; refresh the dashboard') : pricingError === 'sync' ? tr('models.dev 同步失败,已保留上次成功目录', 'models.dev sync failed; the last good catalog was kept') : tr('成本设置保存失败,请检查输入', 'Cost settings could not be saved; check the inputs')) : null,
|
|
2388
|
-
React.createElement('div', { className: 'uh-pricing-foot' },
|
|
2389
|
-
React.createElement('span', { className: 'uh-note' }, tr('保存不会重算已有正成本;回填只处理未计价调用。', 'Saving does not recalculate existing positive costs; backfill only handles unpriced calls.')),
|
|
2390
|
-
React.createElement('div', { className: 'uh-actions' },
|
|
2391
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: () => setPricingOpen(false) }, tr('取消', 'Cancel')),
|
|
2392
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', disabled: pricingSaving || pricingSyncing, onClick: () => savePricingSettings(false) }, pricingSaving ? tr('保存中…', 'Saving…') : tr('保存', 'Save')),
|
|
2393
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh uh-pricing-backfill', disabled: pricingSaving || pricingSyncing, onClick: () => savePricingSettings(true) }, tr('保存并回填', 'Save and backfill')),
|
|
2394
|
-
),
|
|
2395
|
-
),
|
|
2396
|
-
) : null
|
|
2397
|
-
|
|
2398
|
-
let tip = null
|
|
2399
|
-
if (hover !== null && hover !== undefined) {
|
|
2400
|
-
const day = hover.day
|
|
2401
|
-
let rowsContent = []
|
|
2402
|
-
let tokensText = ''
|
|
2403
|
-
if (day !== undefined) {
|
|
2404
|
-
const sorted = (Array.isArray(day.perWorkspace) ? day.perWorkspace : []).slice().sort((a, b) => b.turns - a.turns)
|
|
2405
|
-
rowsContent = sorted.map((entry) => {
|
|
2406
|
-
const idx = wsIndex.get(entry.workspaceId)
|
|
2407
|
-
return React.createElement('div', {
|
|
2408
|
-
key: entry.workspaceId,
|
|
2409
|
-
className: 'uh-tip-row',
|
|
2410
|
-
onClick: () => { toggleFilter(entry.workspaceId); setHover(null) },
|
|
2411
|
-
},
|
|
2412
|
-
React.createElement('span', { className: 'uh-dot', style: { background: wsColor(idx === undefined ? 0 : idx) } }),
|
|
2413
|
-
React.createElement('span', {}, wsTitle(entry.workspaceId)),
|
|
2414
|
-
React.createElement('span', { className: 'uh-n' }, language === 'en' ? entry.turns + ' uses' : entry.turns + ' 次'),
|
|
2415
|
-
)
|
|
2416
|
-
})
|
|
2417
|
-
const dayTokenValues = rowTokens(day)
|
|
2418
|
-
if (dayTokenValues.input + dayTokenValues.output + dayTokenValues.cacheRead > 0) {
|
|
2419
|
-
tokensText = language === 'en' ? 'Tokens: Input ' + fmtCompact(dayTokenValues.input) + ' · Cache hits ' + fmtCompact(dayTokenValues.cacheRead) + ' · Output ' + fmtCompact(dayTokenValues.output) : 'Token:输入 ' + fmtCompact(dayTokenValues.input) + ' · 缓存命中 ' + fmtCompact(dayTokenValues.cacheRead) + ' · 输出 ' + fmtCompact(dayTokenValues.output)
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
const flip = hover.x > 640
|
|
2423
|
-
tip = React.createElement('div', {
|
|
2424
|
-
key: hover.date,
|
|
2425
|
-
className: 'uh-tip',
|
|
2426
|
-
style: {
|
|
2427
|
-
left: hover.x + 14,
|
|
2428
|
-
top: hover.y + 12,
|
|
2429
|
-
transform: flip ? 'translateX(calc(-100% - 28px))' : 'none',
|
|
2430
|
-
},
|
|
2431
|
-
},
|
|
2432
|
-
React.createElement('div', { className: 'uh-tip-date' }, humanDate(hover.date, language)),
|
|
2433
|
-
day !== undefined && day.turns > 0
|
|
2434
|
-
? rowsContent
|
|
2435
|
-
: React.createElement('div', { className: 'uh-empty', style: { padding: '6px 0' } }, tr('这一天没有使用记录', 'No usage records for this day')),
|
|
2436
|
-
tokensText !== '' ? React.createElement('div', { className: 'uh-tip-tokens' }, tokensText) : null,
|
|
2437
|
-
)
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
const rangeLabel = range === 'custom' && activeCustomRange !== null
|
|
2441
|
-
? (language === 'en' ? activeCustomRange.start + ' to ' + activeCustomRange.end + ' (UTC)' : activeCustomRange.start + ' 至 ' + activeCustomRange.end)
|
|
2442
|
-
: range === 'today' ? tr('今日', 'Today') : range === '30d' ? tr('近 30 天', 'Last 30 Days') : range === '90d' ? tr('近 90 天', 'Last 90 Days') : tr('全部', 'All Time')
|
|
2443
|
-
const rangeFilePart = rangeFilenamePart(range, activeCustomRange, useUtc)
|
|
2444
|
-
const customRangeErrorText = customDraftIssue === 'invalid'
|
|
2445
|
-
? tr('请选择有效的开始日期和结束日期', 'Choose valid start and end dates')
|
|
2446
|
-
: customDraftIssue === 'order'
|
|
2447
|
-
? tr('结束日期不能早于开始日期', 'End date must be on or after the start date')
|
|
2448
|
-
: customDraftIssue === 'bounds'
|
|
2449
|
-
? tr('可选范围为 ' + earliestAvailableDate + ' 至 ' + latestCalendarDate, 'Choose a date from ' + earliestAvailableDate + ' to ' + latestCalendarDate)
|
|
2450
|
-
: ''
|
|
2451
|
-
const customRangePanel = customRangeOpen ? React.createElement('div', { className: 'uh-custom-range', role: 'group', 'aria-label': tr('自定义时间范围', 'Custom date range') },
|
|
2452
|
-
React.createElement('div', { className: 'uh-custom-range-meta' },
|
|
2453
|
-
React.createElement('div', { className: 'uh-custom-range-title' }, React.createElement(LineIcon, { name: 'calendar', size: 15 }), tr('自定义时间范围', 'Custom date range')),
|
|
2454
|
-
React.createElement('div', { className: 'uh-custom-range-note' }, tr('可查看全部可扫描历史日数据;中文按本地日期,English 按 UTC。热力图始终展示最近 53 周。', 'All available historical daily data can be selected. Chinese uses local dates; English uses UTC. The heatmap always shows the latest 53 weeks.')),
|
|
2455
|
-
),
|
|
2456
|
-
React.createElement('div', { className: 'uh-custom-range-fields' },
|
|
2457
|
-
React.createElement('label', { className: 'uh-custom-range-field' },
|
|
2458
|
-
React.createElement('span', {}, tr('开始日期', 'Start date')),
|
|
2459
|
-
React.createElement('input', { type: 'date', value: customDraft.start, min: earliestAvailableDate, max: latestCalendarDate, onChange: (event) => setCustomDraft((prev) => Object.assign({}, prev, { start: event.target.value })) }),
|
|
2460
|
-
),
|
|
2461
|
-
React.createElement('label', { className: 'uh-custom-range-field' },
|
|
2462
|
-
React.createElement('span', {}, tr('结束日期', 'End date')),
|
|
2463
|
-
React.createElement('input', { type: 'date', value: customDraft.end, min: earliestAvailableDate, max: latestCalendarDate, onChange: (event) => setCustomDraft((prev) => Object.assign({}, prev, { end: event.target.value })) }),
|
|
2464
|
-
),
|
|
2465
|
-
),
|
|
2466
|
-
React.createElement('div', { className: 'uh-custom-range-actions' },
|
|
2467
|
-
React.createElement('button', { type: 'button', className: 'uh-custom-range-cancel', onClick: () => setCustomRangeOpen(false) }, tr('取消', 'Cancel')),
|
|
2468
|
-
React.createElement('button', { type: 'button', className: 'uh-custom-range-apply', disabled: customDraftIssue !== '', onClick: applyCustomRange }, tr('应用', 'Apply')),
|
|
2469
|
-
),
|
|
2470
|
-
customRangeErrorText !== '' ? React.createElement('div', { className: 'uh-custom-range-error', role: 'alert' }, customRangeErrorText) : null,
|
|
2471
|
-
) : null
|
|
2472
|
-
const trendBounds = queryScope !== null ? { start: queryScope.start, end: queryScope.end } : resolveRangeBounds(stats, range, useUtc, activeCustomRange)
|
|
2473
|
-
const hourlyTrendRows = queryReady && queryScope !== null && queryScope.start === queryScope.end && queryResult && Array.isArray(queryResult.hourly) ? buildTrendHourlyRows(queryResult.hourly, useUtc) : []
|
|
2474
|
-
const trendRows = hourlyTrendRows.length > 0 ? hourlyTrendRows : buildTrendRows(queryReady && queryResult && Array.isArray(queryResult.daily) ? queryResult.daily : activeDayRows, trendBounds, useUtc)
|
|
2475
|
-
const trendAnimationKey = queryReady && queryResult ? queryKey + ':' + queryResult.revision : queryKey
|
|
2476
|
-
const toggleTrendSeries = (key) => {
|
|
2477
|
-
setTrendVisible((prev) => {
|
|
2478
|
-
if (prev.includes(key)) return prev.length <= 1 ? prev : prev.filter((item) => item !== key)
|
|
2479
|
-
return prev.concat(key)
|
|
2480
|
-
})
|
|
2481
|
-
}
|
|
2482
|
-
const trendPanel = React.createElement(UsageTrendChart, {
|
|
2483
|
-
key: trendAnimationKey,
|
|
2484
|
-
rows: trendRows,
|
|
2485
|
-
visible: trendVisible,
|
|
2486
|
-
language,
|
|
2487
|
-
rangeLabel,
|
|
2488
|
-
loading: queryLoading && !queryReady,
|
|
2489
|
-
error: queryError !== '' && queryError !== 'stale' && !queryReady ? tr('趋势数据加载失败', 'Trend data unavailable') : '',
|
|
2490
|
-
onToggle: toggleTrendSeries,
|
|
2491
|
-
onPointClick: openAuditForDate,
|
|
2492
|
-
})
|
|
2493
|
-
const detailScopeLabel = selectedDetailScope === null
|
|
2494
|
-
? rangeLabel
|
|
2495
|
-
: selectedDetailScope.start === selectedDetailScope.end
|
|
2496
|
-
? selectedDetailScope.start
|
|
2497
|
-
: selectedDetailScope.start + ' → ' + selectedDetailScope.end
|
|
2498
|
-
const auditToken = (row, key) => Number(row && row.values && row.values[key]) || 0
|
|
2499
|
-
const auditTotal = (row) => auditToken(row, 'input') + auditToken(row, 'cacheRead') + auditToken(row, 'cacheWrite') + auditToken(row, 'output') + auditToken(row, 'reasoning')
|
|
2500
|
-
const auditSource = (row) => row && row.materialization === 'ledger-recovery' ? tr('账本恢复', 'Ledger recovery') : row && row.materialization === 'ledger-reuse' ? tr('账本复用', 'Ledger reuse') : row && row.materialization === 'scan' ? tr('扫描', 'Scan') : row && row.materialization === 'live' ? tr('实时', 'Live') : tr('未知', 'Unknown')
|
|
2501
|
-
const auditTime = (row, detailed) => row && Number.isFinite(row.time) ? new Date(row.time).toLocaleString(language === 'en' ? 'en-US' : 'zh-CN', language === 'en' ? (detailed ? { timeZone: 'UTC' } : { timeZone: 'UTC', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : (detailed ? undefined : { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })) : '—'
|
|
2502
|
-
const selectedAudit = auditRows.find((row) => row.id === auditSelectedId) || auditRows[0] || null
|
|
2503
|
-
const exportAuditCsv = async () => {
|
|
2504
|
-
if (selectedDetailScope === null || auditExporting) return
|
|
2505
|
-
setAuditExporting(true)
|
|
2506
|
-
setAuditError('')
|
|
2507
|
-
try {
|
|
2508
|
-
let cursor = null
|
|
2509
|
-
const all = []
|
|
2510
|
-
for (let page = 0; page < 50; page += 1) {
|
|
2511
|
-
const data = await getUsageRecords(selectedDetailScope, cursor, 200)
|
|
2512
|
-
if (data === null || typeof data !== 'object' || !Array.isArray(data.items)) throw new Error('audit export failed')
|
|
2513
|
-
all.push(...data.items)
|
|
2514
|
-
if (!data.hasMore || !data.nextCursor) break
|
|
2515
|
-
cursor = data.nextCursor
|
|
2516
|
-
}
|
|
2517
|
-
const quote = (value) => '"' + String(value === undefined || value === null ? '' : value).replace(/"/g, '""') + '"'
|
|
2518
|
-
const line = (values) => values.map(quote).join(',')
|
|
2519
|
-
const headers = [tr('时间', 'Time'), tr('日期', 'Date'), tr('Provider', 'Provider'), tr('请求模型', 'Requested model'), tr('实际模型', 'Actual model'), tr('显示模型', 'Display model'), 'turn', 'step', 'seq', tr('输入', 'Input'), tr('缓存命中', 'Cache read'), tr('缓存写入', 'Cache write'), tr('输出', 'Output'), tr('推理', 'Reasoning'), tr('成本', 'Cost'), tr('计价状态', 'Cost status'), tr('计价模型', 'Pricing model'), tr('来源', 'Source')]
|
|
2520
|
-
const lines = [line([tr('DSH 用量明细导出', 'DSH Usage Audit Export')]), line([tr('范围', 'Scope'), selectedDetailScope.start + ' → ' + selectedDetailScope.end]), line([tr('时区', 'Timezone'), selectedDetailScope.utc ? 'UTC' : tr('本地', 'Local')]), line(headers)]
|
|
2521
|
-
for (const row of all) lines.push(line([row.time, row.date, row.provider, row.requestedModel, row.actualModel, row.model, row.turn, row.step, row.seq, auditToken(row, 'input'), auditToken(row, 'cacheRead'), auditToken(row, 'cacheWrite'), auditToken(row, 'output'), auditToken(row, 'reasoning'), row.cost && row.cost.status === 'priced' ? row.cost.total : '', row.cost && row.cost.status ? row.cost.status : 'unpriced', row.cost && row.cost.pricingModel ? row.cost.pricingModel : '', row.materialization || 'unknown']))
|
|
2522
|
-
const blob = new Blob(['\uFEFF' + lines.join('\r\n')], { type: 'text/csv;charset=utf-8' })
|
|
2523
|
-
const url = URL.createObjectURL(blob)
|
|
2524
|
-
const anchor = document.createElement('a')
|
|
2525
|
-
anchor.href = url
|
|
2526
|
-
anchor.download = 'dsh-all-usage-audit-' + selectedDetailScope.start + '-to-' + selectedDetailScope.end + '.csv'
|
|
2527
|
-
document.body.appendChild(anchor); anchor.click(); anchor.remove()
|
|
2528
|
-
URL.revokeObjectURL(url)
|
|
2529
|
-
} catch (err) {
|
|
2530
|
-
setAuditError('audit-export')
|
|
2531
|
-
} finally {
|
|
2532
|
-
setAuditExporting(false)
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
const recordsPanel = React.createElement('div', { className: 'uh-panel uh-records-panel', ref: recordsPanelRef, style: { display: detailView === 'logs' ? 'block' : 'none' } },
|
|
2536
|
-
React.createElement('div', { className: 'uh-records-head' },
|
|
2537
|
-
React.createElement('div', {},
|
|
2538
|
-
React.createElement('h3', { className: 'uh-tbl-title uh-title-with-icon' }, React.createElement(LineIcon, { name: 'list', size: 16 }), tr('请求日志', 'Request Logs')),
|
|
2539
|
-
React.createElement('div', { className: 'uh-note' }, detailScopeLabel + (selectedDetailScope && selectedDetailScope.utc ? ' · UTC' : '')),
|
|
2540
|
-
),
|
|
2541
|
-
React.createElement('div', { className: 'uh-actions' },
|
|
2542
|
-
auditLoading ? React.createElement('span', { className: 'uh-query-note' }, tr('同步中…', 'Refreshing…')) : null,
|
|
2543
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', title: tr('导出当前日志', 'Export current logs'), onClick: exportAuditCsv, disabled: auditExporting || selectedDetailScope === null }, React.createElement(LineIcon, { name: 'export', size: 13 }), auditExporting ? tr('导出中…', 'Exporting…') : tr('导出日志', 'Export logs')),
|
|
2544
|
-
),
|
|
2545
|
-
),
|
|
2546
|
-
auditError !== '' ? React.createElement('div', { className: 'uh-records-error', role: 'alert' }, auditError === 'stale' ? tr('数据已更新,正在重新加载日志…', 'Data changed; reloading logs…') : auditError === 'audit-export' ? tr('日志导出失败', 'Unable to export logs') : tr('日志加载失败,请重试', 'Unable to load logs')) : null,
|
|
2547
|
-
React.createElement('div', { className: 'uh-records-note' }, tr('按时间倒序显示可审计的 Token 调用;选择一行查看 turn / step 和完整 Token 分桶。', 'Token calls are newest first; select a row to inspect its turn / step and token buckets.')),
|
|
2548
|
-
auditRows.length === 0 && !auditLoading ? React.createElement('div', { className: 'uh-empty' }, tr('当前范围没有可审计的 Token 调用', 'No auditable Token calls in this scope')) : React.createElement('div', { className: 'uh-records-scroll' },
|
|
2549
|
-
React.createElement('div', { className: 'uh-record-grid uh-record-header' },
|
|
2550
|
-
React.createElement('div', {}, tr('时间', 'Time')), React.createElement('div', {}, tr('Provider / 模型', 'Provider / Model')), React.createElement('div', { className: 'uh-record-num' }, 'turn / step'), React.createElement('div', { className: 'uh-record-num' }, tr('输入', 'Input')), React.createElement('div', { className: 'uh-record-num' }, tr('缓存命中', 'Cache read')), React.createElement('div', { className: 'uh-record-num' }, tr('缓存写入', 'Cache write')), React.createElement('div', { className: 'uh-record-num' }, tr('输出', 'Output')), React.createElement('div', { className: 'uh-record-num' }, tr('成本', 'Cost')), React.createElement('div', {}, tr('来源', 'Source')),
|
|
2551
|
-
),
|
|
2552
|
-
auditRows.map((row) => React.createElement('div', { key: row.id, className: 'uh-record-grid uh-record-row' + (selectedAudit && selectedAudit.id === row.id ? ' uh-on' : ''), role: 'button', tabIndex: 0, 'aria-pressed': selectedAudit && selectedAudit.id === row.id, onClick: () => setAuditSelectedId(row.id), onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setAuditSelectedId(row.id) } } },
|
|
2553
|
-
React.createElement('div', { className: 'uh-record-time' }, auditTime(row, false)),
|
|
2554
|
-
React.createElement('div', { className: 'uh-record-model', title: row.model || '' }, row.model || tr('未知模型', 'Unknown model'), row.requestedModel && row.actualModel && row.requestedModel !== row.actualModel ? React.createElement('small', {}, row.requestedModel + ' → ' + row.actualModel) : null),
|
|
2555
|
-
React.createElement('div', { className: 'uh-record-num' }, (row.turn === null || row.turn === undefined ? '—' : row.turn) + ' / ' + (row.step === null || row.step === undefined ? '—' : row.step)),
|
|
2556
|
-
React.createElement('div', { className: 'uh-record-num' }, fmtCompact(auditToken(row, 'input'))),
|
|
2557
|
-
React.createElement('div', { className: 'uh-record-num' }, fmtCompact(auditToken(row, 'cacheRead'))),
|
|
2558
|
-
React.createElement('div', { className: 'uh-record-num' }, fmtCompact(auditToken(row, 'cacheWrite'))),
|
|
2559
|
-
React.createElement('div', { className: 'uh-record-num' }, fmtCompact(auditToken(row, 'output'))),
|
|
2560
|
-
React.createElement('div', { className: 'uh-record-num uh-cost-num' }, costDisplay(row, language)),
|
|
2561
|
-
React.createElement('div', { className: 'uh-record-source' }, auditSource(row)),
|
|
2562
|
-
)),
|
|
2563
|
-
),
|
|
2564
|
-
React.createElement('div', { className: 'uh-records-footer' },
|
|
2565
|
-
React.createElement('span', { className: 'uh-note' }, auditRows.length > 0 ? (auditHasMore ? tr('已显示 ' + auditRows.length + ' 条,继续加载可查看更多', auditRows.length + ' shown; load more for additional records') : tr('共显示 ' + auditRows.length + ' 条', auditRows.length + ' records shown')) : ''),
|
|
2566
|
-
auditHasMore ? React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: loadMoreAudit, disabled: auditLoading }, auditLoading ? tr('加载中…', 'Loading…') : tr('加载更多', 'Load more')) : null,
|
|
2567
|
-
),
|
|
2568
|
-
selectedAudit ? React.createElement('div', { className: 'uh-record-detail' },
|
|
2569
|
-
React.createElement('div', { className: 'uh-record-detail-head' }, React.createElement('strong', {}, tr('选中调用', 'Selected call')), React.createElement('span', { className: 'uh-note' }, auditTime(selectedAudit, true))),
|
|
2570
|
-
React.createElement('div', { className: 'uh-record-detail-meta' },
|
|
2571
|
-
React.createElement('span', {}, (selectedAudit.provider || tr('未知供应商', 'Unknown provider')) + ' / ' + (selectedAudit.actualModel || selectedAudit.requestedModel || selectedAudit.model || tr('未知模型', 'Unknown model'))),
|
|
2572
|
-
React.createElement('span', {}, 'turn ' + (selectedAudit.turn === null || selectedAudit.turn === undefined ? '—' : selectedAudit.turn) + ' · step ' + (selectedAudit.step === null || selectedAudit.step === undefined ? '—' : selectedAudit.step)),
|
|
2573
|
-
React.createElement('span', {}, tr('来源:', 'Source: ') + auditSource(selectedAudit)),
|
|
2574
|
-
React.createElement('span', {}, tr('计价模型:', 'Pricing model: ') + (selectedAudit.cost && selectedAudit.cost.pricingModel ? selectedAudit.cost.pricingModel : tr('未计价', 'unpriced'))),
|
|
2575
|
-
),
|
|
2576
|
-
React.createElement('div', { className: 'uh-record-token-strip' },
|
|
2577
|
-
['input', 'cacheRead', 'cacheWrite', 'output', 'reasoning'].map((key) => React.createElement('div', { key }, React.createElement('span', {}, key === 'cacheRead' ? tr('缓存命中', 'Cache read') : key === 'cacheWrite' ? tr('缓存写入', 'Cache write') : key === 'reasoning' ? tr('推理', 'Reasoning') : key === 'input' ? tr('输入', 'Input') : tr('输出', 'Output')), React.createElement('strong', {}, fmtCompact(auditToken(selectedAudit, key))))),
|
|
2578
|
-
React.createElement('div', { className: 'uh-record-token-total' }, React.createElement('span', {}, tr('总处理', 'Total')), React.createElement('strong', {}, fmtCompact(auditTotal(selectedAudit)))),
|
|
2579
|
-
React.createElement('div', { className: 'uh-record-token-total' }, React.createElement('span', {}, tr('成本', 'Cost')), React.createElement('strong', {}, costDisplay(selectedAudit, language))),
|
|
2580
|
-
),
|
|
2581
|
-
) : null,
|
|
2582
|
-
)
|
|
2583
|
-
const scanning = !scan.done
|
|
2584
|
-
const pct = scan.total > 0 ? Math.min(100, Math.round((scan.scanned / scan.total) * 100)) : 40
|
|
2585
|
-
const isEmpty = scan.done && dayRows.length === 0 && agg.totals.turns === 0 && agg.totals.calls === 0
|
|
2586
|
-
const syncCompletedAt = typeof sync.lastCompletedAt === 'number' && sync.lastCompletedAt > 0 ? new Date(sync.lastCompletedAt).toLocaleString(language === 'en' ? 'en-US' : 'zh-CN') : ''
|
|
2587
|
-
const lastStatsText = lastStatsAt > 0 ? new Date(lastStatsAt).toLocaleString(language === 'en' ? 'en-US' : 'zh-CN') : ''
|
|
2588
|
-
const healthTitle = syncCompletedAt === '' ? undefined : (language === 'en' ? 'Historical scan completed ' + syncCompletedAt : '历史扫描完成于 ' + syncCompletedAt)
|
|
2589
|
-
const healthText = language === 'en'
|
|
2590
|
-
? (lastStatsText !== '' ? 'Updated ' + lastStatsText : (scanning ? 'Refreshing data' : 'Update state pending'))
|
|
2591
|
-
+ ' · ' + (sync.sessionsSkippedByRevision || 0) + ' revision reused'
|
|
2592
|
-
+ ' · ' + (sync.sessionsRead || 0) + ' read'
|
|
2593
|
-
+ ((sync.sessionsRestoredFromLedger || 0) > 0 ? ' · ' + sync.sessionsRestoredFromLedger + ' ledger restored' : '')
|
|
2594
|
-
+ ((sync.sessionsFailed || 0) > 0 ? ' · ' + sync.sessionsFailed + ' failed' : '')
|
|
2595
|
-
+ ' · ' + (sync.persistenceSnapshotsAvailable === true ? 'revision optimization on' : 'full-read fallback')
|
|
2596
|
-
: (lastStatsText !== '' ? '已更新 ' + lastStatsText : (scanning ? '正在更新数据' : '数据更新准备中'))
|
|
2597
|
-
+ ' · revision 复用 ' + (sync.sessionsSkippedByRevision || 0)
|
|
2598
|
-
+ ' · 实际读取 ' + (sync.sessionsRead || 0)
|
|
2599
|
-
+ ((sync.sessionsRestoredFromLedger || 0) > 0 ? ' · 账本恢复 ' + sync.sessionsRestoredFromLedger : '')
|
|
2600
|
-
+ ((sync.sessionsFailed || 0) > 0 ? ' · 失败 ' + sync.sessionsFailed : '')
|
|
2601
|
-
+ ' · ' + (sync.persistenceSnapshotsAvailable === true ? '免读优化已启用' : '全量读取回退')
|
|
2602
|
-
const staleText = statsError === '' ? '' : (language === 'en'
|
|
2603
|
-
? 'Usage data may be stale' + (lastStatsText !== '' ? '; last full update ' + lastStatsText : '')
|
|
2604
|
-
: '用量数据可能已过期' + (lastStatsText !== '' ? ';上次完整更新 ' + lastStatsText : ''))
|
|
2605
|
-
|
|
2606
|
-
return React.createElement('div', { className: 'uh-page' },
|
|
2607
|
-
React.createElement('div', { className: 'uh-head' },
|
|
2608
|
-
React.createElement('div', { className: 'uh-title-wrap' },
|
|
2609
|
-
React.createElement('h2', { className: 'uh-title' }, tr('用量统计', 'Usage Statistics')),
|
|
2610
|
-
),
|
|
2611
|
-
React.createElement('div', { className: 'uh-actions' },
|
|
2612
|
-
React.createElement('button', {
|
|
2613
|
-
className: 'uh-refresh',
|
|
2614
|
-
title: tr('管理工作区别名', 'Manage workspace aliases'),
|
|
2615
|
-
onClick: () => { if (aliasOpen) setAliasOpen(false); else openAliasPanel() },
|
|
2616
|
-
}, React.createElement(LineIcon, { name: 'edit', size: 14 }), tr('工作区别名', 'Workspace Aliases')),
|
|
2617
|
-
React.createElement('button', {
|
|
2618
|
-
className: 'uh-refresh',
|
|
2619
|
-
title: tr('配置模型价格与同步', 'Configure model prices and sync'),
|
|
2620
|
-
onClick: () => { if (pricingOpen) setPricingOpen(false); else openPricingPanel() },
|
|
2621
|
-
}, React.createElement(LineIcon, { name: 'wallet', size: 14 }), tr('成本设置', 'Cost Settings')),
|
|
2622
|
-
React.createElement('div', {
|
|
2623
|
-
className: 'uh-language-menu' + (languageMenuOpen ? ' uh-open' : ''),
|
|
2624
|
-
ref: languageMenuRef,
|
|
2625
|
-
onKeyDown: (event) => { if (event.key === 'Escape') { event.preventDefault(); setLanguageMenuOpen(false) } },
|
|
2626
|
-
},
|
|
2627
|
-
React.createElement('button', {
|
|
2628
|
-
type: 'button',
|
|
2629
|
-
className: 'uh-language-trigger' + (languageMenuOpen ? ' uh-open' : ''),
|
|
2630
|
-
title: tr('切换界面语言', 'Change interface language'),
|
|
2631
|
-
'aria-label': tr('界面语言', 'Interface language'),
|
|
2632
|
-
'aria-haspopup': 'menu',
|
|
2633
|
-
'aria-expanded': languageMenuOpen,
|
|
2634
|
-
onClick: () => setLanguageMenuOpen((open) => !open),
|
|
2635
|
-
},
|
|
2636
|
-
React.createElement(LineIcon, { name: 'language', size: 14 }),
|
|
2637
|
-
React.createElement('span', { className: 'uh-language-label' }, language === 'en' ? 'English' : '中文'),
|
|
2638
|
-
React.createElement(LineIcon, { name: 'chevron', size: 13, className: 'uh-language-caret' }),
|
|
2639
|
-
),
|
|
2640
|
-
languageMenuOpen ? React.createElement('div', { className: 'uh-language-options', role: 'menu', 'aria-label': tr('界面语言', 'Interface language') },
|
|
2641
|
-
[['zh', '中文'], ['en', 'English']].map((entry) => React.createElement('button', {
|
|
2642
|
-
key: entry[0],
|
|
2643
|
-
type: 'button',
|
|
2644
|
-
role: 'menuitemradio',
|
|
2645
|
-
'aria-checked': language === entry[0],
|
|
2646
|
-
className: 'uh-language-option' + (language === entry[0] ? ' uh-on' : ''),
|
|
2647
|
-
onClick: () => chooseLanguage(entry[0]),
|
|
2648
|
-
},
|
|
2649
|
-
React.createElement(LineIcon, { name: 'language', size: 14 }),
|
|
2650
|
-
React.createElement('span', {}, entry[1]),
|
|
2651
|
-
language === entry[0] ? React.createElement(LineIcon, { name: 'check', size: 14, className: 'uh-language-option-check' }) : null,
|
|
2652
|
-
)),
|
|
2653
|
-
) : null,
|
|
2654
|
-
),
|
|
2655
|
-
React.createElement('div', { className: 'uh-range' },
|
|
2656
|
-
['today', '30d', '90d', 'all', 'custom'].map((r) => React.createElement('button', {
|
|
2657
|
-
key: r,
|
|
2658
|
-
type: 'button',
|
|
2659
|
-
className: range === r ? 'uh-on' : '',
|
|
2660
|
-
title: r === 'custom' && range === 'custom' ? rangeLabel : undefined,
|
|
2661
|
-
onClick: () => { if (r === 'custom') openCustomRange(); else chooseRange(r) },
|
|
2662
|
-
}, r === 'today' ? tr('今日', 'Today') : r === '30d' ? tr('近 30 天', 'Last 30 Days') : r === '90d' ? tr('近 90 天', 'Last 90 Days') : r === 'all' ? tr('全部', 'All Time') : tr('自定义', 'Custom'))),
|
|
2663
|
-
),
|
|
2664
|
-
React.createElement('button', { className: 'uh-refresh', title: tr('导出当前时间范围与模型查看模式的 CSV 数据', 'Export CSV data for the current time range and model view'), onClick: exportCsv }, React.createElement(LineIcon, { name: 'export', size: 14 }), tr('导出数据', 'Export Data')),
|
|
2665
|
-
React.createElement('button', { className: 'uh-refresh uh-icon-button', title: tr('刷新统计数据', 'Refresh usage statistics'), 'aria-label': tr('刷新统计数据', 'Refresh usage statistics'), onClick: onRefresh }, React.createElement(LineIcon, { name: 'refresh', size: 16 })),
|
|
2666
|
-
),
|
|
2667
|
-
),
|
|
2668
|
-
React.createElement('div', { className: 'uh-filter-bar', role: 'group', 'aria-label': tr('统一筛选', 'Unified filters') },
|
|
2669
|
-
React.createElement(UsageFilterMenu, {
|
|
2670
|
-
label: tr('全部工作区', 'All workspaces'),
|
|
2671
|
-
ariaLabel: tr('工作区筛选', 'Workspace filter'),
|
|
2672
|
-
className: 'uh-filter-workspace',
|
|
2673
|
-
icon: 'folder',
|
|
2674
|
-
value: wsFilter || '',
|
|
2675
|
-
options: [{ value: '', label: tr('全部工作区', 'All workspaces') }].concat(rangeWorkspaceOptions.map((w) => ({ value: w.id, label: wsTitle(w.id) }))),
|
|
2676
|
-
onChange: (value) => setWsFilter(value || null),
|
|
2677
|
-
}),
|
|
2678
|
-
React.createElement(UsageFilterMenu, {
|
|
2679
|
-
label: tr('全部供应商', 'All providers'),
|
|
2680
|
-
ariaLabel: tr('供应商筛选', 'Provider filter'),
|
|
2681
|
-
className: 'uh-filter-provider',
|
|
2682
|
-
icon: 'chart',
|
|
2683
|
-
value: providerFilter || '',
|
|
2684
|
-
options: [{ value: '', label: tr('全部供应商', 'All providers') }].concat(providerOptions.map((value) => ({ value, label: value }))),
|
|
2685
|
-
onChange: (value) => chooseProvider(value),
|
|
2686
|
-
}),
|
|
2687
|
-
React.createElement(UsageFilterMenu, {
|
|
2688
|
-
label: tr('全部模型', 'All models'),
|
|
2689
|
-
ariaLabel: tr('模型筛选', 'Model filter'),
|
|
2690
|
-
className: 'uh-filter-model',
|
|
2691
|
-
icon: 'cache',
|
|
2692
|
-
value: modelFilter || '',
|
|
2693
|
-
options: [{ value: '', label: tr('全部模型', 'All models') }].concat(modelOptions.map((value) => ({ value, label: value }))),
|
|
2694
|
-
onChange: (value) => chooseModel(value),
|
|
2695
|
-
}),
|
|
2696
|
-
(wsFilter !== null || providerFilter !== null || modelFilter !== null) ? React.createElement('button', { type: 'button', className: 'uh-filter-clear', onClick: clearFilters }, tr('清除筛选', 'Clear filters')) : null,
|
|
2697
|
-
queryLoading ? React.createElement('span', { className: 'uh-query-note' }, tr('正在更新筛选结果…', 'Updating filtered data…')) : null,
|
|
2698
|
-
queryError !== '' && queryError !== 'stale' ? React.createElement('span', { className: 'uh-query-note', role: 'alert' }, tr('筛选结果加载失败', 'Filtered data unavailable')) : null,
|
|
2699
|
-
),
|
|
2700
|
-
aliasOpen ? aliasPanel : null,
|
|
2701
|
-
pricingPanel,
|
|
2702
|
-
customRangePanel,
|
|
2703
|
-
scanning ? React.createElement('div', { className: 'uh-progress' },
|
|
2704
|
-
React.createElement('span', {}, language === 'en' ? 'Scanning historical sessions: ' + scan.scanned + ' / ' + scan.total + (scan.failed > 0 ? ' (' + scan.failed + ' failed to read)' : '') : '正在统计历史会话 ' + scan.scanned + ' / ' + scan.total + (scan.failed > 0 ? '(' + scan.failed + ' 个读取失败)' : '')),
|
|
2705
|
-
React.createElement('div', { className: 'uh-bar' }, React.createElement('div', { className: 'uh-fill', style: { width: pct + '%' } })),
|
|
2706
|
-
) : null,
|
|
2707
|
-
React.createElement('div', { className: 'uh-sync-health' + (staleText !== '' ? ' uh-stale' : ''), title: staleText !== '' ? undefined : healthTitle },
|
|
2708
|
-
React.createElement(LineIcon, { name: staleText !== '' ? 'refresh' : 'clock', size: 14 }),
|
|
2709
|
-
React.createElement('span', {}, staleText !== '' ? staleText : healthText),
|
|
2710
|
-
staleText !== '' ? React.createElement('button', { className: 'uh-sync-retry', onClick: onRefresh }, tr('重试', 'Retry')) : null,
|
|
2711
|
-
),
|
|
2712
|
-
isEmpty ? React.createElement('div', { className: 'uh-panel' },
|
|
2713
|
-
React.createElement('div', { className: 'uh-empty' }, tr('还没有使用记录。开始对话后,这里会点亮。', 'No usage recorded yet. This area will light up after you start a conversation.')),
|
|
2714
|
-
) : React.createElement(React.Fragment, null,
|
|
2715
|
-
React.createElement(React.Fragment, null,
|
|
2716
|
-
React.createElement('div', { className: 'uh-ios-summary' },
|
|
2717
|
-
React.createElement('div', { className: 'uh-ios-summary-hero' },
|
|
2718
|
-
React.createElement('div', { className: 'uh-ios-summary-total' },
|
|
2719
|
-
React.createElement('div', { className: 'uh-ios-summary-total-icon' }, React.createElement(LineIcon, { name: 'chart', size: 24 })),
|
|
2720
|
-
React.createElement('div', { className: 'uh-ios-summary-total-copy' },
|
|
2721
|
-
React.createElement('div', { className: 'uh-ios-summary-label' }, tr('总处理 Token', 'Total Tokens Processed')),
|
|
2722
|
-
React.createElement('div', { className: 'uh-ios-summary-value' }, valueWithMagnitude(fmtCompact(animatedTotal), totalTokens, language)),
|
|
2723
|
-
React.createElement('div', { className: 'uh-ios-summary-caption' }, language === 'en' ? rangeLabel + ' · ' + fmtCompact(animatedTurns) + (scopedCountIsCalls ? ' calls' : ' uses') + ' · includes cache reads/writes and reasoning' : rangeLabel + ' · ' + fmtCompact(animatedTurns) + (scopedCountIsCalls ? ' 次调用' : ' 次使用') + ' · 含缓存读写与推理'),
|
|
2724
|
-
),
|
|
2725
|
-
),
|
|
2726
|
-
React.createElement('div', { className: 'uh-ios-summary-meta' },
|
|
2727
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-stat' },
|
|
2728
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-label' }, React.createElement(LineIcon, { name: 'chart', size: 16 }), tr('总请求数', 'Total Requests')),
|
|
2729
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-value' }, fmtCount(animatedRequests, language)),
|
|
2730
|
-
),
|
|
2731
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-stat uh-ios-summary-meta-cost' },
|
|
2732
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-label' }, React.createElement(LineIcon, { name: 'wallet', size: 16 }), tr('估算成本', 'Estimated Cost')),
|
|
2733
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-value' }, costValue),
|
|
2734
|
-
React.createElement('div', { className: 'uh-ios-summary-meta-caption' }, costCoverage),
|
|
2735
|
-
),
|
|
2736
|
-
),
|
|
2737
|
-
),
|
|
2738
|
-
React.createElement('div', { className: 'uh-ios-metrics' },
|
|
2739
|
-
card(tr('DeepSeek 账户余额', 'DeepSeek Account Balance'), balanceValue, balanceSub, 0, 'wallet'),
|
|
2740
|
-
card(scopedCountIsCalls ? tr('匹配调用次数', 'Matching Calls') : tr('总使用次数', 'Total Uses'), fmtCompact(animatedTurns), range === 'all' && !scopedCountIsCalls ? (language === 'en' ? agg.totals.sessions + ' sessions' : agg.totals.sessions + ' 个会话') : (language === 'en' ? (scopedCountIsCalls ? 'Calls in ' : 'Turns in ') + rangeLabel : rangeLabel + (scopedCountIsCalls ? '内的调用数' : '内的回合数')), 1, 'chart'),
|
|
2741
|
-
card(tr('连续使用', 'Current Streak'), language === 'en' ? st.streak + ' days' : st.streak + ' 天', language === 'en' ? 'Longest streak: ' + st.best + ' days' : '最长连续 ' + st.best + ' 天', 2, 'clock'),
|
|
2742
|
-
tokenCard,
|
|
2743
|
-
summaryRateMetric,
|
|
2744
|
-
),
|
|
2745
|
-
),
|
|
2746
|
-
React.createElement('div', { className: 'uh-token-semantics' },
|
|
2747
|
-
React.createElement(LineIcon, { name: 'cache', size: 16 }),
|
|
2748
|
-
tr('总处理 Token = 输入 + 输出 + 缓存读写 + 推理。缓存命中代表复用上下文,不等于新生成 Token 或实际费用。', 'Total tokens processed = input + output + cache reads/writes + reasoning. Cache hits represent reused context; they are not newly generated tokens or actual cost.'),
|
|
2749
|
-
),
|
|
2750
|
-
trendPanel,
|
|
2751
|
-
React.createElement('div', { className: 'uh-panel' },
|
|
2752
|
-
React.createElement('div', { className: 'uh-section-title' }, React.createElement(LineIcon, { name: 'calendar', size: 16 }), tr('使用热力图', 'Usage Heatmap')),
|
|
2753
|
-
React.createElement('div', { className: 'uh-hm-head' },
|
|
2754
|
-
React.createElement('div', { className: 'uh-chips' }, workspaces.map((w, i) => {
|
|
2755
|
-
const on = wsFilter === w.id
|
|
2756
|
-
return React.createElement('button', {
|
|
2757
|
-
key: w.id,
|
|
2758
|
-
className: 'uh-chip' + (on ? ' uh-on' : ''),
|
|
2759
|
-
onClick: () => toggleFilter(w.id),
|
|
2760
|
-
title: w.path,
|
|
2761
|
-
},
|
|
2762
|
-
React.createElement('span', { className: 'uh-dot', style: { background: wsColor(i) } }),
|
|
2763
|
-
React.createElement('span', { className: 'uh-chip-title' }, wsTitle(w.id)),
|
|
2764
|
-
)
|
|
2765
|
-
})),
|
|
2766
|
-
React.createElement('div', { className: 'uh-legend' },
|
|
2767
|
-
React.createElement('span', {}, tr('少', 'Less')),
|
|
2768
|
-
[0, 1, 2, 3, 4].map((l) => React.createElement('span', { key: l, className: 'uh-cell', style: { background: cellBg(l) } })),
|
|
2769
|
-
React.createElement('span', {}, tr('多', 'More')),
|
|
2770
|
-
),
|
|
2771
|
-
),
|
|
2772
|
-
React.createElement('div', { className: 'uh-hm-scroll' },
|
|
2773
|
-
React.createElement('div', { className: 'uh-months' }, monthLabels.map((m, i) => React.createElement('span', { key: i, style: { left: m.left } }, m.text))),
|
|
2774
|
-
React.createElement('div', { className: 'uh-hm-body' },
|
|
2775
|
-
React.createElement('div', { className: 'uh-wdays' }, weekdayLabels.map((w, i) => React.createElement('span', { key: i }, w))),
|
|
2776
|
-
React.createElement('div', { className: 'uh-grid' }, cellElements),
|
|
2777
|
-
),
|
|
2778
|
-
),
|
|
2779
|
-
React.createElement('div', { className: 'uh-note', style: { marginTop: 10 } }, tr('口径:每完成一个回合点亮一次(含子代理会话);悬停查看按工作区明细,点击工作区可筛选热力图与明细表。日期按本地时区。', 'Methodology: one cell lights up for each completed turn, including subagent sessions. Hover to view workspace details; click a workspace to filter the heatmap and detail tables. English dates and day boundaries use UTC.')),
|
|
2780
|
-
),
|
|
2781
|
-
React.createElement('div', { className: 'uh-detail-tabs', role: 'tablist', 'aria-label': tr('用量明细视图', 'Usage detail views') },
|
|
2782
|
-
[['logs', tr('请求日志', 'Request Logs'), 'list'], ['model', tr('模型统计', 'Model Stats'), 'chart'], ['workspace', tr('工作区统计', 'Workspace Stats'), 'folder']].map((entry) => React.createElement('button', { key: entry[0], type: 'button', role: 'tab', 'aria-selected': detailView === entry[0], className: 'uh-detail-tab' + (detailView === entry[0] ? ' uh-on' : ''), onClick: () => setDetailView(entry[0]) }, React.createElement(LineIcon, { name: entry[2], size: 14 }), entry[1])),
|
|
2783
|
-
),
|
|
2784
|
-
recordsPanel,
|
|
2785
|
-
),
|
|
2786
|
-
detailView === 'model' ? React.createElement('div', { className: 'uh-panel uh-ios-list-panel' },
|
|
2787
|
-
React.createElement('div', { className: 'uh-hm-head' },
|
|
2788
|
-
React.createElement('h3', { className: 'uh-tbl-title uh-title-with-icon', style: { margin: 0 } }, React.createElement(LineIcon, { name: 'chart', size: 16 }), language === 'en' ? 'Model Usage Details (' + rangeLabel + ')' : '模型用量明细(' + rangeLabel + ')'),
|
|
2789
|
-
React.createElement('div', { className: 'uh-range' },
|
|
2790
|
-
[['route', tr('混合查看', 'Combined View')], ['model', tr('按模型', 'By Model')], ['provider', tr('按供应商', 'By Provider')]].map((entry) => React.createElement('button', {
|
|
2791
|
-
key: entry[0], className: modelView === entry[0] ? 'uh-on' : '', onClick: () => setModelView(entry[0]),
|
|
2792
|
-
}, entry[1])),
|
|
2793
|
-
),
|
|
2794
|
-
),
|
|
2795
|
-
modelRows.length === 0
|
|
2796
|
-
? React.createElement('div', { className: 'uh-empty' }, tr('尚无带模型路由信息的用量记录', 'No usage records with model-routing information yet'))
|
|
2797
|
-
: React.createElement(React.Fragment, null,
|
|
2798
|
-
modelDonutChart,
|
|
2799
|
-
React.createElement('div', { className: 'uh-tbl-scroll' },
|
|
2800
|
-
React.createElement('div', { className: 'uh-model-hrow uh-hrow' },
|
|
2801
|
-
React.createElement('div', {}, modelColumnLabel),
|
|
2802
|
-
React.createElement('div', { className: 'uh-num' }, tr('调用', 'Calls')),
|
|
2803
|
-
React.createElement('div', { className: 'uh-num' }, tr('输入', 'Input')),
|
|
2804
|
-
React.createElement('div', { className: 'uh-num' }, tr('缓存命中', 'Cache Hits')),
|
|
2805
|
-
React.createElement('div', { className: 'uh-num' }, tr('输出', 'Output')),
|
|
2806
|
-
React.createElement('div', { className: 'uh-num' }, tr('推理', 'Reasoning')),
|
|
2807
|
-
React.createElement('div', { className: 'uh-num' }, tr('总处理', 'Total Processed')),
|
|
2808
|
-
React.createElement('div', { className: 'uh-num' }, tr('成本', 'Cost')),
|
|
2809
|
-
React.createElement('div', { className: 'uh-num' }, tr('命中率', 'Hit Rate')),
|
|
2810
|
-
),
|
|
2811
|
-
modelElements,
|
|
2812
|
-
),
|
|
2813
|
-
),
|
|
2814
|
-
React.createElement('div', { className: 'uh-note', style: { marginTop: 10 } }, language === 'en' ? modelViewLabel + ': Combined View distinguishes “Provider / Model”; By Model merges identically named models across providers; By Provider aggregates all of a provider’s models. Historical records without routing information are grouped as “Unknown.”' : modelViewLabel + ':混合查看按“供应商 / 模型”区分;按模型会跨供应商合并同名模型;按供应商则汇总其全部模型。缺少路由信息的历史记录会归为“未知”。'),
|
|
2815
|
-
) : null,
|
|
2816
|
-
detailView === 'workspace' ? React.createElement('div', { className: 'uh-panel uh-ios-list-panel' },
|
|
2817
|
-
React.createElement('h3', { className: 'uh-tbl-title uh-title-with-icon' }, React.createElement(LineIcon, { name: 'folder', size: 16 }), language === 'en' ? 'Workspace Details (' + rangeLabel + ')' : '工作区明细(' + rangeLabel + ')'),
|
|
2818
|
-
rows.length === 0
|
|
2819
|
-
? React.createElement('div', { className: 'uh-empty' }, tr('该时间范围内没有使用记录', 'No usage records in this time range'))
|
|
2820
|
-
: React.createElement(React.Fragment, null,
|
|
2821
|
-
workspaceDonutChart,
|
|
2822
|
-
React.createElement('div', { className: 'uh-tbl-scroll' },
|
|
2823
|
-
React.createElement('div', { className: 'uh-hrow' },
|
|
2824
|
-
React.createElement('div', {}, tr('工作区', 'Workspace')),
|
|
2825
|
-
React.createElement('div', { className: 'uh-num' }, tr('回合', 'Turns')),
|
|
2826
|
-
React.createElement('div', { className: 'uh-num' }, tr('输入', 'Input')),
|
|
2827
|
-
React.createElement('div', { className: 'uh-num' }, tr('缓存命中', 'Cache Hits')),
|
|
2828
|
-
React.createElement('div', { className: 'uh-num' }, tr('输出', 'Output')),
|
|
2829
|
-
React.createElement('div', { className: 'uh-num' }, tr('推理', 'Reasoning')),
|
|
2830
|
-
React.createElement('div', { className: 'uh-num' }, tr('总处理', 'Total Processed')),
|
|
2831
|
-
React.createElement('div', { className: 'uh-num' }, tr('成本', 'Cost')),
|
|
2832
|
-
React.createElement('div', { className: 'uh-num' }, tr('命中率', 'Hit Rate')),
|
|
2833
|
-
React.createElement('div', { className: 'uh-num' }, tr('占比', 'Share')),
|
|
2834
|
-
),
|
|
2835
|
-
rowElements,
|
|
2836
|
-
),
|
|
2837
|
-
),
|
|
2838
|
-
) : null,
|
|
2839
|
-
),
|
|
2840
|
-
tip,
|
|
2841
|
-
)
|
|
2842
|
-
}
|
|
2843
|
-
|
|
2844
|
-
class UsageDashboardBoundary extends React.Component {
|
|
2845
|
-
constructor(props) { super(props); this.state = { error: null, resetKey: props.resetKey } }
|
|
2846
|
-
static getDerivedStateFromError(error) { return { error } }
|
|
2847
|
-
componentDidUpdate(prevProps) {
|
|
2848
|
-
if (prevProps.resetKey !== this.props.resetKey && this.state.error !== null) this.setState({ error: null, resetKey: this.props.resetKey })
|
|
2849
|
-
}
|
|
2850
|
-
render() {
|
|
2851
|
-
if (this.state.error !== null) return this.props.fallback(this.state.error)
|
|
2852
|
-
return this.props.children
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
function UsageSidebarEntry(props) {
|
|
2856
|
-
const [open, setOpen] = React.useState(false)
|
|
2857
|
-
const [dashboardResetKey, setDashboardResetKey] = React.useState(0)
|
|
2858
|
-
const [language, setLanguage] = React.useState(storedLanguage)
|
|
2859
|
-
const tr = (zh, en) => language === 'en' ? en : zh
|
|
2860
|
-
const dashboardFallback = () => React.createElement('div', { className: 'uh-boundary-fallback', role: 'alert' },
|
|
2861
|
-
React.createElement('div', { className: 'uh-boundary-title' }, tr('用量统计暂时无法显示', 'Usage statistics is temporarily unavailable')),
|
|
2862
|
-
React.createElement('div', { className: 'uh-boundary-note' }, tr('当前范围加载失败,入口仍然可用。', 'The selected range failed to render; the sidebar entry is still available.')),
|
|
2863
|
-
React.createElement('div', { className: 'uh-actions' },
|
|
2864
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: () => setDashboardResetKey((value) => value + 1) }, React.createElement(LineIcon, { name: 'refresh', size: 14 }), tr('重试', 'Retry')),
|
|
2865
|
-
React.createElement('button', { type: 'button', className: 'uh-refresh', onClick: () => setOpen(false) }, React.createElement(LineIcon, { name: 'close', size: 14 }), tr('关闭', 'Close')),
|
|
2866
|
-
),
|
|
2867
|
-
)
|
|
2868
|
-
const changeLanguage = (next) => {
|
|
2869
|
-
const value = next === 'en' ? 'en' : 'zh'
|
|
2870
|
-
setLanguage(value)
|
|
2871
|
-
persistLanguage(value)
|
|
2872
|
-
}
|
|
2873
|
-
React.useEffect(() => {
|
|
2874
|
-
if (!open) return undefined
|
|
2875
|
-
const closeOnEscape = (event) => { if (event.key === 'Escape') setOpen(false) }
|
|
2876
|
-
document.addEventListener('keydown', closeOnEscape)
|
|
2877
|
-
return () => document.removeEventListener('keydown', closeOnEscape)
|
|
2878
|
-
}, [open])
|
|
2879
|
-
return React.createElement(React.Fragment, null,
|
|
2880
|
-
React.createElement('button', {
|
|
2881
|
-
type: 'button', className: 'uh-side-entry', title: tr('用量统计', 'Usage Statistics'), 'aria-label': tr('用量统计', 'Usage Statistics'), onClick: () => setOpen(true),
|
|
2882
|
-
}, React.createElement('span', { className: 'uh-side-entry-icon' }, React.createElement(LineIcon, { name: 'chart', size: 17 })), props.wide ? React.createElement('span', { className: 'uh-side-entry-label' }, tr('用量统计', 'Usage Statistics')) : null),
|
|
2883
|
-
open ? React.createElement('div', { className: 'uh-side-modal', role: 'presentation', onMouseDown: (event) => { if (event.target === event.currentTarget) setOpen(false) } },
|
|
2884
|
-
React.createElement('div', { className: 'uh-side-dialog', role: 'dialog', 'aria-modal': true, 'aria-label': tr('用量统计', 'Usage Statistics') },
|
|
2885
|
-
React.createElement('div', { className: 'uh-side-dialog-head' },
|
|
2886
|
-
React.createElement('button', { className: 'uh-refresh uh-close-button', type: 'button', title: tr('关闭用量统计', 'Close Usage Statistics'), 'aria-label': tr('关闭用量统计', 'Close Usage Statistics'), onClick: () => setOpen(false) }, React.createElement(LineIcon, { name: 'close', size: 18 })),
|
|
2887
|
-
),
|
|
2888
|
-
React.createElement(UsageDashboardBoundary, { resetKey: dashboardResetKey, fallback: dashboardFallback }, React.createElement(UsagePage, { timerCtx: props.timerCtx, language, onLanguageChange: changeLanguage })),
|
|
2889
|
-
),
|
|
2890
|
-
) : null,
|
|
2891
|
-
)
|
|
2892
|
-
}
|
|
2893
|
-
|
|
2894
|
-
exports.inject = ['timer', 'slots']
|
|
2895
|
-
exports.apply = (ctx) => {
|
|
2896
|
-
const slots = ctx.get('slots')
|
|
2897
|
-
const timer = ctx.get('timer')
|
|
2898
|
-
if (slots === undefined || timer === undefined) return
|
|
2899
|
-
slots.inject('sidebar.footer.action', () => slots.register(
|
|
2900
|
-
{ name: 'sidebar.footer.action', id: 'all-usage', order: 10 },
|
|
2901
|
-
(props) => React.createElement(UsageSidebarEntry, { wide: props.wide, timerCtx: timer }),
|
|
2902
|
-
))
|
|
2903
|
-
}
|
|
2904
|
-
return module.exports;
|
|
2905
|
-
}
|
|
2906
|
-
});
|
|
1
|
+
window.__ModuleLoader__.load({id:"dsh-all-usage",factory:e=>{var t={},a=t;Object.defineProperty(a,Symbol.toStringTag,{value:"Module"});const r=e("react");function n(e){return String(e).padStart(2,"0")}function i(e,t){const a=t?e.getUTCFullYear():e.getFullYear(),r=t?e.getUTCMonth():e.getMonth(),i=t?e.getUTCDate():e.getDate();return a+"-"+n(r+1)+"-"+n(i)}function s(e,t,a){return a?new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()+t)):new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function l(e,t){if("string"!=typeof e||!/^\d{4}-\d{2}-\d{2}$/.test(e))return!1;const a=Number(e.slice(0,4)),r=Number(e.slice(5,7)),n=Number(e.slice(8,10)),s=t?new Date(Date.UTC(a,r-1,n)):new Date(a,r-1,n);return Number.isFinite(s.getTime())&&i(s,t)===e}function o(e,t){if(null===e||"object"!=typeof e)return null;const a=e.start,r=e.end;return!l(a,t)||!l(r,t)||a>r?null:{start:a,end:r}}function c(e,t){let a=t;if(Array.isArray(e))for(const r of e)r&&l(r.date,!0)&&r.date<=t&&r.date<a&&(a=r.date);return{min:a,max:t}}function u(){let e=0;return{next:()=>(e+=1,e),isCurrent:t=>t===e}}function d(e){if(null===e||"object"!=typeof e)return null;const t="string"==typeof e.instanceId?e.instanceId:"",a="number"==typeof e.revision&&Number.isFinite(e.revision)?e.revision:null;return""===t||null===a?null:t+":"+a}function p(e,t,a){const r=e&&"object"==typeof e?e[t]:void 0;return"number"==typeof r&&Number.isFinite(r)?r:a}function m(e){return null!==e&&"object"==typeof e&&["dataRevision","metadataRevision","scanRevision","pricingRevision"].every(t=>null!==p(e,t,null))}function h(e){if(null===e||"object"!=typeof e||"string"!=typeof e.instanceId||""===e.instanceId)return null;if("string"==typeof e.queryRevision&&""!==e.queryRevision)return e.instanceId+":"+e.queryRevision;const t=p(e,"dataRevision",p(e,"revision",null)),a=p(e,"pricingRevision",0);return null===t?null:e.instanceId+":"+t+":"+a}function g(e){return String(Math.round(10*e)/10)}function b(e){return"number"==typeof e&&Number.isFinite(e)?e<1e3?String(e):e<1e6?g(e/1e3)+"k":e<1e9?g(e/1e6)+"M":g(e/1e9)+"B":"0"}function f(e,t){return"number"==typeof e&&Number.isFinite(e)?Math.round(e).toLocaleString("en"===t?"en-US":"zh-CN"):"0"}function x(e,t){const a=e+t;return a<=0?0:t/a*100}function y(e){const t=e.size||16,a={fill:"none",stroke:"currentColor",strokeWidth:1.8,strokeLinecap:"round",strokeLinejoin:"round"},n={edit:[r.createElement("path",{key:"a",d:"M12.2 3.4l2.4 2.4M4 16l2.8-.6L15 7.2a1.7 1.7 0 0 0-2.4-2.4L4.4 13z",...a})],export:[r.createElement("path",{key:"a",d:"M12 3v11M8 7l4-4 4 4M5 13v5h14v-5",...a})],refresh:[r.createElement("path",{key:"a",d:"M19 9a7 7 0 1 0 1.1 5.2M19 4v5h-5",...a})],close:[r.createElement("path",{key:"a",d:"M6 6l12 12M18 6L6 18",...a})],chart:[r.createElement("path",{key:"a",d:"M4 19V5M4 19h16M7 15l3-4 3 2 5-7",...a})],list:[r.createElement("path",{key:"a",d:"M6 6h12M6 12h12M6 18h12",...a}),r.createElement("circle",{key:"b",cx:3.5,cy:6,r:.7,fill:"currentColor"}),r.createElement("circle",{key:"c",cx:3.5,cy:12,r:.7,fill:"currentColor"}),r.createElement("circle",{key:"d",cx:3.5,cy:18,r:.7,fill:"currentColor"})],cache:[r.createElement("path",{key:"a",d:"M12 4l7 4-7 4-7-4 7-4zM5 12l7 4 7-4M5 16l7 4 7-4",...a})],wallet:[r.createElement("path",{key:"a",d:"M4 7.5A2.5 2.5 0 0 1 6.5 5H18v14H6.5A2.5 2.5 0 0 1 4 16.5zM4 8h14M14 13h.01",...a})],clock:[r.createElement("path",{key:"a",d:"M12 6v6l4 2M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z",...a})],folder:[r.createElement("path",{key:"a",d:"M3.5 7.5h6l2 2h9v8.5a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2z",...a})],language:[r.createElement("circle",{key:"a",cx:12,cy:12,r:8,...a}),r.createElement("path",{key:"b",d:"M4 12h16M12 4c2.1 2.2 3.2 4.9 3.2 8S14.1 17.8 12 20M12 4C9.9 6.2 8.8 8.9 8.8 12s1.1 5.8 3.2 8",...a})],chevron:[r.createElement("path",{key:"a",d:"M7 10l5 5 5-5",...a})],check:[r.createElement("path",{key:"a",d:"M5 12.5l4.2 4.1L19 7.3",...a})],plus:[r.createElement("path",{key:"a",d:"M12 5v14M5 12h14",...a})],calendar:[r.createElement("path",{key:"a",d:"M6 4v3M18 4v3M4 9h16M5 6h14a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z",...a})]};return r.createElement("svg",{className:"uh-line-icon "+(e.className||""),width:t,height:t,viewBox:"0 0 24 24","aria-hidden":!0},n[e.name]||n.chart)}function v(e,t){if("en"===t||"number"!=typeof e||!Number.isFinite(e)||e<1e4)return"";const a=e>=1e8?e/1e8:e/1e4,r=Math.round(1e3*a)/1e3;return String(r)+(e>=1e8?"亿":"万")}function w(e,t,a){const n=v(t,a);return r.createElement(r.Fragment,null,e,n?r.createElement("span",{className:"uh-unit"},n):null)}function k(e,t,a){return null==t?"—":("CNY"===e?"¥":"USD"===e?"$":e+" ")+t.toLocaleString("en"===a?"en-US":"zh-CN",{minimumFractionDigits:4,maximumFractionDigits:4})}function E(e){const t=("number"==typeof e?String(e):"string"==typeof e?e.trim().toLowerCase():"").match(/^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/);if(!t)return{digits:0n,scale:0};let a=(t[1]||"")+(t[2]||""),r=(t[2]||"").length-(t[3]?Number(t[3]):0);for(r<0&&(a+="0".repeat(-r),r=0),r>a.length&&(a="0".repeat(r-a.length+1)+a);r>0&&a.length>1&&a.endsWith("0");)a=a.slice(0,-1),r-=1;return{digits:BigInt(a.replace(/^0+(?=\d)/,"")||"0"),scale:r}}function N(e){const t=E(e);if(0n===t.digits)return"0";const a=t.digits.toString();if(0===t.scale)return a;const r=a.padStart(t.scale+1,"0"),n=r.length-t.scale;return r.slice(0,n)+"."+r.slice(n)}function M(e,t){const a=E(e),r=E(t),n=Math.max(a.scale,r.scale);return N((a.digits*10n**BigInt(n-a.scale)+r.digits*10n**BigInt(n-r.scale)).toString()+(n>0?"e-"+n:""))}function C(){return{currency:"USD",input:"0",output:"0",cacheRead:"0",cacheWrite:"0",baseTotal:"0",total:"0",pricedCalls:0,unpricedCalls:0,ambiguousCalls:0,unsupportedCalls:0}}function S(e){const t=e&&e.cost&&"object"==typeof e.cost?e.cost:e&&"object"==typeof e?e:{},a={currency:"USD",input:"0",output:"0",cacheRead:"0",cacheWrite:"0",baseTotal:"0",total:"0",pricedCalls:0,unpricedCalls:0,ambiguousCalls:0,unsupportedCalls:0};if(a.currency="string"==typeof t.currency&&""!==t.currency?t.currency:"USD",t.breakdown&&"object"==typeof t.breakdown)return"priced"===t.status?(a.input=N(t.breakdown.input),a.output=N(t.breakdown.output),a.cacheRead=N(t.breakdown.cacheRead),a.cacheWrite=N(t.breakdown.cacheWrite),a.baseTotal=N(t.baseTotal),a.total=N(t.total),a.pricedCalls=1):"ambiguous"===t.status?a.ambiguousCalls=1:"unsupported"===t.status?a.unsupportedCalls=1:a.unpricedCalls=1,a;for(const e of["input","output","cacheRead","cacheWrite","baseTotal","total"])a[e]=N(t[e]);for(const e of["pricedCalls","unpricedCalls","ambiguousCalls","unsupportedCalls"])a[e]=Number.isFinite(t[e])?t[e]:0;return a}function z(e,t){const a=S(t);for(const t of["input","output","cacheRead","cacheWrite","baseTotal","total"])e[t]=M(e[t],a[t]);return e.pricedCalls+=a.pricedCalls,e.unpricedCalls+=a.unpricedCalls,e.ambiguousCalls+=a.ambiguousCalls,e.unsupportedCalls+=a.unsupportedCalls,e}function A(e,t){const a=S(e);if(a.pricedCalls<=0)return"—";const r=Number(a.total);return Number.isFinite(r)?k(a.currency,r,t):a.currency+" "+a.total}function T(e){const t=e&&e.config&&"object"==typeof e.config?e.config:{},a=t.sync&&"object"==typeof t.sync?t.sync:{};return{sync:{autoEnabled:!0===a.autoEnabled,intervalMs:Number.isFinite(a.intervalMs)?a.intervalMs:216e5},providerAliases:t.providerAliases&&"object"==typeof t.providerAliases?Object.assign({},t.providerAliases):{},mappings:Array.isArray(t.mappings)?t.mappings.map(e=>Object.assign({},e)):[],overrides:Array.isArray(t.overrides)?t.overrides.map(e=>Object.assign({},e,{tiers:Array.isArray(e.tiers)?e.tiers.map(e=>Object.assign({},e)):[]})):[]}}function R(e,t){const a=T(t);if(null===e||"object"!=typeof e)return a;const r=T({config:e});return Object.assign({},a,{providerAliases:r.providerAliases,mappings:r.mappings,overrides:r.overrides})}function j(e,t){const a={priced:["已计价","priced"],unpriced:["未计价","unpriced"],ambiguous:["待确认","ambiguous"],unsupported:["不支持","unsupported"]},r=a[e]||a.unpriced;return"en"===t?r[1]:r[0]}function D(e){const t=String(e??"").trim().toLowerCase();if(""===t||t.length>128)return!1;const a=t.match(/^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/);if(!a)return!1;const r=a[3]?Number(a[3]):0;if(!Number.isSafeInteger(r)||Math.abs(r)>24)return!1;let n=(a[1]||"")+(a[2]||""),i=(a[2]||"").length-r;return n=n.replace(/^0+(?=\d)/,""),i<0&&(n+="0".repeat(-i),i=0),i>n.length&&(n="0".repeat(i-n.length+1)+n),n.length<=40}function I(e,t){const a=Number(e&&e.size);return!(!e||"context"!==e.type||!Number.isSafeInteger(a)||a<=t||a>1e9)&&["input","output","cacheRead","cacheWrite"].every(t=>D(e[t]))}function U(e){return String(e||"").trim().toLowerCase().replace(/^.*\//,"").split(":")[0]}function F(e,t){const a=e.split("-"),r="en"===t,n=r?new Date(Date.UTC(Number(a[0]),Number(a[1])-1,Number(a[2]))):new Date(Number(a[0]),Number(a[1])-1,Number(a[2])),i=r?n.getUTCMonth():n.getMonth(),s=r?n.getUTCDay():n.getDay();if("en"===t){const e=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][s];return["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][i]+" "+Number(a[2])+", "+a[0]+" ("+e+", UTC)"}const l=["周日","周一","周二","周三","周四","周五","周六"][s];return a[0]+"年"+Number(a[1])+"月"+Number(a[2])+"日 "+l}function O(e,t,a){if("en"===a){const a=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][t];return 0===t?e+" "+a:a}return 0===t?e+"年1月":t+1+"月"}function W(e){return e>=10?4:e>=6?3:e>=3?2:e>=1?1:0}const L=[20,45,70,96];function q(e){return e<=0?"var(--dsw-alias-bg-layer-2)":"color-mix(in srgb, #2ea043 "+L[e-1]+"%, var(--dsw-alias-bg-layer-2))"}function P(e){return"hsl("+137*e%360+", 70%, 55%)"}function K(e,t,a,r){const n=a&&Array.isArray(e&&e.byDayUtc)?e.byDayUtc:Array.isArray(e&&e.byDay)?e.byDay:[],l=i(new Date,a);if("custom"===t){const e=o(r,a);return null===e?null:{start:e.start,end:e.end}}if("today"===t)return{start:l,end:l};if("30d"===t)return{start:i(s(new Date,-29,a),a),end:l};if("90d"===t)return{start:i(s(new Date,-89,a),a),end:l};const u=c(n,l);return{start:u.min,end:u.max}}function B(e){return null===e?"":JSON.stringify({start:e.start,end:e.end,utc:!0===e.utc,workspaceId:e.workspaceId||null,provider:e.provider||null,modelKey:e.modelKey||null})}function H(e){const t=e&&e.tokens&&"object"==typeof e.tokens?e.tokens:e||{};return{input:Number.isFinite(t.input)?t.input:0,output:Number.isFinite(t.output)?t.output:0,cacheRead:Number.isFinite(t.cacheRead)?t.cacheRead:0,cacheWrite:Number.isFinite(t.cacheWrite)?t.cacheWrite:0,reasoning:Number.isFinite(t.reasoning)?t.reasoning:0}}function V(e,t,a){return e&&Number.isFinite(e.time)?function(e,t,a){const r=a?{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}:{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1};return"en"===t&&(r.timeZone="UTC"),new Date(e).toLocaleString("en"===t?"en-US":"zh-CN",r)}(e.time,t,a):e&&"string"==typeof e.date?a?F(e.date,t):e.date.slice(5):""}function Y(e,t){return e&&Number.isFinite(e.time)?String(e.time):e&&"string"==typeof e.date?e.date:String(t)}function J(e){return e&&"string"==typeof e.date?e.date:""}function X(e,t,a){const r="string"==typeof e.actualModel&&""!==e.actualModel?e.actualModel:"string"==typeof e.requestedModel&&""!==e.requestedModel?e.requestedModel:"",n="string"==typeof e.model&&""!==e.model?e.model:a,i=n.indexOf(" / "),s=("string"==typeof e.provider&&""!==e.provider?e.provider:"")||(i>0?n.slice(0,i):t),l=s!==t?s+" / ":"",o=""!==r?n:""!==l&&n.startsWith(l)?n.slice(l.length):i>0?n.slice(i+3):n;return{provider:s,model:function(e){const t=String(e||"").trim();return t.includes(" / ")?t:U(t)}(r||o)||a}}function Z(e,t){const[a,n]=r.useState({value:0,done:!1});return r.useEffect(()=>{if("number"!=typeof e||!Number.isFinite(e)||e<=0)return void n({value:0,done:!1});if(a.done)return void n({value:e,done:!0});const r=Date.now(),i=t.interval(()=>{const t=Math.min(1,(Date.now()-r)/700),a=1-Math.pow(1-t,3);t>=1?(i(),n({value:e,done:!0})):n({value:Math.round(e*a),done:!1})},32);return i},[e]),a.value}function G(e){if(!Array.isArray(e)||0===e.length)return"";if(1===e.length)return"M"+e[0].x.toFixed(2)+" "+e[0].y.toFixed(2);const t=[];for(let a=0;a<e.length-1;a+=1){const r=e[a+1].x-e[a].x;t.push(0===r?0:(e[a+1].y-e[a].y)/r)}const a=new Array(e.length).fill(0);a[0]=t[0],a[e.length-1]=t[t.length-1];for(let r=1;r<e.length-1;r+=1){const e=t[r-1],n=t[r];a[r]=e*n<=0?0:(e+n)/2}for(let e=0;e<t.length;e+=1){if(0===t[e]){a[e]=0,a[e+1]=0;continue}const r=a[e]/t[e],n=a[e+1]/t[e],i=r*r+n*n;if(i>9){const s=3/Math.sqrt(i);a[e]=s*r*t[e],a[e+1]=s*n*t[e]}}let r="M"+e[0].x.toFixed(2)+" "+e[0].y.toFixed(2);for(let t=0;t<e.length-1;t+=1){const n=e[t+1].x-e[t].x,i=e[t].x+n/3,s=e[t].y+a[t]*n/3,l=e[t+1].x-n/3,o=e[t+1].y-a[t+1]*n/3;r+=" C"+i.toFixed(2)+" "+s.toFixed(2)+" "+l.toFixed(2)+" "+o.toFixed(2)+" "+e[t+1].x.toFixed(2)+" "+e[t+1].y.toFixed(2)}return r}function $(e){if(!Array.isArray(e)||e.length<2)return 1;let t=0;for(let a=1;a<e.length;a+=1){const r=e[a].x-e[a-1].x,n=e[a].y-e[a-1].y;t+=Math.sqrt(r*r+n*n)}return Math.max(1,Math.ceil(1.35*t+2))}const _=["#0a84ff","#30d158","#bf5af2","#ff9f0a","#ff375f","#64d2ff"],Q={total:"#f4c542",input:"#5aa9ff",cacheRead:"#44d483",cacheWrite:"#d98bff",output:"#ff8c66",reasoning:"#aab4c4"},ee={total:.2,input:.16,cacheRead:.18,cacheWrite:.14,output:.16,reasoning:.1};function te(e,t){const a=v(e,t);return""!==a?a:b(e)}function ae(e,t){return te(e,t)+("en"===t?" tokens":" Token")}function re(e,t,a,r,n){const i=Math.max(0,n-r),s=r=>({x:e+a*Math.cos(r),y:t+a*Math.sin(r)}),l=s(r);if(i>=2*Math.PI-1e-4){const e=s(r+Math.PI);return"M"+l.x.toFixed(2)+" "+l.y.toFixed(2)+" A"+a+" "+a+" 0 1 1 "+e.x.toFixed(2)+" "+e.y.toFixed(2)+" A"+a+" "+a+" 0 1 1 "+l.x.toFixed(2)+" "+l.y.toFixed(2)}const o=s(n);return"M"+l.x.toFixed(2)+" "+l.y.toFixed(2)+" A"+a+" "+a+" 0 "+(i>Math.PI?1:0)+" 1 "+o.x.toFixed(2)+" "+o.y.toFixed(2)}const ne=r.memo(function(e){const t="en"===e.language?"en":"zh",[a,n]=r.useState(null),i=r.useRef(null),s=r.useRef(null),l=r.useRef(null),o=r.useRef(null),c=r.useMemo(()=>function(e,t,a=5){const r=Math.max(1,Number.isInteger(a)?a:5),n=(Array.isArray(e)?e:[]).map((e,t)=>({label:e&&void 0!==e.label?String(e.label):"",value:Number(e&&e.value),color:e&&"string"==typeof e.color&&""!==e.color?e.color:_[t%_.length],cost:S(e)})).filter(e=>""!==e.label&&Number.isFinite(e.value)&&e.value>0).sort((e,t)=>t.value-e.value),i=n.reduce((e,t)=>e+t.value,0);if(i<=0)return{total:0,segments:[]};const s=n.slice(0,r),l=n.slice(r),o=l.reduce((e,t)=>e+t.value,0);if(o>0){const e={currency:"USD",input:"0",output:"0",cacheRead:"0",cacheWrite:"0",baseTotal:"0",total:"0",pricedCalls:0,unpricedCalls:0,ambiguousCalls:0,unsupportedCalls:0};for(const t of l)z(e,t.cost);s.push({label:t+" ("+(n.length-r)+")",value:o,color:"#b8c2cf",cost:e,other:!0})}let c=-Math.PI/2;return{total:i,segments:s.map((e,t)=>{const a=e.value/i*Math.PI*2,r=s.length>1?Math.min(.018,a/3):0,n=c+r,l=c+a-r;return c+=a,{...e,index:t,percentage:e.value/i*100,startAngle:l<=n?c-a:n,endAngle:l<=n?c:l}})}}(e.items,"en"===t?"Other":"其他"),[e.items,t]),u=null===a?null:c.segments[a]||null,d=e=>(e>=10?Math.round(e):Math.round(10*e)/10)+"%",p=r.useCallback(()=>{l.current=null,null!==o.current&&(window.clearTimeout(o.current),o.current=null);const e=i.current,t=s.current;if(null!==e&&null!==t){if(t.fixed)e.style.left=t.left+"px",e.style.top=t.top+"px";else{const a=t.visual?.getBoundingClientRect();if(!a)return;const r=198,n=82;e.style.left=Math.max(8,Math.min(Math.max(8,a.width-r),t.x-a.left+14))+"px",e.style.top=Math.max(8,Math.min(Math.max(8,a.height-n),t.y-a.top+14))+"px"}e.style.visibility="visible"}},[]),m=r.useCallback(e=>{s.current=e,null===l.current&&(l.current=window.requestAnimationFrame(p),o.current=window.setTimeout(()=>{null!==l.current&&(window.cancelAnimationFrame(l.current),p())},80))},[p]),h=r.useCallback(e=>{m({x:e.clientX,y:e.clientY,visual:e.currentTarget.ownerSVGElement?.parentElement})},[m]),g=r.useCallback(()=>{n(null),s.current=null},[]);return r.useEffect(()=>()=>{null!==l.current&&window.cancelAnimationFrame(l.current),null!==o.current&&window.clearTimeout(o.current)},[]),r.useEffect(()=>{null!==a&&null!==s.current&&null===l.current&&(l.current=window.requestAnimationFrame(p))},[a,p]),c.total<=0?null:r.createElement("div",{className:"uh-donut-chart","aria-label":e.title},r.createElement("div",{className:"uh-donut-title"},r.createElement(y,{name:e.icon||"chart",size:16}),e.title),r.createElement("div",{className:"uh-donut-layout"},r.createElement("div",{className:"uh-donut-visual"},r.createElement("svg",{className:"uh-donut-svg",viewBox:"0 0 260 260",role:"img","aria-label":e.title+" "+ae(c.total,t)},r.createElement("circle",{cx:130,cy:130,r:77.5,className:"uh-donut-track",fill:"none",stroke:"var(--dsw-alias-bg-layer-2)",strokeWidth:33}),c.segments.map(e=>r.createElement("path",{key:"donut-"+e.index,d:re(130,130,77.5,e.startAngle,e.endAngle),className:"uh-donut-segment"+(a===e.index?" uh-active":""),fill:"none",stroke:e.color,strokeWidth:33,strokeLinecap:"butt",strokeLinejoin:"round",pathLength:1,style:{animationDelay:90*e.index+"ms"},tabIndex:0,"aria-label":e.label+" "+ae(e.value,t)+" "+d(e.percentage)+" "+A(e.cost,t),onMouseEnter:t=>{n(e.index),h(t)},onMouseMove:h,onMouseLeave:g,onFocus:()=>{n(e.index),m({fixed:!0,left:12,top:12})},onBlur:g}))),u?r.createElement("div",{ref:i,className:"uh-donut-tooltip",style:{visibility:"hidden"}},r.createElement("span",{className:"uh-donut-dot",style:{background:u.color}}),r.createElement("div",{},r.createElement("strong",{},u.label),r.createElement("span",{},ae(u.value,t)+" · "+d(u.percentage)),r.createElement("span",{className:"uh-donut-tooltip-cost"},A(u.cost,t)))):null,r.createElement("div",{className:"uh-donut-center"},r.createElement("strong",{},te(c.total,t)),r.createElement("span",{},"en"===t?"tokens":"Token"))),r.createElement("div",{className:"uh-donut-legend",role:"list"},c.segments.map(e=>r.createElement("div",{key:"legend-"+e.index,className:"uh-donut-legend-row",role:"listitem"},r.createElement("span",{className:"uh-donut-dot",style:{background:e.color}}),r.createElement("div",{className:"uh-donut-legend-copy"},r.createElement("strong",{title:e.label},e.label)),r.createElement("div",{className:"uh-donut-legend-metrics"},r.createElement("span",{},ae(e.value,t)),r.createElement("span",{className:"uh-donut-cost"},A(e.cost,t))),r.createElement("strong",{className:"uh-donut-percent"},d(e.percentage)))))))});function ie(e,t){return{total:"en"===t?"Total":"总处理",input:"en"===t?"Input":"输入",cacheRead:"en"===t?"Cache hits":"缓存命中",cacheWrite:"en"===t?"Cache writes":"缓存写入",output:"en"===t?"Output":"输出",reasoning:"en"===t?"Reasoning":"推理"}[e]||e}const se=r.memo(function(e){const t="en"===e.language?"en":"zh",a=(e,a)=>"en"===t?a:e,n=Array.isArray(e.rows)?e.rows:[],i=Array.isArray(e.visible)&&e.visible.length>0?e.visible:["total"],[s,l]=r.useState(null),[o,c]=r.useState(null),u=280,d=r.useMemo(()=>function(e,t,a=900,r=250){const n=Array.isArray(t)&&t.length>0?t:["total"],i={left:46,right:14,top:14,bottom:30},s=Math.max(1,a-i.left-i.right),l=Math.max(1,r-i.top-i.bottom),o=(Array.isArray(e)?e:[]).flatMap(e=>n.map(t=>"total"===t?e.total:e.tokens[t]||0)),c=Math.max(1,...o),u={};for(const t of n)u[t]=(Array.isArray(e)?e:[]).map((a,r)=>({x:i.left+(e.length>1?r*s/(e.length-1):s/2),y:i.top+l-("total"===t?a.total:a.tokens[t]||0)/c*l,value:"total"===t?a.total:a.tokens[t]||0}));return{width:a,height:r,padding:i,max:c,points:u}}(n,i,900,u),[n,i]),p=Q,m=ee,h=u-d.padding.bottom,g=r.useMemo(()=>{const e={};for(const t of i){const a=d.points[t]||[],r=G(a);e[t]={line:r,area:0===a.length?"":r+" L"+a[a.length-1].x.toFixed(2)+" "+h+" L"+a[0].x.toFixed(2)+" "+h+" Z",length:$(a)}}return e},[d,i,h]),f=r.useMemo(()=>n.length<=1?[0]:Array.from(new Set([0,Math.floor((n.length-1)/4),Math.floor((n.length-1)/2),Math.floor(3*(n.length-1)/4),n.length-1])),[n]),x=!e.loading&&!e.error&&n.length>0,v=n.length>0&&Number.isFinite(n[0].time)?a("每小时 Token 使用趋势,选择小时查看当天请求日志","Hourly Token usage trend; select an hour to view request logs"):a("每日 Token 使用趋势,选择日期查看请求日志","Daily Token usage trend; select a date to view request logs"),w=(null===s||n[s],null===s?null:(d.points[i[0]]||[])[s]||null),k=null===o?null:n[o]||null,E=null===o?null:(d.points[i[0]]||[])[o]||null,N=null!==s&&null!==k&&null!==E,M=null!==E&&E.x>612?" uh-left":" uh-right",C=null===E?void 0:{left:(E.x/900*100).toFixed(2)+"%",top:Math.max(23,Math.min(77,E.y/u*100)).toFixed(2)+"%"},S=e=>{l(e),c(e)},z=e.loading?r.createElement("div",{className:"uh-trend-stage uh-trend-loading",role:"status","aria-label":a("正在加载趋势","Loading trend")},r.createElement("span",{className:"uh-trend-spinner","aria-hidden":!0})):e.error?r.createElement("div",{className:"uh-trend-stage uh-trend-message",role:"alert"},e.error):0===n.length?r.createElement("div",{className:"uh-trend-stage uh-trend-message"},a("该范围内暂无趋势数据","No trend data in this range")):r.createElement("div",{className:"uh-trend-chart-wrap"},r.createElement("svg",{className:"uh-trend-svg",viewBox:"0 0 900 "+u,role:"group","aria-label":v},[0,.5,1].map(e=>r.createElement(r.Fragment,{key:e},r.createElement("line",{x1:d.padding.left,x2:900-d.padding.right,y1:d.padding.top+(u-d.padding.top-d.padding.bottom)*e,y2:d.padding.top+(u-d.padding.top-d.padding.bottom)*e,className:"uh-trend-grid"}),r.createElement("text",{x:d.padding.left-7,y:d.padding.top+(u-d.padding.top-d.padding.bottom)*e+4,className:"uh-trend-axis-label",textAnchor:"end"},b(Math.round(d.max*(1-e)))))),r.createElement("defs",{},i.map(e=>r.createElement("linearGradient",{key:e,id:"uh-trend-gradient-"+e,x1:"0",y1:"0",x2:"0",y2:"1"},r.createElement("stop",{offset:"4%",stopColor:p[e]||"#9aa4b2",stopOpacity:m[e]||.12}),r.createElement("stop",{offset:"96%",stopColor:p[e]||"#9aa4b2",stopOpacity:0})))),i.map((e,t)=>{const a=g[e]?g[e].area:"";return""===a?null:r.createElement("path",{key:"area-"+e,d:a,className:"uh-trend-area","data-series":e,fill:"url(#uh-trend-gradient-"+e+")",style:{animationDelay:80+80*t+"ms"}})}),i.map(e=>r.createElement("path",{key:"line-base-"+e,d:g[e]?g[e].line:"",className:"uh-trend-line","data-series":e,stroke:p[e]||"#9aa4b2"})),i.map((e,t)=>{d.points[e];const a=g[e]?g[e].length:0;return r.createElement("path",{key:"line-draw-"+e,d:g[e]?g[e].line:"",className:"uh-trend-line-draw","data-series":e,stroke:p[e]||"#9aa4b2",style:{"--uh-draw-length":a+"px",animationDelay:90*t+"ms"}})}),i.map(e=>{const t=d.points[e]||[];if(1!==t.length)return null;const a=t[0];return r.createElement("circle",{key:"single-point-"+e,cx:a.x,cy:a.y,r:4,className:"uh-trend-point",fill:p[e]||"#9aa4b2"})}),null!==s&&w?r.createElement(r.Fragment,{key:"hover-"+s},r.createElement("line",{x1:w.x,x2:w.x,y1:d.padding.top,y2:h,className:"uh-trend-cursor"}),i.map(e=>{const t=(d.points[e]||[])[s];return t?r.createElement("circle",{key:e,cx:t.x,cy:t.y,r:4,className:"uh-trend-point",fill:p[e]||"#9aa4b2"}):null})):null,n.map((a,n)=>{const s=(d.points[i[0]]||[])[n];if(!s)return null;const o=(d.points[i[0]]||[])[n+1],c=o?Math.max(8,o.x-s.x):n>0?Math.max(8,s.x-(d.points[i[0]]||[])[n-1].x):24;return r.createElement("rect",{key:Y(a,n),x:Math.max(d.padding.left,s.x-c/2),y:d.padding.top,width:c,height:u-d.padding.top-d.padding.bottom,className:"uh-trend-hit",tabIndex:0,role:"button","aria-label":V(a,t,!0)+" "+ie("total",t)+" "+b(a.total),onMouseEnter:()=>S(n),onMouseLeave:()=>l(null),onFocus:()=>S(n),onBlur:()=>l(null),onKeyDown:t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),"function"==typeof e.onPointClick&&e.onPointClick(J(a)))},onClick:()=>{"function"==typeof e.onPointClick&&e.onPointClick(J(a))}})}),f.map(e=>{const a=(d.points[i[0]]||[])[e],s=n[e];return a&&s?r.createElement("text",{key:Y(s,e),x:a.x,y:272,className:"uh-trend-axis-label",textAnchor:0===e?"start":e===n.length-1?"end":"middle"},V(s,t,!1)):null})),k&&E?r.createElement("div",{className:"uh-trend-tooltip"+M+(N?" uh-visible":""),style:C,"aria-hidden":!N},r.createElement("strong",{className:"uh-trend-tooltip-title"},V(k,t,!0)),i.map(e=>r.createElement("div",{key:e,className:"uh-trend-tooltip-row",style:{color:p[e]||"#9aa4b2"}},r.createElement("span",{className:"uh-trend-dot",style:{background:p[e]||"#9aa4b2"}}),r.createElement("span",{className:"uh-trend-tooltip-label"},ie(e,t)),r.createElement("strong",{className:"uh-trend-tooltip-value"},b(function(e,t){return"total"===t?e.total:e.tokens&&Number.isFinite(e.tokens[t])?e.tokens[t]:0}(k,e)))))):null);return r.createElement("div",{className:"uh-panel uh-trend-panel"},r.createElement("div",{className:"uh-trend-head"},r.createElement("div",{},r.createElement("h3",{className:"uh-tbl-title uh-title-with-icon"},r.createElement(y,{name:"chart",size:16}),a("Token 使用趋势","Token Usage Trend")),r.createElement("div",{className:"uh-note"},e.rangeLabel||"")),x?r.createElement("div",{className:"uh-note"},a("点击数据点查看当日明细","Click a point to inspect that day")):null),z,x?r.createElement("div",{className:"uh-trend-legend"},["total","input","cacheRead","cacheWrite","output","reasoning"].map(a=>r.createElement("button",{key:a,type:"button",className:"uh-trend-legend-item"+(i.includes(a)?" uh-on":""),onClick:()=>(t=>{"function"==typeof e.onToggle&&e.onToggle(t)})(a),"aria-pressed":i.includes(a)},r.createElement("span",{className:"uh-trend-dot",style:{background:p[a]||"#9aa4b2"}}),ie(a,t)))):null)}),le=r.memo(function(e){const t="en"===e.language?"en":"zh",a=e.day,n=r.useMemo(()=>(a&&Array.isArray(a.perWorkspace)?a.perWorkspace:[]).slice().sort((e,t)=>t.turns-e.turns),[a]),i=void 0===a?null:H(a),s=null!==i&&i.input+i.output+i.cacheRead>0?"en"===t?"Tokens: Input "+b(i.input)+" · Cache hits "+b(i.cacheRead)+" · Output "+b(i.output):"Token:输入 "+b(i.input)+" · 缓存命中 "+b(i.cacheRead)+" · 输出 "+b(i.output):"";return r.createElement("div",{ref:e.tooltipRef,className:"uh-tip",style:{left:0,top:0,visibility:"hidden"}},r.createElement("div",{className:"uh-tip-date"},F(e.date,t)),void 0!==a&&a.turns>0?n.map(a=>r.createElement("div",{key:a.workspaceId,className:"uh-tip-row",onClick:()=>e.onWorkspaceSelect(a.workspaceId)},r.createElement("span",{className:"uh-dot",style:{background:P(e.workspaceIndexes.get(a.workspaceId)||0)}}),r.createElement("span",{},e.workspaceTitles.get(a.workspaceId)||("en"===t?"Unknown workspace":"未知工作区")),r.createElement("span",{className:"uh-n"},"en"===t?a.turns+" uses":a.turns+" 次"))):r.createElement("div",{className:"uh-empty",style:{padding:"6px 0"}},"en"===t?"No usage records for this day":"这一天没有使用记录"),""!==s?r.createElement("div",{className:"uh-tip-tokens"},s):null)}),oe=r.memo(function(e){const t="en"===e.language?"en":"zh",a=(e,a)=>"en"===t?a:e,n=Array.isArray(e.workspaces)?e.workspaces:[],l=Array.isArray(e.rows)?e.rows:[],o=e.workspaceId||null,[c,u]=r.useState(null),d=r.useRef(null),p=r.useRef(null),m=r.useRef({x:0,y:0}),h=r.useRef(null),g=r.useRef(null),b=r.useRef(e.onDateClick),f=r.useRef(e.onWorkspaceSelect);b.current=e.onDateClick,f.current=e.onWorkspaceSelect;const x=r.useMemo(()=>function(e,t,a){const r=String(e||"").split("-").map(Number),n=t?new Date(Date.UTC(r[0],r[1]-1,r[2])):new Date(r[0],r[1]-1,r[2]),l=t?n.getUTCDay():n.getDay(),o=s(n,-l,t),c=s(o,-364,t),u=[];for(let e=0;e<371;e+=1){const a=s(c,e,t);u.push({date:i(a,t),month:t?a.getUTCMonth():a.getMonth(),year:t?a.getUTCFullYear():a.getFullYear()})}const d=[];for(let e=0;e<53;e+=1){const t=u[7*e],r=e>0?u[7*(e-1)]:null;null!==r&&t.month===r.month||d.push({left:100*e/53+"%",text:O(t.year,t.month,a)})}return{cells:u,months:d,weekdays:"en"===a?["","Mon","","Wed","","Fri",""]:["","周一","","周三","","周五",""]}}(e.todayKey,!0===e.utc,t),[e.todayKey,e.utc,t]),v=r.useMemo(()=>{const e=new Map;for(const t of l)t&&"string"==typeof t.date&&e.set(t.date,t);return e},[l]),w=r.useMemo(()=>{const t=new Map,r=new Map,i=e.aliases&&"object"==typeof e.aliases?e.aliases:{};return n.forEach((e,n)=>{const s=i[e.id];t.set(e.id,"string"==typeof s&&""!==s?s:e.title||a("未知工作区","Unknown workspace")),r.set(e.id,n)}),{titles:t,indexes:r}},[n,e.aliases,t]),k=r.useCallback(()=>{h.current=null,null!==g.current&&(window.clearTimeout(g.current),g.current=null);const e=p.current;if(null===e)return;const t=m.current,a="undefined"==typeof window?1280:window.innerWidth;e.style.left=t.x+14+"px",e.style.top=t.y+12+"px",e.style.transform=t.x>.65*a?"translateX(calc(-100% - 28px))":"none",e.style.visibility="visible"},[]),E=r.useCallback((e,t)=>{m.current={x:e,y:t},null===h.current&&(h.current=window.requestAnimationFrame(k),g.current=window.setTimeout(()=>{null!==h.current&&(window.cancelAnimationFrame(h.current),k())},80))},[k]);r.useEffect(()=>()=>{null!==h.current&&window.cancelAnimationFrame(h.current),null!==g.current&&window.clearTimeout(g.current)},[]),r.useEffect(()=>{null!==c&&null===h.current&&(h.current=window.requestAnimationFrame(k))},[c,k]);const N=r.useCallback((e,t)=>{d.current!==e&&(d.current=e,u(e)),E(t.clientX,t.clientY)},[E]),M=r.useCallback(e=>{E(e.clientX,e.clientY)},[E]),C=r.useCallback(()=>{d.current=null,u(null)},[]),S=r.useCallback(e=>{"function"==typeof f.current&&f.current(e),C()},[C]),z=r.useMemo(()=>x.cells.map((t,a)=>{const n=v.get(t.date);let i=0;if(void 0!==n)if(!0===e.queryUsable||null===o)i=n.turns;else{const e=Array.isArray(n.perWorkspace)?n.perWorkspace.find(e=>e.workspaceId===o):void 0;void 0!==e&&(i=e.turns)}const s=null!==o&&void 0!==n&&n.turns>0&&0===i,l={background:q(W(i)),opacity:s?.22:1,animationDelay:1.2*a+"ms"};return t.date===e.todayKey&&(l.animation="uh-cell-in .45s ease both, uh-glow 3s ease-in-out .7s infinite"),r.createElement("div",{key:t.date,className:"uh-cell",style:l,onMouseEnter:e=>N(t.date,e),onMouseMove:M,onMouseLeave:C,onClick:()=>{"function"==typeof b.current&&b.current(t.date)}})}),[x.cells,v,e.queryUsable,e.todayKey,o,N,M,C]),A=null===c?void 0:v.get(c);return r.createElement("div",{className:"uh-panel"},r.createElement("div",{className:"uh-section-title"},r.createElement(y,{name:"calendar",size:16}),a("使用热力图","Usage Heatmap")),r.createElement("div",{className:"uh-hm-head"},r.createElement("div",{className:"uh-chips"},n.map((e,t)=>r.createElement("button",{key:e.id,className:"uh-chip"+(o===e.id?" uh-on":""),onClick:()=>S(e.id),title:e.path},r.createElement("span",{className:"uh-dot",style:{background:P(t)}}),r.createElement("span",{className:"uh-chip-title"},w.titles.get(e.id))))),r.createElement("div",{className:"uh-legend"},r.createElement("span",{},a("少","Less")),[0,1,2,3,4].map(e=>r.createElement("span",{key:e,className:"uh-cell",style:{background:q(e)}})),r.createElement("span",{},a("多","More")))),r.createElement("div",{className:"uh-hm-scroll"},r.createElement("div",{className:"uh-months"},x.months.map((e,t)=>r.createElement("span",{key:t,style:{left:e.left}},e.text))),r.createElement("div",{className:"uh-hm-body"},r.createElement("div",{className:"uh-wdays"},x.weekdays.map((e,t)=>r.createElement("span",{key:t},e))),r.createElement("div",{className:"uh-grid"},z))),r.createElement("div",{className:"uh-note",style:{marginTop:10}},a("口径:每完成一个回合点亮一次(含子代理会话);悬停查看按工作区明细,点击工作区可筛选热力图与明细表。日期按本地时区。","Methodology: one cell lights up for each completed turn, including subagent sessions. Hover to view workspace details; click a workspace to filter the heatmap and detail tables. English dates and day boundaries use UTC.")),null!==c?r.createElement(le,{date:c,day:A,language:t,tooltipRef:p,workspaceTitles:w.titles,workspaceIndexes:w.indexes,onWorkspaceSelect:S}):null)});function ce(e,t){return Number(e&&e.values&&e.values[t])||0}const ue=r.memo(function(e){return e.render()},(e,t)=>e.revision===t.revision),de=r.memo(function(e){const t="en"===e.language?"en":"zh",a=(e,a)=>"en"===t?a:e,n=Array.isArray(e.rows)?e.rows:[],i=r.useMemo(()=>n.find(t=>t.id===e.selectedId)||n[0]||null,[n,e.selectedId]),s=e=>e&&"ledger-recovery"===e.materialization?a("账本恢复","Ledger recovery"):e&&"ledger-reuse"===e.materialization?a("账本复用","Ledger reuse"):e&&"scan"===e.materialization?a("扫描","Scan"):e&&"live"===e.materialization?a("实时","Live"):a("未知","Unknown"),l=(e,a)=>e&&Number.isFinite(e.time)?new Date(e.time).toLocaleString("en"===t?"en-US":"zh-CN","en"===t?a?{timeZone:"UTC"}:{timeZone:"UTC",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}:a?void 0:{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}):"—",o=t=>{"function"==typeof e.onSelect&&e.onSelect(t)};return r.createElement("div",{className:"uh-panel uh-records-panel",ref:e.panelRef,style:{display:e.visible?"block":"none"}},r.createElement("div",{className:"uh-records-head"},r.createElement("div",{},r.createElement("h3",{className:"uh-tbl-title uh-title-with-icon"},r.createElement(y,{name:"list",size:16}),a("请求日志","Request Logs")),r.createElement("div",{className:"uh-note"},e.scopeLabel+(e.scopeUtc?" · UTC":""))),r.createElement("div",{className:"uh-actions"},e.loading?r.createElement("span",{className:"uh-query-note"},a("同步中…","Refreshing…")):null,r.createElement("button",{type:"button",className:"uh-refresh",title:a("导出当前日志","Export current logs"),onClick:e.onExport,disabled:e.exporting||!e.scopeAvailable},r.createElement(y,{name:"export",size:13}),e.exporting?a("导出中…","Exporting…"):a("导出日志","Export logs")))),""!==e.error?r.createElement("div",{className:"uh-records-error",role:"alert"},"stale"===e.error?a("数据已更新,正在重新加载日志…","Data changed; reloading logs…"):"audit-export"===e.error?a("日志导出失败","Unable to export logs"):a("日志加载失败,请重试","Unable to load logs")):null,r.createElement("div",{className:"uh-records-note"},a("按时间倒序显示可审计的 Token 调用;选择一行查看 turn / step 和完整 Token 分桶。","Token calls are newest first; select a row to inspect its turn / step and token buckets.")),0!==n.length||e.loading?r.createElement("div",{className:"uh-records-scroll"},r.createElement("div",{className:"uh-record-grid uh-record-header"},r.createElement("div",{},a("时间","Time")),r.createElement("div",{},a("Provider / 模型","Provider / Model")),r.createElement("div",{className:"uh-record-num"},"turn / step"),r.createElement("div",{className:"uh-record-num"},a("输入","Input")),r.createElement("div",{className:"uh-record-num"},a("缓存命中","Cache read")),r.createElement("div",{className:"uh-record-num"},a("缓存写入","Cache write")),r.createElement("div",{className:"uh-record-num"},a("输出","Output")),r.createElement("div",{className:"uh-record-num"},a("成本","Cost")),r.createElement("div",{},a("来源","Source"))),n.map(e=>r.createElement("div",{key:e.id,className:"uh-record-grid uh-record-row"+(i&&i.id===e.id?" uh-on":""),role:"button",tabIndex:0,"aria-pressed":i&&i.id===e.id,onClick:()=>o(e.id),onKeyDown:t=>{"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),o(e.id))}},r.createElement("div",{className:"uh-record-time"},l(e,!1)),r.createElement("div",{className:"uh-record-model",title:e.model||""},e.model||a("未知模型","Unknown model"),e.requestedModel&&e.actualModel&&e.requestedModel!==e.actualModel?r.createElement("small",{},e.requestedModel+" → "+e.actualModel):null),r.createElement("div",{className:"uh-record-num"},(null===e.turn||void 0===e.turn?"—":e.turn)+" / "+(null===e.step||void 0===e.step?"—":e.step)),r.createElement("div",{className:"uh-record-num"},b(ce(e,"input"))),r.createElement("div",{className:"uh-record-num"},b(ce(e,"cacheRead"))),r.createElement("div",{className:"uh-record-num"},b(ce(e,"cacheWrite"))),r.createElement("div",{className:"uh-record-num"},b(ce(e,"output"))),r.createElement("div",{className:"uh-record-num uh-cost-num"},A(e,t)),r.createElement("div",{className:"uh-record-source"},s(e))))):r.createElement("div",{className:"uh-empty"},a("当前范围没有可审计的 Token 调用","No auditable Token calls in this scope")),r.createElement("div",{className:"uh-records-footer"},r.createElement("span",{className:"uh-note"},n.length>0?e.hasMore?a("已显示 "+n.length+" 条,继续加载可查看更多",n.length+" shown; load more for additional records"):a("共显示 "+n.length+" 条",n.length+" records shown"):""),e.hasMore?r.createElement("button",{type:"button",className:"uh-refresh",onClick:e.onLoadMore,disabled:e.loading},e.loading?a("加载中…","Loading…"):a("加载更多","Load more")):null),i?r.createElement("div",{className:"uh-record-detail"},r.createElement("div",{className:"uh-record-detail-head"},r.createElement("strong",{},a("选中调用","Selected call")),r.createElement("span",{className:"uh-note"},l(i,!0))),r.createElement("div",{className:"uh-record-detail-meta"},r.createElement("span",{},(i.provider||a("未知供应商","Unknown provider"))+" / "+(i.actualModel||i.requestedModel||i.model||a("未知模型","Unknown model"))),r.createElement("span",{},"turn "+(null===i.turn||void 0===i.turn?"—":i.turn)+" · step "+(null===i.step||void 0===i.step?"—":i.step)),r.createElement("span",{},a("来源:","Source: ")+s(i)),r.createElement("span",{},a("计价模型:","Pricing model: ")+(i.cost&&i.cost.pricingModel?i.cost.pricingModel:a("未计价","unpriced")))),r.createElement("div",{className:"uh-record-token-strip"},["input","cacheRead","cacheWrite","output","reasoning"].map(e=>r.createElement("div",{key:e},r.createElement("span",{},"cacheRead"===e?a("缓存命中","Cache read"):"cacheWrite"===e?a("缓存写入","Cache write"):"reasoning"===e?a("推理","Reasoning"):"input"===e?a("输入","Input"):a("输出","Output")),r.createElement("strong",{},b(ce(i,e))))),r.createElement("div",{className:"uh-record-token-total"},r.createElement("span",{},a("总处理","Total")),r.createElement("strong",{},b(ce(c=i,"input")+ce(c,"cacheRead")+ce(c,"cacheWrite")+ce(c,"output")+ce(c,"reasoning")))),r.createElement("div",{className:"uh-record-token-total"},r.createElement("span",{},a("成本","Cost")),r.createElement("strong",{},A(i,t))))):null);var c},function(e,t){return e.visible===t.visible&&e.scopeLabel===t.scopeLabel&&e.scopeUtc===t.scopeUtc&&e.scopeAvailable===t.scopeAvailable&&e.loading===t.loading&&e.exporting===t.exporting&&e.error===t.error&&e.rows===t.rows&&e.selectedId===t.selectedId&&e.hasMore===t.hasMore&&e.language===t.language&&e.actionKey===t.actionKey});function pe(e){const t=Array.isArray(e.options)?e.options:[],a=void 0===e.value||null===e.value?"":String(e.value),n=t.find(e=>String(e.value)===a),[i,s]=r.useState(!1),l=r.useRef(null);return r.useEffect(()=>{if(!i||"undefined"==typeof document)return;const e=e=>{l.current&&!l.current.contains(e.target)&&s(!1)};return document.addEventListener("pointerdown",e),()=>document.removeEventListener("pointerdown",e)},[i]),r.createElement("div",{className:"uh-language-menu uh-filter-menu"+(e.className?" "+e.className:"")+(i?" uh-open":""),ref:l,onKeyDown:e=>{"Escape"===e.key&&i&&(e.preventDefault(),e.stopPropagation(),s(!1))}},r.createElement("button",{type:"button",className:"uh-language-trigger uh-filter-trigger"+(i?" uh-open":""),title:n?n.label:e.label,"aria-label":e.ariaLabel||e.label,"aria-haspopup":"listbox","aria-expanded":i,onClick:()=>s(e=>!e)},r.createElement(y,{name:e.icon||"chart",size:14}),r.createElement("span",{className:"uh-filter-label"},n?n.label:e.label),r.createElement(y,{name:"chevron",size:13,className:"uh-language-caret"})),i?r.createElement("div",{className:"uh-language-options uh-filter-options",role:"listbox","aria-label":e.ariaLabel||e.label},t.map(t=>{const n=String(t.value),i=n===a;return r.createElement("button",{key:n,type:"button",role:"option","aria-selected":i,className:"uh-language-option"+(i?" uh-on":""),onClick:()=>{return t=n,"function"==typeof e.onChange&&e.onChange(t),void s(!1);var t}},r.createElement(y,{name:e.icon||"chart",size:14}),r.createElement("span",{className:"uh-filter-option-label"},t.label),i?r.createElement(y,{name:"check",size:14,className:"uh-language-option-check"}):null)})):null)}const me="dsh-all-usage/styles.css";if("undefined"!=typeof document){let e=document.querySelector("style[data-plugin-css="+JSON.stringify(me)+"]");null===e&&(e=document.createElement("style"),e.dataset.plugin="dsh-all-usage",e.dataset.pluginCss=me,document.head.appendChild(e)),e.textContent='\n.uh-page { display:flex; flex-direction:column; gap:14px; padding:2px 2px 28px; font-family:inherit; }\n.uh-head { position:relative; z-index:20; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }\n.uh-title { margin:0; font-size:15px; font-weight:600; color:var(--dsw-alias-label-primary); }\n.uh-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }\n.uh-language-menu, .uh-filter-menu { position:relative; z-index:12; }\n.uh-filter-menu { flex:0 1 auto; min-width:0; }\n.uh-filter-workspace { width:180px; }\n.uh-filter-provider { width:180px; }\n.uh-filter-model { width:260px; }\n.uh-filter-menu.uh-open { z-index:14; }\n.uh-language-trigger { display:inline-flex; align-items:center; gap:6px; min-height:30px; padding:4px 9px 4px 10px; border:1px solid transparent; border-radius:15px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 7%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; font-weight:600; line-height:1; cursor:pointer; transition:border-color .15s ease, background-color .15s ease, transform .1s ease; }\n.uh-filter-trigger { width:100%; min-width:0; justify-content:flex-start; }\n.uh-language-trigger:hover, .uh-language-trigger.uh-open { border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 58%, var(--dsw-alias-border-l2)); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 13%, var(--dsw-alias-bg-layer-1)); }\n.uh-language-trigger:active { transform:scale(.96); }\n.uh-language-label { min-width:26px; text-align:left; }\n.uh-filter-label { min-width:0; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; }\n.uh-language-caret { color:var(--dsw-alias-label-secondary); transition:transform .18s ease; }\n.uh-language-trigger.uh-open .uh-language-caret { transform:rotate(180deg); }\n.uh-language-menu.uh-open { z-index:30; }\n.uh-language-options { position:absolute; top:calc(100% + 7px); right:0; min-width:142px; padding:5px; border:1px solid var(--dsw-alias-border-l2); border-radius:12px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 14px 28px color-mix(in srgb, #000 24%, transparent); animation:uh-menu-in .16s ease both; }\n.uh-filter-options { left:0; right:auto; min-width:100%; max-width:300px; }\n.uh-language-option { display:flex; align-items:center; gap:8px; width:100%; min-height:32px; padding:6px 8px; border:0; border-radius:8px; background:transparent; color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; text-align:left; cursor:pointer; transition:background-color .14s ease, color .14s ease; }\n.uh-filter-option-label { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-language-option:hover, .uh-language-option:focus-visible { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, var(--dsw-alias-bg-layer-2)); outline:0; }\n.uh-language-option.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 19%, var(--dsw-alias-bg-layer-2)); color:var(--dsw-alias-label-primary); font-weight:600; }\n.uh-language-option-check { margin-left:auto; color:var(--dsw-alias-brand-primary); }\n.uh-range { display:inline-flex; border:1px solid var(--dsw-alias-border-l2); border-radius:8px; overflow:hidden; }\n.uh-range button { border:0; background:transparent; color:var(--dsw-alias-label-secondary); padding:4px 12px; font-size:12px; cursor:pointer; font-family:inherit; transition:background-color .15s ease, color .15s ease; }\n.uh-range button + button { border-left:1px solid var(--dsw-alias-border-l2); }\n.uh-range button.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, var(--dsw-alias-bg-layer-2)); color:var(--dsw-alias-label-primary); font-weight:600; }\n.uh-custom-range { display:grid; grid-template-columns:minmax(180px, 1fr) auto auto; gap:10px 14px; align-items:end; padding:12px; border:1px solid var(--dsw-alias-border-l1); border-radius:10px; background:var(--dsw-alias-bg-layer-1); }\n.uh-custom-range-meta { min-width:0; }\n.uh-custom-range-title { display:flex; align-items:center; gap:7px; color:var(--dsw-alias-label-primary); font-size:13px; font-weight:600; }\n.uh-custom-range-note { margin-top:3px; color:var(--dsw-alias-label-secondary); font-size:11px; line-height:1.45; }\n.uh-custom-range-fields { display:grid; grid-template-columns:repeat(2, minmax(136px, 1fr)); gap:8px; }\n.uh-custom-range-field { display:flex; flex-direction:column; gap:4px; color:var(--dsw-alias-label-secondary); font-size:11px; }\n.uh-custom-range-field input { min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:3px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:12px; outline:none; }\n.uh-custom-range-field input:focus { border-color:var(--dsw-alias-brand-primary); }\n.uh-custom-range-actions { display:flex; gap:6px; }\n.uh-custom-range-cancel, .uh-custom-range-apply { min-height:30px; border-radius:6px; padding:4px 10px; font:inherit; font-size:12px; cursor:pointer; }\n.uh-custom-range-cancel { border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); }\n.uh-custom-range-apply { border:1px solid var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color:var(--dsw-alias-label-primary); }\n.uh-custom-range-apply:disabled { opacity:.48; cursor:not-allowed; }\n.uh-custom-range-error { grid-column:1 / -1; color:#d92d20; font-size:12px; }\n.uh-refresh { border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); border-radius:8px; padding:4px 12px; font-size:12px; cursor:pointer; font-family:inherit; transition:border-color .15s ease, color .15s ease, transform .1s ease; }\n.uh-refresh:hover { border-color:var(--dsw-alias-brand-primary); }\n.uh-refresh:active, .uh-chip:active, .uh-range button:active { transform:scale(.96); }\n.uh-alias-panel-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; font-size:13px; font-weight:600; color:var(--dsw-alias-label-primary); }\n.uh-alias-close { border:0; background:transparent; color:var(--dsw-alias-label-secondary); font-size:12px; cursor:pointer; font-family:inherit; padding:0; transition:color .15s ease; }\n.uh-alias-close:hover { color:var(--dsw-alias-brand-primary); }\n.uh-alias-list { display:grid; grid-template-columns:repeat(auto-fill, minmax(250px, 1fr)); gap:8px 16px; max-height:240px; overflow-y:auto; }\n.uh-alias-item { display:flex; align-items:center; gap:8px; min-width:0; }\n.uh-alias-folder { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:11px; color:var(--dsw-alias-label-secondary); }\n.uh-alias-input { flex:none; width:150px; border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); border-radius:6px; padding:3px 8px; font-size:12px; font-family:inherit; outline:none; transition:border-color .15s ease; }\n.uh-alias-input:focus { border-color:var(--dsw-alias-brand-primary); }\n.uh-alias-panel-foot { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-top:10px; padding-top:10px; border-top:1px solid var(--dsw-alias-border-l1); }\n.uh-alias-ok { border:1px solid var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 16%, transparent); color:var(--dsw-alias-label-primary); border-radius:6px; font-size:12px; padding:3px 12px; cursor:pointer; font-family:inherit; flex:none; transition:transform .1s ease; }\n.uh-alias-ok:active { transform:scale(.96); }\n.uh-anim-panel { animation:uh-panel-in .28s ease both; }\n.uh-pricing-panel { display:flex; flex-direction:column; gap:12px; }\n.uh-pricing-head, .uh-pricing-toolbar, .uh-pricing-section-head, .uh-pricing-foot { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }\n.uh-pricing-note { color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.55; }\n.uh-pricing-toolbar { padding:10px 0; border-top:1px solid var(--dsw-alias-border-l1); border-bottom:1px solid var(--dsw-alias-border-l1); }\n.uh-pricing-switch { display:inline-flex; align-items:center; gap:7px; color:var(--dsw-alias-label-primary); font-size:12px; }\n.uh-pricing-section { display:flex; flex-direction:column; gap:8px; }\n.uh-pricing-table-wrap { max-height:392px; overflow-x:auto; overflow-y:scroll; scrollbar-gutter:stable; scrollbar-width:auto; scrollbar-color:#707780 #1d1f22; border:1px solid var(--dsw-alias-border-l1); border-radius:8px; background:var(--dsw-alias-bg-layer-2); }\n.uh-pricing-model-table { width:100%; min-width:1080px; border-collapse:collapse; table-layout:fixed; font-size:11px; }\n.uh-pricing-model-table th, .uh-pricing-model-table td { min-width:0; padding:8px 9px; border-bottom:1px solid var(--dsw-alias-border-l1); text-align:left; vertical-align:middle; }\n.uh-pricing-model-table th { position:sticky; top:0; z-index:1; color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-2); font-weight:650; white-space:nowrap; }\n.uh-pricing-model-table th:nth-child(1) { width:24%; }\n.uh-pricing-model-table th:nth-child(2) { width:84px; }\n.uh-pricing-model-table th:nth-child(3) { width:20%; }\n.uh-pricing-model-table th:nth-child(4) { width:96px; }\n.uh-pricing-model-table th:nth-child(n+5) { width:100px; text-align:right; }\n.uh-pricing-model-table td:nth-child(n+5) { text-align:right; }\n.uh-pricing-model-table tbody tr:last-child td { border-bottom:0; }\n.uh-pricing-model-table tbody tr:not(.uh-pricing-tier-row):hover { background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 65%, transparent); }\n.uh-pricing-model-name, .uh-pricing-model-target { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-primary); }\n.uh-pricing-model-rate { color:var(--dsw-alias-label-secondary); font-variant-numeric:tabular-nums; white-space:nowrap; }\n.uh-pricing-status, .uh-pricing-tier-badge { display:inline-flex; justify-content:center; padding:3px 6px; border-radius:6px; font-size:10px; font-weight:650; white-space:nowrap; }\n.uh-pricing-status-priced { color:#157347; background:color-mix(in srgb, #30d158 22%, transparent); }\n.uh-pricing-status-unpriced, .uh-pricing-status-ambiguous, .uh-pricing-status-unsupported { color:#9a5b00; background:color-mix(in srgb, #ff9f0a 20%, transparent); }\n.uh-pricing-tier-badge { color:var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent); }\n.uh-pricing-tier-badge.uh-flat { color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-1); }\n.uh-pricing-tier-row > td { padding:0 9px 8px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 36%, transparent); }\n.uh-pricing-tier-details > summary { display:inline-flex; align-items:center; gap:6px; min-height:28px; color:var(--dsw-alias-label-secondary); cursor:pointer; list-style:none; font-size:11px; }\n.uh-pricing-tier-details > summary::-webkit-details-marker { display:none; }\n.uh-pricing-tier-caret { transition:transform .15s ease; }\n.uh-pricing-tier-details[open] .uh-pricing-tier-caret { transform:rotate(180deg); }\n.uh-pricing-tier-context { margin-left:6px; color:var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary)); }\n.uh-pricing-tier-table { width:100%; margin:2px 0 5px; border:1px solid var(--dsw-alias-border-l1); border-radius:6px; border-collapse:separate; border-spacing:0; overflow:hidden; table-layout:fixed; background:var(--dsw-alias-bg-base); }\n.uh-pricing-tier-table th, .uh-pricing-tier-table td { position:static; width:auto !important; padding:6px 8px; border-bottom:1px solid var(--dsw-alias-border-l1); text-align:right !important; background:transparent; font-size:10px; }\n.uh-pricing-tier-table th:first-child, .uh-pricing-tier-table td:first-child { width:30% !important; text-align:left !important; }\n.uh-pricing-tier-table tbody tr:last-child td { border-bottom:0; }\n.uh-pricing-tier-table tbody tr:hover { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 6%, transparent); }\n.uh-pricing-used-model-picker { position:relative; z-index:2; min-width:0; }\n.uh-pricing-used-model-picker:focus-within { z-index:30; }\n.uh-pricing-used-model-input { box-sizing:border-box; width:100%; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }\n.uh-pricing-used-model-input:focus { border-color:var(--dsw-alias-brand-primary); }\n.uh-pricing-used-model-options { top:calc(100% + 7px); left:0; right:auto; width:100%; min-width:280px; max-height:240px; overflow-y:auto; z-index:40; }\n.uh-pricing-model-search { position:relative; z-index:2; min-width:0; }\n.uh-pricing-model-search:focus-within { z-index:30; }\n.uh-pricing-model-search-input { box-sizing:border-box; width:100%; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }\n.uh-pricing-model-search-input:focus { border-color:var(--dsw-alias-brand-primary); }\n.uh-pricing-model-options { top:calc(100% + 7px); left:0; right:auto; width:100%; min-width:280px; max-height:240px; overflow-y:auto; z-index:40; }\n.uh-pricing-model-option { align-items:flex-start; }\n.uh-pricing-model-option-name { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-pricing-model-option-id { margin-left:auto; padding-left:10px; color:var(--dsw-alias-label-secondary); font-size:10px; white-space:nowrap; }\n.uh-pricing-edit-row { display:grid; grid-template-columns:minmax(240px,1.2fr) minmax(260px,1.3fr) minmax(78px,.45fr) 32px; gap:10px; align-items:center; min-width:650px; }\n.uh-pricing-price-row { grid-template-columns:repeat(5,minmax(108px,1fr)) 32px; min-width:650px; }\n.uh-pricing-price-head { display:grid; grid-template-columns:repeat(5,minmax(108px,1fr)) 32px; gap:10px; align-items:center; min-width:650px; color:var(--dsw-alias-label-secondary); font-size:10px; }\n.uh-pricing-price-head span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-pricing-edit-row input, .uh-pricing-tier-edit-row input { box-sizing:border-box; min-width:0; min-height:30px; border:1px solid var(--dsw-alias-border-l2); border-radius:6px; padding:4px 7px; background:var(--dsw-alias-bg-base); color:var(--dsw-alias-label-primary); font:inherit; font-size:11px; outline:none; }\n.uh-pricing-edit-row input:focus, .uh-pricing-tier-edit-row input:focus { border-color:var(--dsw-alias-brand-primary); }\n.uh-pricing-edit-row .uh-refresh, .uh-pricing-tier-edit-row .uh-refresh { min-height:30px; padding:0; }\n.uh-pricing-overrides { display:flex; flex-direction:column; min-width:760px; }\n.uh-pricing-override { display:flex; flex-direction:column; gap:8px; padding:10px 0; border-bottom:1px solid var(--dsw-alias-border-l1); }\n.uh-pricing-override:last-child { border-bottom:0; }\n.uh-pricing-tier-editor { margin-left:10px; padding-left:12px; border-left:2px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 34%, var(--dsw-alias-border-l1)); }\n.uh-pricing-tier-editor-head { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:30px; }\n.uh-pricing-tier-editor-title { display:flex; align-items:baseline; gap:8px; min-width:0; }\n.uh-pricing-tier-editor-title strong { font-size:11px; }\n.uh-pricing-tier-editor-title span { color:var(--dsw-alias-label-secondary); font-size:10px; }\n.uh-pricing-tier-edit-head, .uh-pricing-tier-edit-row { display:grid; grid-template-columns:minmax(132px,.85fr) repeat(4,minmax(108px,1fr)) 32px; gap:10px; align-items:center; min-width:690px; }\n.uh-pricing-tier-edit-head { margin:5px 0; color:var(--dsw-alias-label-secondary); font-size:10px; }\n.uh-pricing-tier-edit-row { margin-top:7px; }\n.uh-pricing-tier-edit-row.uh-invalid input { border-color:var(--dsw-alias-warning, #a55b00); }\n.uh-pricing-tier-empty { padding:5px 0; color:var(--dsw-alias-label-secondary); font-size:10px; }\n.uh-pricing-error { color:var(--dsw-alias-warning, #a55b00); font-size:12px; line-height:1.45; }\n.uh-pricing-foot { padding-top:4px; }\n.uh-cost-num { color:var(--dsw-alias-label-primary); }\n.uh-progress { font-size:12px; color:var(--dsw-alias-label-secondary); display:flex; align-items:center; gap:10px; }\n.uh-sync-health { margin-top:8px; padding:8px 12px; display:flex; align-items:center; flex-wrap:wrap; gap:6px; border:1px solid var(--dsw-alias-border-l1); border-radius:10px; color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-1); font-size:11px; line-height:1.45; }\n.uh-sync-health.uh-stale { color:var(--dsw-alias-warning, #a55b00); border-color:color-mix(in srgb, var(--dsw-alias-warning, #d9822b) 45%, var(--dsw-alias-border-l1)); }\n.uh-sync-retry { border:0; background:transparent; color:inherit; font:inherit; text-decoration:underline; cursor:pointer; padding:0 2px; }\n.uh-trend-panel { min-height:300px; animation:uh-panel-in .38s ease both; }\n.uh-trend-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:10px; }\n.uh-trend-chart-wrap { position:relative; min-height:250px; width:100%; overflow:hidden; }\n.uh-trend-stage { display:grid; place-items:center; min-height:250px; width:100%; }\n.uh-trend-message { color:var(--dsw-alias-label-secondary); font-size:12px; }\n.uh-trend-spinner { width:24px; height:24px; border:2px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, var(--dsw-alias-border-l2)); border-top-color:var(--dsw-alias-brand-primary); border-radius:50%; animation:uh-spinner-turn .78s linear infinite; }\n.uh-trend-svg { display:block; width:100%; height:auto; min-height:220px; }\n.uh-trend-grid { stroke:var(--dsw-alias-border-l1); stroke-width:1; stroke-dasharray:3 4; opacity:.8; }\n.uh-trend-cursor { stroke:var(--dsw-alias-label-secondary); stroke-width:1; stroke-dasharray:3 4; opacity:.65; pointer-events:none; }\n.uh-trend-point { stroke:var(--dsw-alias-bg-layer-1); stroke-width:2; vector-effect:non-scaling-stroke; pointer-events:none; }\n.uh-trend-axis-label { fill:var(--dsw-alias-label-secondary); font-size:11px; font-family:inherit; }\n.uh-trend-line { fill:none; stroke-width:2.2; vector-effect:non-scaling-stroke; stroke-linecap:round; stroke-linejoin:round; opacity:.22; }\n.uh-trend-line-draw { fill:none; stroke-width:2.2; vector-effect:non-scaling-stroke; stroke-linecap:round; stroke-linejoin:round; stroke-dasharray:var(--uh-draw-length); stroke-dashoffset:var(--uh-draw-length); opacity:.96; pointer-events:none; animation:uh-trend-draw .95s cubic-bezier(.22,.61,.36,1) both; }\n.uh-trend-area { opacity:1; animation:uh-trend-fill .8s ease; }\n.uh-trend-hit { fill:transparent; cursor:crosshair; outline:none; }\n.uh-trend-hit:focus { fill:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent); outline:1px solid var(--dsw-alias-brand-primary); outline-offset:2px; }\n.uh-trend-tooltip { position:absolute; z-index:4; min-width:166px; padding:10px 11px; border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 88%, transparent); border-radius:8px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-1) 94%, transparent); box-shadow:0 10px 24px rgba(0,0,0,.22); backdrop-filter:blur(10px); color:var(--dsw-alias-label-primary); font-size:12px; line-height:1.45; pointer-events:none; opacity:0; visibility:hidden; transform:translate(14px,-50%) scale(.985); transform-origin:left center; transition:left .16s cubic-bezier(.22,.61,.36,1), top .16s cubic-bezier(.22,.61,.36,1), opacity .12s ease, transform .16s cubic-bezier(.22,.61,.36,1), visibility 0s linear .16s; }\n.uh-trend-tooltip.uh-left { transform:translate(calc(-100% - 14px),-50%) scale(.985); transform-origin:right center; }\n.uh-trend-tooltip.uh-visible { opacity:1; visibility:visible; transform:translate(14px,-50%) scale(1); transition-delay:0s; }\n.uh-trend-tooltip.uh-left.uh-visible { transform:translate(calc(-100% - 14px),-50%) scale(1); }\n.uh-trend-tooltip-title { display:block; margin-bottom:6px; color:var(--dsw-alias-label-primary); font-size:12px; font-weight:650; }\n.uh-trend-tooltip-row { display:grid; grid-template-columns:8px minmax(0,1fr) auto; align-items:center; gap:7px; min-width:0; margin-top:3px; font-size:11px; }\n.uh-trend-tooltip-row .uh-trend-dot { width:8px; height:8px; margin:0; }\n.uh-trend-tooltip-label { overflow:hidden; font-weight:600; text-overflow:ellipsis; white-space:nowrap; }\n.uh-trend-tooltip-value { color:inherit; font-weight:600; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.uh-trend-dot { display:inline-block; width:7px; height:7px; margin-right:5px; border-radius:50%; vertical-align:1px; }\n.uh-trend-legend { display:flex; flex-wrap:wrap; gap:5px 8px; margin-top:5px; }\n.uh-trend-legend-item { display:inline-flex; align-items:center; gap:3px; border:0; border-radius:7px; padding:3px 6px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:11px; cursor:pointer; transition:color .15s ease; }\n.uh-trend-legend-item:hover { background:var(--dsw-alias-bg-layer-2); color:var(--dsw-alias-label-primary); }\n.uh-trend-legend-item.uh-on { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); }\n.uh-filter-bar { position:relative; z-index:10; display:flex; align-items:center; flex-wrap:wrap; gap:7px; }\n.uh-filter-clear { border:0; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:11px; cursor:pointer; text-decoration:underline; }\n.uh-query-note { color:var(--dsw-alias-label-secondary); font-size:11px; }\n.uh-detail-tabs { display:flex; align-items:center; flex-wrap:wrap; gap:4px; padding:4px; border:1px solid var(--dsw-alias-border-l1); border-radius:9px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 58%, transparent); }\n.uh-detail-tab { display:inline-flex; align-items:center; gap:6px; min-height:32px; padding:5px 11px; border:0; border-radius:7px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:12px; cursor:pointer; transition:background-color .15s ease, color .15s ease, transform .12s ease; }\n.uh-detail-tab:hover { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-2); }\n.uh-detail-tab.uh-on { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 3px rgba(0,0,0,.14); }\n.uh-records-panel { animation:uh-panel-in .28s ease both; }\n.uh-records-head { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:7px; }\n.uh-records-note { margin:8px 0 10px; color:var(--dsw-alias-label-secondary); font-size:11px; line-height:1.45; }\n.uh-records-error { margin:7px 0; color:var(--dsw-alias-warning, #a55b00); font-size:11px; }\n.uh-records-scroll { overflow:auto; border:1px solid var(--dsw-alias-border-l1); border-radius:8px; }\n.uh-record-grid { display:grid; grid-template-columns:112px minmax(190px,1.45fr) 78px repeat(4,minmax(76px,.72fr)) 96px 82px; gap:0; min-width:900px; align-items:center; }\n.uh-record-grid > div { min-width:0; padding:8px 7px; border-bottom:1px solid var(--dsw-alias-border-l1); font-size:11px; }\n.uh-record-header { color:var(--dsw-alias-label-secondary); background:var(--dsw-alias-bg-layer-2); font-weight:600; }\n.uh-record-header > div { white-space:nowrap; }\n.uh-record-row { color:var(--dsw-alias-label-primary); cursor:pointer; outline:none; transition:background-color .14s ease, box-shadow .14s ease; }\n.uh-record-row:hover { background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 68%, transparent); }\n.uh-record-row.uh-on { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, var(--dsw-alias-bg-layer-1)); box-shadow:inset 3px 0 var(--dsw-alias-brand-primary); }\n.uh-record-row:focus-visible { box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary); }\n.uh-record-row:last-child > div { border-bottom:0; }\n.uh-record-time, .uh-record-num { color:var(--dsw-alias-label-secondary); font-variant-numeric:tabular-nums; white-space:nowrap; }\n.uh-record-num { text-align:right; }\n.uh-record-model { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:550; }\n.uh-record-model small { display:block; overflow:hidden; color:var(--dsw-alias-label-secondary); font-size:10px; font-weight:400; text-overflow:ellipsis; white-space:nowrap; }\n.uh-record-source { color:var(--dsw-alias-label-secondary); white-space:nowrap; }\n.uh-records-footer { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:9px; }\n.uh-record-detail { margin-top:12px; padding:10px 11px; border-top:1px solid var(--dsw-alias-border-l2); background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 42%, transparent); animation:uh-detail-in .24s ease both; }\n.uh-record-detail-head, .uh-record-detail-meta { display:flex; align-items:center; flex-wrap:wrap; gap:7px 14px; }\n.uh-record-detail-head { justify-content:space-between; margin-bottom:5px; color:var(--dsw-alias-label-primary); font-size:12px; }\n.uh-record-detail-meta { color:var(--dsw-alias-label-secondary); font-size:11px; }\n.uh-record-token-strip { display:grid; grid-template-columns:repeat(5,minmax(72px,1fr)) repeat(2,minmax(82px,1.1fr)); gap:6px; margin-top:9px; }\n.uh-record-token-strip > div { display:flex; flex-direction:column; gap:2px; min-width:0; padding:6px 7px; border-radius:6px; background:var(--dsw-alias-bg-layer-2); }\n.uh-record-token-strip span { color:var(--dsw-alias-label-secondary); font-size:10px; }\n.uh-record-token-strip strong { color:var(--dsw-alias-label-primary); font-size:12px; font-variant-numeric:tabular-nums; }\n.uh-record-token-total { border:1px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 38%, var(--dsw-alias-border-l1)) !important; }\n@keyframes uh-detail-in { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }\n@media (max-width:640px) {\n .uh-trend-head { flex-direction:column; }\n .uh-filter-menu { flex:1 1 130px; width:auto; }\n .uh-filter-trigger { max-width:100%; }\n .uh-trend-tooltip { min-width:116px; }\n .uh-records-head { flex-direction:column; }\n .uh-record-token-strip { grid-template-columns:repeat(2,minmax(0,1fr)); }\n .uh-record-token-total { grid-column:1 / -1; }\n .uh-pricing-head { align-items:flex-start; }\n .uh-pricing-toolbar { align-items:flex-start; }\n .uh-pricing-section-head { align-items:center; }\n .uh-pricing-edit-row { grid-template-columns:minmax(0,1fr) 36px; min-width:0; width:100%; }\n .uh-pricing-edit-row > .uh-pricing-used-model-picker,\n .uh-pricing-edit-row > .uh-pricing-model-search,\n .uh-pricing-price-row > input { grid-column:1 / -1; }\n .uh-pricing-edit-row > input[type=\'number\'] { grid-column:1; }\n .uh-pricing-edit-row > .uh-icon-button { grid-column:2; }\n .uh-pricing-price-head { display:none; }\n .uh-pricing-overrides { min-width:0; width:100%; }\n .uh-pricing-override { min-width:0; }\n .uh-pricing-price-row { grid-template-columns:minmax(0,1fr) 36px; min-width:0; }\n .uh-pricing-price-row > input[type=\'number\'] { grid-column:1 / -1; }\n .uh-pricing-tier-editor { margin-left:0; padding-left:0; border-left:0; }\n .uh-pricing-tier-editor-head { align-items:flex-start; flex-wrap:wrap; }\n .uh-pricing-tier-editor-title { flex-direction:column; gap:2px; }\n .uh-pricing-tier-edit-head { display:none; }\n .uh-pricing-tier-edit-row { grid-template-columns:minmax(0,1fr) 36px; min-width:0; padding:8px; border:1px solid var(--dsw-alias-border-l1); border-radius:6px; }\n .uh-pricing-tier-edit-row > input { grid-column:1 / -1; }\n .uh-pricing-tier-edit-row > .uh-icon-button { grid-column:2; }\n .uh-pricing-tier-context { display:block; margin:2px 0 0; }\n}\n.uh-bar { flex:1; height:6px; border-radius:3px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; max-width:340px; }\n.uh-fill { height:100%; background:var(--dsw-alias-brand-primary); border-radius:3px; transition:width .3s ease; }\n.uh-cards { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px; }\n.uh-card { background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l1); border-radius:12px; padding:12px 14px; display:flex; flex-direction:column; gap:6px; min-height:86px; animation:uh-card-in .45s ease both; transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease; }\n.uh-card:hover { transform:translateY(-2px); border-color:var(--dsw-alias-border-l2); box-shadow:0 6px 18px rgba(0,0,0,.10); }\n.uh-card-label { font-size:12px; color:var(--dsw-alias-label-secondary); }\n.uh-card-value { font-size:20px; font-weight:650; color:var(--dsw-alias-label-primary); line-height:1.2; }\n.uh-card-sub { font-size:11px; color:var(--dsw-alias-label-secondary); line-height:1.55; }\n.uh-wsbars { display:flex; flex-direction:column; gap:6px; margin-top:2px; }\n.uh-wsbar { display:flex; flex-direction:column; gap:3px; cursor:pointer; padding:2px 6px; margin:0 -6px; border-radius:8px; transition:background-color .15s ease; }\n.uh-wsbar:hover { background:var(--dsw-alias-bg-layer-2); }\n.uh-wsbar.uh-sel { outline:1px solid var(--dsw-alias-brand-primary); }\n.uh-wsbar-top { display:flex; align-items:center; gap:6px; min-width:0; }\n.uh-wsbar-title { font-size:12px; color:var(--dsw-alias-label-primary); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; min-width:0; }\n.uh-wsbar-num { font-size:11px; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-secondary); flex:none; }\n.uh-panel { background:var(--dsw-alias-bg-layer-1); border:1px solid var(--dsw-alias-border-l1); border-radius:12px; padding:14px; }\n.uh-hm-head { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:10px; }\n.uh-chips { display:flex; flex-wrap:wrap; gap:6px; }\n.uh-chip { display:inline-flex; align-items:center; gap:6px; border:1px solid var(--dsw-alias-border-l2); background:transparent; color:var(--dsw-alias-label-primary); border-radius:999px; padding:2px 10px; font-size:11px; cursor:pointer; font-family:inherit; max-width:190px; transition:border-color .15s ease, background-color .15s ease, color .15s ease, transform .1s ease; }\n.uh-chip .uh-chip-title { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-chip.uh-on { border-color:var(--dsw-alias-brand-primary); background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent); }\n.uh-dot { width:8px; height:8px; border-radius:50%; flex:none; }\n.uh-legend { display:flex; align-items:center; gap:4px; font-size:11px; color:var(--dsw-alias-label-secondary); }\n.uh-legend .uh-cell { width:10px; height:10px; border-radius:2px; animation:none; }\n.uh-hm-scroll { overflow-x:auto; padding-bottom:2px; }\n.uh-months { position:relative; height:16px; margin-left:30px; width:calc(100% - 30px); min-width:686px; font-size:10px; color:var(--dsw-alias-label-secondary); }\n.uh-months span { position:absolute; top:0; }\n.uh-hm-body { display:flex; gap:6px; min-width:0; }\n.uh-wdays { display:grid; grid-template-rows:repeat(7,10px); gap:3px; font-size:10px; color:var(--dsw-alias-label-secondary); text-align:right; width:24px; }\n.uh-wdays span { line-height:10px; }\n.uh-grid { flex:1 1 auto; min-width:686px; display:grid; grid-auto-flow:column; grid-template-columns:repeat(53,minmax(10px,1fr)); grid-template-rows:repeat(7,minmax(10px,auto)); gap:3px; }\n.uh-cell { width:100%; height:auto; min-width:10px; aspect-ratio:1; border-radius:2px; background:var(--dsw-alias-bg-layer-2); animation:uh-cell-in .45s ease both; transition:transform .12s ease, box-shadow .12s ease; }\n.uh-cell:hover { transform:scale(1.35); box-shadow:0 1px 6px rgba(0,0,0,.28); position:relative; z-index:2; }\n.uh-tip { position:fixed; z-index:1200; background:var(--dsw-alias-bg-overlay); border:1px solid var(--dsw-alias-border-l2); border-radius:10px; padding:10px 12px; box-shadow:0 8px 24px rgba(0,0,0,.18); pointer-events:auto; min-width:200px; max-width:290px; animation:uh-tip-in .16s ease both; }\n.uh-tip-date { font-size:12px; font-weight:600; color:var(--dsw-alias-label-primary); margin-bottom:6px; }\n.uh-tip-row { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--dsw-alias-label-primary); padding:3px 6px; margin:0 -6px; border-radius:6px; cursor:pointer; transition:background-color .12s ease; }\n.uh-tip-row:hover { background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); }\n.uh-tip-row .uh-n { margin-left:auto; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-secondary); }\n.uh-tip-tokens { font-size:11px; color:var(--dsw-alias-label-secondary); margin-top:6px; border-top:1px solid var(--dsw-alias-border-l1); padding-top:6px; }\n.uh-tbl-title { font-size:13px; font-weight:600; color:var(--dsw-alias-label-primary); margin:0 0 10px; }\n.uh-tbl-scroll { overflow-x:auto; }\n.uh-hrow, .uh-row { display:grid; grid-template-columns:minmax(160px,2.2fr) .7fr .9fr .9fr .9fr .9fr 1.1fr .9fr .8fr 1fr; gap:8px; align-items:center; min-width:900px; padding:7px 10px; border-radius:8px; font-size:12px; }\n.uh-model-hrow, .uh-model-row { display:grid; grid-template-columns:minmax(190px,2.2fr) .7fr .9fr .9fr .9fr .9fr 1.1fr .9fr .8fr; gap:8px; align-items:center; min-width:860px; padding:7px 10px; border-radius:8px; font-size:12px; }\n.uh-hrow { color:var(--dsw-alias-label-secondary); font-size:11px; }\n.uh-row { cursor:pointer; border:1px solid transparent; transition:background-color .15s ease, border-color .15s ease; }\n.uh-row:hover { background:var(--dsw-alias-bg-layer-2); }\n.uh-row.uh-sel { border-color:var(--dsw-alias-brand-primary); }\n.uh-num { text-align:right; font-variant-numeric:tabular-nums; color:var(--dsw-alias-label-primary); }\n.uh-hrow .uh-num { color:var(--dsw-alias-label-secondary); }\n.uh-ws-title { color:var(--dsw-alias-label-primary); font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-row-title-wrap { min-width:0; }\n.uh-ws-path { color:var(--dsw-alias-label-secondary); font-size:11px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-barwrap { height:5px; border-radius:3px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; margin-top:3px; }\n.uh-barwrap.uh-bar-thin { height:3px; margin-top:1px; }\n.uh-barfill { height:100%; border-radius:3px; transform-origin:left center; animation:uh-bar-grow .7s cubic-bezier(.22,.61,.36,1) both; transition:width .5s cubic-bezier(.22,.61,.36,1); }\n.uh-empty { color:var(--dsw-alias-label-secondary); font-size:12px; text-align:center; padding:26px 0; }\n.uh-note { font-size:11px; color:var(--dsw-alias-label-secondary); line-height:1.6; }\n.uh-side-entry { width:100%; border:0; background:transparent; color:var(--dsw-alias-label-secondary); border-radius:8px; min-height:36px; padding:7px 10px; display:flex; align-items:center; gap:9px; font:inherit; font-size:13px; cursor:pointer; text-align:left; }\n.uh-side-entry:hover { background:var(--dsw-alias-interactive-bg-hover); color:var(--dsw-alias-label-primary); }\n.uh-side-entry-icon { width:18px; text-align:center; flex:none; font-size:15px; }\n.uh-side-entry-label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-boundary-fallback { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px; min-height:360px; padding:24px; border:1px solid var(--dsw-alias-border-l1); border-radius:12px; background:var(--dsw-alias-bg-layer-1); text-align:center; }\n.uh-boundary-title { color:var(--dsw-alias-label-primary); font-size:15px; font-weight:650; }\n.uh-boundary-note { max-width:420px; color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.6; }\n.uh-side-modal { position:fixed; inset:0; z-index:1100; background:color-mix(in srgb, #000 44%, transparent); display:flex; align-items:stretch; justify-content:center; padding:26px; }\n.uh-side-dialog { width:min(1120px, 100%); overflow-x:hidden; overflow-y:scroll; scrollbar-gutter:stable; scrollbar-width:auto; scrollbar-color:#707780 #1d1f22; background:var(--dsw-alias-bg-base); border:1px solid var(--dsw-alias-border-l2); border-radius:14px; box-shadow:0 18px 52px rgba(0,0,0,.35); padding:18px; }\n.uh-side-dialog::-webkit-scrollbar, .uh-pricing-table-wrap::-webkit-scrollbar { width:12px; height:12px; }\n.uh-side-dialog::-webkit-scrollbar-track, .uh-pricing-table-wrap::-webkit-scrollbar-track { background:#1d1f22; border-left:1px solid #363a40; }\n.uh-side-dialog::-webkit-scrollbar-thumb, .uh-pricing-table-wrap::-webkit-scrollbar-thumb { background:#707780; border:3px solid #1d1f22; border-radius:6px; }\n.uh-side-dialog::-webkit-scrollbar-thumb:hover, .uh-pricing-table-wrap::-webkit-scrollbar-thumb:hover { background:#9aa1aa; }\n.uh-side-dialog-head { display:flex; justify-content:flex-end; margin-bottom:8px; }\n@media (max-width: 640px) { .uh-side-modal { padding:0; } .uh-side-dialog { border-radius:0; border:0; padding:14px; } }\n/* iOS-style dashboard: grouped surfaces, tactile controls, and an elevated sheet. */\n.uh-page { gap:18px; max-width:1160px; margin:0 auto; padding:4px 2px 34px; font-family:-apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif; }\n.uh-head { position:sticky; top:-18px; z-index:20; margin:0 -2px; padding:18px 2px 14px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 88%, transparent); backdrop-filter:blur(18px) saturate(150%); border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 76%, transparent); }\n.uh-title { font-size:22px; line-height:1.2; font-weight:700; letter-spacing:0; }\n.uh-actions { gap:8px; }\n.uh-range { padding:2px; gap:2px; border:0; border-radius:9px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent); overflow:visible; }\n.uh-range button, .uh-range button + button { min-height:28px; border:0; border-radius:7px; padding:4px 10px; }\n.uh-range button.uh-on { background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 3px rgba(0,0,0,.16); }\n.uh-refresh { min-height:30px; border:0; border-radius:15px; padding:5px 12px; display:inline-flex; align-items:center; justify-content:center; gap:6px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-brand-primary); font-weight:600; }\n.uh-line-icon { flex:none; }\n.uh-icon-button { width:30px; padding:0; }\n.uh-refresh:hover { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, var(--dsw-alias-bg-layer-1)); }\n.uh-progress { padding:10px 12px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 9%, var(--dsw-alias-bg-layer-1)); border:0; border-radius:12px; }\n.uh-cards { grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); gap:10px; border:0; border-radius:0; overflow:visible; background:transparent; }\n.uh-card { min-height:84px; padding:12px 14px; gap:4px; border:0; border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.07), 0 8px 22px rgba(0,0,0,.05); animation:none; }\n.uh-card:first-child { border:0; background:color-mix(in srgb, #0a84ff 15%, var(--dsw-alias-bg-layer-1)); }\n.uh-card:nth-child(2) { background:color-mix(in srgb, #30d158 13%, var(--dsw-alias-bg-layer-1)); }\n.uh-card:nth-child(3) { background:color-mix(in srgb, #ff9f0a 14%, var(--dsw-alias-bg-layer-1)); }\n.uh-card:hover { transform:translateY(-2px); box-shadow:0 12px 28px rgba(0,0,0,.12); }\n.uh-card-label { display:flex; align-items:center; gap:6px; font-size:12px; font-weight:600; letter-spacing:0; }\n.uh-ios-summary-label, .uh-section-title, .uh-title-with-icon { display:flex; align-items:center; gap:7px; }\n.uh-section-title { margin-bottom:12px; color:var(--dsw-alias-label-primary); font-size:14px; font-weight:650; }\n/* Raise the complete reading scale without changing the data grid geometry. */\n.uh-page { font-size:14px; }\n.uh-range button, .uh-refresh { font-size:13px; }\n.uh-card-label, .uh-ios-summary-label { font-size:13px; }\n.uh-card-sub, .uh-ios-summary-caption, .uh-note { font-size:12px; }\n.uh-hrow, .uh-row, .uh-model-hrow, .uh-model-row { font-size:13px; }\n.uh-tbl-title { font-size:15px; }\n.uh-num { font-variant-numeric:tabular-nums; }\n.uh-card-value { font-size:23px; font-weight:700; letter-spacing:0; }\n.uh-card-sub { font-size:11px; line-height:1.45; }\n.uh-panel { padding:16px; border:0; border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 6px 18px rgba(0,0,0,.04); }\n.uh-hm-head { margin-bottom:12px; }\n.uh-chip { border:0; border-radius:14px; padding:5px 10px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 7%, transparent); }\n.uh-chip.uh-on { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 18%, transparent); color:var(--dsw-alias-brand-primary); }\n.uh-hrow, .uh-row, .uh-model-hrow, .uh-model-row { border-radius:10px; }\n.uh-hrow, .uh-model-hrow { position:sticky; top:66px; z-index:2; background:var(--dsw-alias-bg-layer-1); border-bottom:1px solid var(--dsw-alias-border-l1); }\n.uh-row, .uh-model-row { padding-top:9px; padding-bottom:9px; }\n.uh-row:nth-child(even) { background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 52%, transparent); }\n.uh-side-entry { min-height:40px; border:0; border-radius:12px; padding:8px 10px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 9%, transparent); color:var(--dsw-alias-brand-primary); font-weight:600; }\n.uh-side-entry:hover { border:0; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 17%, transparent); }\n.uh-side-entry-icon { color:var(--dsw-alias-brand-primary); font-weight:700; }\n.uh-side-modal { align-items:flex-end; padding:0; background:rgba(0,0,0,.34); backdrop-filter:blur(8px); }\n.uh-side-dialog { width:min(1260px, 100%); max-height:calc(100vh - 44px); border:0; border-radius:24px 24px 0 0; padding:22px 24px 28px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); box-shadow:0 -10px 44px rgba(0,0,0,.25); }\n.uh-side-dialog-head { position:sticky; top:-22px; z-index:8; justify-content:center; height:22px; margin:-22px -24px 8px; padding:8px 24px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); border:0; }\n.uh-side-dialog-head::before { content:""; width:36px; height:5px; border-radius:3px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent); }\n.uh-side-dialog-head .uh-refresh { position:absolute; right:20px; top:7px; min-height:28px; background:transparent; }\n.uh-close-button { width:30px; padding:0; font-size:22px; line-height:1; color:var(--dsw-alias-label-secondary); }\n.uh-close-button:hover { color:var(--dsw-alias-label-primary); background:color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent); }\n@media (max-width:640px) { .uh-page { gap:14px; padding-bottom:20px; } .uh-head { position:static; padding:4px 0 10px; } .uh-title { font-size:20px; } .uh-custom-range { grid-template-columns:1fr; align-items:stretch; } .uh-custom-range-fields { grid-template-columns:repeat(2, minmax(0, 1fr)); } .uh-custom-range-actions { justify-content:flex-end; } .uh-side-dialog { max-height:calc(100vh - 8px); border-radius:20px 20px 0 0; padding:18px 14px 24px; } .uh-side-dialog-head { top:-18px; margin:-18px -14px 8px; padding:7px 14px; } .uh-card-value { font-size:22px; } }\n/* Navigation separates the dashboard into three focused iOS-style surfaces. */\n.uh-ios-tabs { display:grid; grid-template-columns:repeat(3, 1fr); gap:4px; padding:4px; border-radius:14px; background:color-mix(in srgb, var(--dsw-alias-label-primary) 9%, transparent); }\n.uh-ios-tab { min-height:32px; border:0; border-radius:10px; background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:13px; font-weight:600; cursor:pointer; }\n.uh-ios-tab.uh-on { color:var(--dsw-alias-label-primary); background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 4px rgba(0,0,0,.16); }\n.uh-ios-summary { display:flex; flex-direction:column; gap:12px; background:transparent; box-shadow:none; }\n.uh-ios-summary-hero { display:grid; grid-template-columns:minmax(0,1fr) minmax(320px,.48fr); min-height:142px; padding:20px 22px; border:1px solid var(--dsw-alias-border-l1); border-radius:18px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 8px 22px rgba(0,0,0,.06); }\n.uh-ios-summary-total { min-width:0; min-height:0; padding:0; border-radius:0; display:flex; align-items:center; justify-content:flex-start; gap:16px; background:transparent; box-shadow:none; }\n.uh-ios-summary-total-icon { display:grid; place-items:center; flex:none; width:54px; height:54px; border-radius:16px; background:color-mix(in srgb,#0a84ff 18%,var(--dsw-alias-bg-layer-2)); color:#0a84ff; }\n.uh-ios-summary-total-copy { min-width:0; }\n.uh-ios-summary-label { font-size:13px; font-weight:600; color:var(--dsw-alias-label-secondary); }\n.uh-ios-summary-total .uh-ios-summary-label { font-size:14px; }\n.uh-ios-summary-value { margin-top:7px; font-size:40px; line-height:1; font-weight:750; letter-spacing:0; color:var(--dsw-alias-label-primary); }\n.uh-unit { margin-left:6px; color:var(--dsw-alias-label-secondary); font-size:.4em; font-weight:650; white-space:nowrap; vertical-align:baseline; }\n.uh-wsbar-num .uh-unit { font-size:.78em; margin-left:3px; }\n.uh-ios-summary-caption { margin-top:8px; font-size:12px; color:var(--dsw-alias-label-secondary); }\n.uh-ios-summary-meta { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); align-items:center; min-width:0; gap:0; padding:0 0 0 22px; border-left:1px solid var(--dsw-alias-border-l1); background:transparent; font-size:12px; color:var(--dsw-alias-label-secondary); }\n.uh-ios-summary-meta-stat { min-width:0; padding:4px 22px; }\n.uh-ios-summary-meta-stat + .uh-ios-summary-meta-stat { border-left:1px solid var(--dsw-alias-border-l1); }\n.uh-ios-summary-meta-label { display:flex; align-items:center; gap:7px; color:var(--dsw-alias-label-secondary); font-size:12px; font-weight:600; white-space:nowrap; }\n.uh-ios-summary-meta-value { margin-top:7px; color:var(--dsw-alias-label-primary); font-size:24px; line-height:1; font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.uh-ios-summary-meta-cost .uh-ios-summary-meta-value { color:#30d158; }\n.uh-ios-summary-meta-caption { margin-top:7px; color:var(--dsw-alias-label-secondary); font-size:11px; white-space:nowrap; }\n.uh-ios-metrics { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:10px; }\n.uh-ios-metric { min-width:0; min-height:108px; padding:16px 18px; border:1px solid var(--dsw-alias-border-l1); border-radius:18px; display:flex; flex-direction:column; justify-content:center; gap:10px; background:var(--dsw-alias-bg-layer-1); box-shadow:0 1px 2px rgba(0,0,0,.06), 0 8px 20px rgba(0,0,0,.05); animation:uh-card-in .35s ease both; }\n.uh-ios-metrics > .uh-card { min-width:0; min-height:141px; padding:16px 18px; gap:4px; border:0; border-radius:18px; }\n.uh-ios-metrics > .uh-card .uh-card-value { min-width:0; font-size:23px; line-height:1.2; font-weight:700; white-space:nowrap; }\n.uh-ios-metrics > .uh-card .uh-card-sub { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n.uh-ios-metrics > .uh-card:nth-child(-n+3) { justify-content:center; }\n.uh-ios-metric-label { display:flex; align-items:center; gap:8px; min-width:0; color:var(--dsw-alias-label-secondary); font-size:13px; font-weight:600; white-space:nowrap; }\n.uh-ios-metric-label .uh-line-icon { flex:none; }\n.uh-ios-metric-value { min-width:0; color:var(--dsw-alias-label-primary); font-size:26px; line-height:1.05; font-weight:700; font-variant-numeric:tabular-nums; white-space:nowrap; }\n.uh-ios-metric-input { background:color-mix(in srgb,#0a84ff 11%,var(--dsw-alias-bg-layer-1)); }\n.uh-ios-metric-input .uh-line-icon { color:#0a84ff; }\n.uh-ios-metric-output { background:color-mix(in srgb,#bf5af2 10%,var(--dsw-alias-bg-layer-1)); }\n.uh-ios-metric-output .uh-line-icon { color:#bf5af2; }\n.uh-ios-metric-write { background:color-mix(in srgb,#ff9f0a 11%,var(--dsw-alias-bg-layer-1)); }\n.uh-ios-metric-write .uh-line-icon { color:#ff9f0a; }\n.uh-ios-metric-read { background:color-mix(in srgb,#30d158 11%,var(--dsw-alias-bg-layer-1)); }\n.uh-ios-metric-read .uh-line-icon { color:#30d158; }\n.uh-ios-metric-rate { background:var(--dsw-alias-bg-layer-1); }\n.uh-ios-metric-rate .uh-line-icon { color:#30d158; }\n.uh-ios-metric-rate-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px; min-width:0; }\n.uh-ios-metric-rate-value { flex:none; color:#30d158; font-size:24px; line-height:1; font-weight:700; font-variant-numeric:tabular-nums; }\n.uh-ios-metric-rate-detail { min-width:0; overflow:hidden; color:var(--dsw-alias-label-secondary); font-size:14px; line-height:1.2; font-weight:600; font-variant-numeric:tabular-nums; text-overflow:ellipsis; white-space:nowrap; }\n.uh-ios-metric-bar { height:7px; border-radius:4px; background:var(--dsw-alias-bg-layer-2); overflow:hidden; }\n.uh-ios-metric-fill { height:100%; border-radius:inherit; background:#30d158; transition:width .35s ease; }\n.uh-token-semantics { display:flex; align-items:flex-start; gap:8px; padding:10px 12px; border-radius:12px; background:color-mix(in srgb, var(--dsw-alias-brand-primary) 8%, var(--dsw-alias-bg-layer-1)); color:var(--dsw-alias-label-secondary); font-size:12px; line-height:1.55; }\n.uh-token-semantics .uh-line-icon { margin-top:1px; color:var(--dsw-alias-brand-primary); }\n.uh-ios-list-panel { min-height:360px; }\n.uh-donut-chart { margin:0 0 18px; }\n.uh-donut-title { display:flex; align-items:center; gap:7px; margin-bottom:12px; color:var(--dsw-alias-label-primary); font-size:14px; font-weight:650; }\n.uh-donut-layout { display:grid; grid-template-columns:minmax(220px,300px) minmax(0,1fr); gap:24px; align-items:center; }\n.uh-donut-visual { position:relative; width:min(100%,280px); aspect-ratio:1; margin:0 auto; }\n.uh-donut-svg { display:block; width:100%; height:100%; overflow:visible; }\n.uh-donut-track { opacity:.78; }\n.uh-donut-segment { fill:none; stroke-dasharray:1; stroke-dashoffset:1; animation:uh-donut-draw .95s cubic-bezier(.22,.61,.36,1) both; cursor:pointer; outline:none; transition:filter .15s ease, opacity .15s ease; }\n.uh-donut-segment:hover, .uh-donut-segment:focus-visible, .uh-donut-segment.uh-active { filter:brightness(1.12); }\n.uh-donut-tooltip { position:absolute; top:0; left:0; z-index:3; display:flex; align-items:flex-start; gap:8px; max-width:190px; padding:9px 10px; border:1px solid var(--dsw-alias-border-l2); border-radius:8px; background:color-mix(in srgb, var(--dsw-alias-bg-base) 94%, transparent); box-shadow:0 10px 24px rgba(0,0,0,.22); backdrop-filter:blur(10px); color:var(--dsw-alias-label-primary); pointer-events:none; font-size:12px; line-height:1.4; transition:left .12s cubic-bezier(.22,.61,.36,1), top .12s cubic-bezier(.22,.61,.36,1); }\n.uh-donut-tooltip > div { min-width:0; display:flex; flex-direction:column; gap:4px; }\n.uh-donut-tooltip strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }\n.uh-donut-tooltip span:not(.uh-donut-dot) { color:var(--dsw-alias-label-secondary); font-size:11px; }\n.uh-donut-tooltip-cost { color:var(--dsw-alias-label-primary) !important; font-variant-numeric:tabular-nums; }\n.uh-donut-center { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; pointer-events:none; }\n.uh-donut-center strong { color:var(--dsw-alias-label-primary); font-size:28px; line-height:1; font-weight:750; font-variant-numeric:tabular-nums; }\n.uh-donut-center span { margin-top:5px; color:var(--dsw-alias-label-secondary); font-size:13px; }\n.uh-donut-legend { min-width:0; }\n.uh-donut-legend-row { display:grid; grid-template-columns:12px minmax(180px,1fr) minmax(250px,.8fr) 54px; gap:10px; align-items:center; min-height:58px; padding:8px 0; border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 78%, transparent); }\n.uh-donut-legend-row:last-child { border-bottom:0; }\n.uh-donut-dot { width:12px; height:12px; border-radius:50%; }\n.uh-donut-legend-copy { min-width:0; display:flex; flex-direction:column; gap:5px; }\n.uh-donut-legend-copy strong { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-primary); font-size:13px; font-weight:650; }\n.uh-donut-legend-metrics { display:grid; grid-template-columns:minmax(120px,1fr) minmax(92px,auto); align-items:center; gap:14px; min-width:0; }\n.uh-donut-legend-metrics span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--dsw-alias-label-secondary); font-size:12px; font-variant-numeric:tabular-nums; text-align:right; }\n.uh-donut-legend-metrics .uh-donut-cost { color:var(--dsw-alias-label-secondary); font-size:12px; }\n.uh-donut-percent { min-width:48px; color:var(--dsw-alias-label-secondary); font-size:12px; font-variant-numeric:tabular-nums; text-align:right; }\n/* Each detail table owns its scrolling and sticky header; sections must not overlap in the page scroll. */\n.uh-tbl-scroll { max-height:360px; overflow:auto; border-radius:12px; background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 55%, transparent); }\n.uh-hrow, .uh-model-hrow { position:sticky; top:0; z-index:3; border-bottom:1px solid var(--dsw-alias-border-l1); box-shadow:0 1px 0 color-mix(in srgb, var(--dsw-alias-bg-base) 70%, transparent); }\n.uh-row, .uh-model-row { min-height:48px; border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 72%, transparent); }\n.uh-row:last-child, .uh-model-row:last-child { border-bottom:0; }\n@media (max-width:640px) { .uh-tbl-scroll { max-height:300px; border-radius:10px; } }\n@media (max-width:640px) { .uh-hm-body { min-width:720px; } .uh-donut-legend-row { grid-template-columns:12px minmax(0,1fr) 48px; gap:8px; } .uh-donut-legend-copy { grid-column:2; grid-row:1; } .uh-donut-legend-metrics { grid-column:2 / -1; grid-row:2; grid-template-columns:minmax(0,1fr) minmax(0,auto); gap:8px; } .uh-donut-percent { grid-column:3; grid-row:1; } .uh-ios-summary-hero { grid-template-columns:1fr; min-height:0; gap:18px; padding:18px; } .uh-ios-summary-total { align-items:flex-start; } .uh-ios-summary-meta { grid-template-columns:repeat(2,minmax(0,1fr)); padding:16px 0 0; border-left:0; border-top:1px solid var(--dsw-alias-border-l1); } .uh-ios-summary-meta-stat { padding:0 12px; } .uh-ios-summary-meta-stat:first-child { padding-left:0; } .uh-ios-summary-meta-stat:last-child { padding-right:0; } .uh-ios-summary-value { font-size:31px; } .uh-ios-metrics { grid-template-columns:repeat(2,minmax(0,1fr)); } .uh-ios-metric:last-child { grid-column:1 / -1; } .uh-ios-metric-value { font-size:24px; } .uh-donut-layout { grid-template-columns:1fr; gap:12px; } .uh-donut-visual { width:min(100%,250px); } }\n@keyframes uh-cell-in { from { opacity:0; transform:scale(.4); } to { opacity:1; transform:scale(1); } }\n@keyframes uh-glow { 0% { box-shadow:0 0 0 0 rgba(46,160,67,.5); } 70% { box-shadow:0 0 0 5px rgba(46,160,67,0); } 100% { box-shadow:0 0 0 0 rgba(46,160,67,0); } }\n@keyframes uh-card-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }\n@keyframes uh-bar-grow { from { transform:scaleX(0); } to { transform:scaleX(1); } }\n@keyframes uh-panel-in { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }\n@keyframes uh-trend-draw { from { stroke-dashoffset:var(--uh-draw-length); opacity:.2; } to { stroke-dashoffset:0; opacity:1; } }\n@keyframes uh-trend-fill { from { opacity:0; } to { opacity:1; } }\n@keyframes uh-donut-draw { from { stroke-dashoffset:1; opacity:.25; } to { stroke-dashoffset:0; opacity:1; } }\n@keyframes uh-spinner-turn { to { transform:rotate(360deg); } }\n@keyframes uh-menu-in { from { opacity:0; transform:translateY(-4px) scale(.97); } to { opacity:1; transform:translateY(0) scale(1); } }\n@keyframes uh-tip-in { from { opacity:0; } to { opacity:1; } }\n@media (prefers-reduced-motion: reduce) {\n .uh-cell, .uh-card, .uh-ios-metric, .uh-barfill, .uh-anim-panel, .uh-trend-panel, .uh-trend-line-draw, .uh-trend-area, .uh-trend-point, .uh-trend-spinner, .uh-donut-segment, .uh-records-panel, .uh-record-detail, .uh-tip, .uh-language-options { animation:none !important; stroke-dashoffset:0 !important; opacity:1 !important; }\n .uh-card, .uh-cell, .uh-ios-metric-fill, .uh-barfill, .uh-fill, .uh-refresh, .uh-chip, .uh-row, .uh-tip-row, .uh-trend-tooltip, .uh-language-trigger, .uh-language-caret, .uh-language-option { transition:none !important; }\n}\n'}const he=(e,t,a)=>{const r=new URLSearchParams({start:e.start,end:e.end,utc:e.utc?"1":"0",limit:String(a||100)});return e.workspaceId&&r.set("workspaceId",e.workspaceId),e.provider&&r.set("provider",e.provider),e.modelKey&&r.set("modelKey",e.modelKey),t&&r.set("cursor",t),fetch("/api/all-usage/records?"+r.toString(),{headers:{accept:"application/json"}}).then(e=>{if(!e.ok){const t=new Error("HTTP "+e.status);throw t.status=e.status,t}return e.json()})},ge=()=>fetch("/api/all-usage/pricing",{headers:{accept:"application/json"}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()}),be=(e,t,a)=>fetch("/api/all-usage/pricing",{method:"POST",headers:{"content-type":"application/json","x-all-usage-request-token":a},body:JSON.stringify({pricing:e,backfill:!0===t})}).then(e=>{if(!e.ok){const t=new Error("HTTP "+e.status);throw t.status=e.status,t}return e.json()}),fe="dsh-all-usage.language";function xe(){try{return"en"===window.localStorage.getItem(fe)?"en":"zh"}catch(e){return"zh"}}const ye="dsh-all-usage.ui-state";function ve(){try{const e=window.localStorage.getItem(ye),t=e?JSON.parse(e):{};if(null===t||"object"!=typeof t||Array.isArray(t))return{};const a={};return["logs","model","workspace"].includes(t.detailView)&&(a.detailView=t.detailView),["route","model","provider"].includes(t.modelView)&&(a.modelView=t.modelView),["today","30d","90d","all"].includes(t.range)&&(a.range=t.range),"boolean"==typeof t.pricingAutoSync&&(a.pricingAutoSync=t.pricingAutoSync),a}catch(e){return{}}}function we(e){try{const t=ve();window.localStorage.setItem(ye,JSON.stringify(Object.assign({},t,e)))}catch(e){}}function ke(e){const t=e.timerCtx,a="en"===e.language?"en":"zh",n=(e,t)=>"en"===a?t:e,p="en"===a,g=r.useRef(null);null===g.current&&(g.current=ve());const v=g.current,E=new Date,N=i(E,p),[M,F]=r.useState(null),[O,W]=r.useState(null),[L,q]=r.useState(""),[V,Y]=r.useState(0),[J,G]=r.useState(null),[$,Q]=r.useState(()=>v.range||"today"),[ee,te]=r.useState({start:"",end:""}),[ae,re]=r.useState({start:"",end:""}),[ie,le]=r.useState(!1),[me,fe]=r.useState(()=>v.modelView||"route"),[xe,ye]=r.useState(null),[ke,Ee]=r.useState(null),[Ne,Me]=r.useState(null),[Ce,Se]=r.useState(null),[ze,Ae]=r.useState(""),[Te,Re]=r.useState(!1),[je,De]=r.useState(""),[Ie,Ue]=r.useState(["total","input","cacheRead","output"]),[Fe,Oe]=r.useState(()=>v.detailView||"logs"),[We,Le]=r.useState(null),[qe,Pe]=r.useState(null),[Ke,Be]=r.useState([]),[He,Ve]=r.useState(null),[Ye,Je]=r.useState(!1),[Xe,Ze]=r.useState(0),[Ge,$e]=r.useState(!1),[_e,Qe]=r.useState(!1),[et,tt]=r.useState(""),[at,rt]=r.useState(!1),[nt,it]=r.useState({}),[st,lt]=r.useState(!1),[ot,ct]=r.useState(null),[ut,dt]=r.useState(null),[pt,mt]=r.useState(!1),[ht,gt]=r.useState(!1),[bt,ft]=r.useState(!1),[xt,yt]=r.useState(!1),[vt,wt]=r.useState(""),[kt,Et]=r.useState({}),[Nt,Mt]=r.useState(null),[Ct,St]=r.useState({}),[zt,At]=r.useState(null),[Tt,Rt]=r.useState({}),[jt,Dt]=r.useState(null),It=r.useRef({}),Ut=r.useRef(0),Ft=r.useRef({}),Ot=r.useRef(!1),Wt=r.useRef(()=>{}),Lt=()=>{Ut.current+=1;const e=Ft.current;for(const t of Object.keys(e))clearTimeout(e[t]);Ft.current={},It.current={},Et({})},[qt,Pt]=r.useState(!1),Kt=r.useRef(null),Bt=r.useRef(null),Ht=r.useRef(null);null===Ht.current&&(Ht.current=u());const Vt=r.useRef(null);null===Vt.current&&(Vt.current=u());const Yt=r.useRef(null);null===Yt.current&&(Yt.current=u());const Jt=r.useRef(null);null===Jt.current&&(Jt.current=u());const Xt=r.useRef(null);null===Xt.current&&(Xt.current=u());const Zt=r.useRef(null);null===Zt.current&&(Zt.current=u());const Gt=Ht.current,$t=Vt.current,_t=Yt.current,Qt=Jt.current,ea=Xt.current,ta=Zt.current,aa=r.useRef(()=>{}),ra=r.useCallback(()=>{if(!Ot.current||ht||bt||xt)return;const e=ta.next();mt(!0),ge().then(t=>{if(ta.isCurrent(e)){if(!t||"object"!=typeof t||!t.config)return wt("load"),void mt(!1);Lt(),ct(t),dt(e=>R(e,t)),wt(""),mt(!1)}},()=>{ta.isCurrent(e)&&(wt("load"),mt(!1))})},[ht,bt,xt]);r.useEffect(()=>{Wt.current=ra});r.useEffect(()=>{we({detailView:Fe})},[Fe]),r.useEffect(()=>{we({modelView:me})},[me]),r.useEffect(()=>{"custom"!==$&&we({range:$})},[$]);const na=r.useMemo(()=>null===M?null:function(e,t,a,r,n,i,s){const l=K(e,t,a,r);return null===l?null:{start:l.start,end:l.end,utc:!0===a,workspaceId:n||null,provider:i||null,modelKey:s||null}}(M,$,p,ee,xe,ke,Ne),[M,$,p,ee.start,ee.end,xe,ke,Ne]),ia=B(na),sa=h(null!==O&&"object"==typeof O?O:M),la=null!==We&&We.baseKey===ia?We.scope:na,oa=B(la),ca=r.useRef(ia);r.useEffect(()=>{ca.current!==ia&&(ca.current=ia,Le(null),Pe(null))},[ia]),r.useEffect(()=>{let e=!0,a=!1,r="",n=null,i=0,s=0,l=null;const o=()=>{null!==l&&(clearTimeout(l),l=null)},c=t=>{if(""===r)return;const a=_t.next();((e,t)=>fetch("/api/all-usage/balance"+(e?"?force=1":""),{headers:{accept:"application/json","x-all-usage-request-token":t}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()}))(!0===t,r).then(t=>{e&&_t.isCurrent(a)&&t&&G(t)},()=>{})},u=t=>{if(!e||null!==l)return;const a="full"===t?i+=1:s+=1;l=setTimeout(()=>{l=null,e&&("full"===t?p():h())},function(e){const t=Math.max(1,Math.min(4,"number"==typeof e&&Number.isFinite(e)?e:1));return 5e3*Math.pow(2,t-1)}(a))},p=()=>{$t.next();const t=Gt.next();fetch("/api/all-usage",{headers:{accept:"application/json"}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()}).then(s=>{if(!e||!Gt.isCurrent(t))return;if(null===s||"object"!=typeof s)return q("full"),void u("full");n=s,s.scan&&(a=!!s.scan.done);const l="string"==typeof s.requestToken?s.requestToken:"",d=""!==l&&l!==r;r=l,i=0,o(),q(""),Y(Date.now()),W(s),F(s),d&&c(!1)},()=>{e&&Gt.isCurrent(t)&&(q("full"),u("full"))})},h=()=>{if(null===n)return void p();const t=$t.next();fetch("/api/all-usage/status",{headers:{accept:"application/json"}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()}).then(r=>{if(!e||!$t.isCurrent(t))return;if(null===r||"object"!=typeof r)return q("status"),void u("status");s=0;const i=function(e,t){if(null===e||"object"!=typeof e||null===t||"object"!=typeof t)return"full";if("string"!=typeof e.instanceId||"string"!=typeof t.instanceId||""===e.instanceId||e.instanceId!==t.instanceId)return"full";if(m(e)&&m(t)){if(e.metadataRevision!==t.metadataRevision)return"full";if(e.pricingRevision!==t.pricingRevision)return"full";if(e.dataRevision!==t.dataRevision)return"query";const a=e.scan,r=t.scan;return e.scanRevision!==t.scanRevision||a&&r&&!!a.done!=!!r.done?"status":"none"}const a=d(e),r=d(t);if(null===a||null===r||a!==r)return"full";const n=e.scan,i=t.scan;return n&&i&&!!n.done!=!!i.done?"full":"none"}(r,n);if(r.scan&&(a=!!r.scan.done),"full"===i)return"number"==typeof r.pricingRevision&&"number"==typeof n.pricingRevision&&r.pricingRevision!==n.pricingRevision&&Wt.current(),void p();n=Object.assign({},n,r),W(r),o(),q("")},()=>{e&&$t.isCurrent(t)&&(q("status"),u("status"))})};p();const g=t.interval(()=>{a||null!==l||h()},2e3),b=t.interval(()=>{a&&null===l&&h()},15e3),f=t.interval(()=>{c(!1)},6e4);return aa.current=()=>{o(),i=0,s=0,p(),c(!0)},()=>{e=!1,o(),g(),b(),f();for(const e of Object.values(Ft.current))clearTimeout(e);Ft.current={}}},[]),r.useEffect(()=>{if(!qt||"undefined"==typeof document)return;const e=e=>{Kt.current&&!Kt.current.contains(e.target)&&Pt(!1)};return document.addEventListener("pointerdown",e),()=>document.removeEventListener("pointerdown",e)},[qt]),r.useEffect(()=>{if(null===na||""===ia)return;const e=Qt.next(),t=sa;Re(!0),De(""),(e=>{const t=new URLSearchParams({start:e.start,end:e.end,utc:e.utc?"1":"0"});return e.workspaceId&&t.set("workspaceId",e.workspaceId),e.provider&&t.set("provider",e.provider),e.modelKey&&t.set("modelKey",e.modelKey),fetch("/api/all-usage/query?"+t.toString(),{headers:{accept:"application/json"}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()})})(na).then(a=>{if(Qt.isCurrent(e)){if(null===a||"object"!=typeof a||null===t||h(a)!==t)return De("stale"),void Re(!1);Se(a),Ae(ia),Re(!1),De("")}},()=>{Qt.isCurrent(e)&&(Re(!1),De("query"))})},[ia,sa]);const ua=r.useCallback(e=>{null!==e&&""!==ia&&(Le({baseKey:ia,scope:{...e}}),Oe("logs"),Pe(null),tt(""))},[ia]),da=r.useCallback(e=>{null!==na&&"string"==typeof e&&ua({...na,start:e,end:e})},[na,ua]),pa="logs"===Fe&&null!==la&&""!==oa;r.useEffect(()=>{if(!pa)return ea.next(),void $e(!1);const e=ea.next(),t=sa;return $e(!0),tt(""),Ve(null),Je(!1),he(la,null,20).then(a=>{if(ea.isCurrent(e)){if(null===a||"object"!=typeof a||!Array.isArray(a.items))return tt("audit"),void $e(!1);if(null===t||h(a)!==t)return tt("stale"),void $e(!1);Be(a.items),Pe(e=>a.items.some(t=>t&&t.id===e)?e:a.items[0]?a.items[0].id:null),Ve(a.nextCursor||null),Je(!0===a.hasMore),$e(!1),tt("")}},t=>{if(ea.isCurrent(e)){if(t&&409===t.status)return $e(!1),tt("stale"),void Ze(e=>e+1);$e(!1),tt("audit")}}),()=>{ea.next()}},[oa,Fe,sa,Xe]),r.useEffect(()=>{null!==We&&"logs"===Fe&&null!==Bt.current&&Bt.current.scrollIntoView({behavior:"smooth",block:"start"})},[We&&B(We.scope),Fe]);const ma=r.useCallback(()=>{aa.current()},[]),ha=r.useCallback(e=>{ye(t=>t===e?null:e)},[]),ga=r.useCallback(()=>{ye(null),Ee(null),Me(null)},[]),ba=r.useCallback(e=>{Ee(e||null)},[]),fa=r.useCallback(e=>{Me(e||null)},[]),xa=p&&Array.isArray(M&&M.byDayUtc)?M.byDayUtc:Array.isArray(M&&M.byDay)?M.byDay:[],ya=c(xa,N).min,va=o(ee,p),wa=function(e,t,a,r){return null!==e&&"object"==typeof e&&l(e.start,r)&&l(e.end,r)?e.start>e.end?"order":e.start<t||e.end>a?"bounds":"":"invalid"}(ae,ya,N,p),ka=null!==Ce&&ze===ia,Ea=ka&&null!==sa&&h(Ce)===sa,Na=ka&&(Ea||""===je),Ma=Na&&Array.isArray(Ce.daily)?Ce.daily:xa,Ca=Na&&Array.isArray(Ce.heatmap)?Ce.heatmap:xa,Sa=null===va?"":va.start+":"+va.end,za=r.useMemo(()=>function(e,t,a,r){const n={totals:{turns:0,calls:0,sessions:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:{currency:"USD",input:"0",output:"0",cacheRead:"0",cacheWrite:"0",baseTotal:"0",total:"0",pricedCalls:0,unpricedCalls:0,ambiguousCalls:0,unsupportedCalls:0}},perWs:[],perModel:[]};if(null===e)return n;if("all"===t)return{totals:e.totals,perWs:e.perWorkspace,perModel:e.perModel||[]};const l=a&&Array.isArray(e.byDayUtc)?e.byDayUtc:Array.isArray(e.byDay)?e.byDay:[];let c,u=null;if("custom"===t){const e=o(r,a);if(null===e)return n;c=e.start,u=e.end}else c=i("today"===t?new Date:s(new Date,"30d"===t?-29:-89,a),a);const d={turns:0,calls:0,sessions:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:{currency:"USD",input:"0",output:"0",cacheRead:"0",cacheWrite:"0",baseTotal:"0",total:"0",pricedCalls:0,unpricedCalls:0,ambiguousCalls:0,unsupportedCalls:0}},p=new Map,m=new Map,h=new Set;for(const e of l){if(e.date<c||null!==u&&e.date>u)continue;const t=Array.isArray(e.sessionIds)?e.sessionIds:[];for(const e of t)h.add(e);d.turns+=e.turns,d.input+=e.tokens.input,d.output+=e.tokens.output,d.cacheRead+=e.tokens.cacheRead,d.cacheWrite+=e.tokens.cacheWrite,d.reasoning+=e.tokens.reasoning,z(d.cost,e.cost);for(const t of e.byWorkspace){let e=p.get(t.workspaceId);void 0===e&&(e={workspaceId:t.workspaceId,turns:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:C()},p.set(t.workspaceId,e)),e.input+=t.input,e.output+=t.output,e.cacheRead+=t.cacheRead,e.cacheWrite+=t.cacheWrite,e.reasoning+=t.reasoning,z(e.cost,t.cost)}for(const t of e.perWorkspace){let e=p.get(t.workspaceId);void 0===e&&(e={workspaceId:t.workspaceId,turns:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:C()},p.set(t.workspaceId,e)),e.turns+=t.turns}for(const t of e.byModel||[]){const e=t.identityKey||t.model;let a=m.get(e);void 0===a&&(a={...t,calls:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:C()},m.set(e,a)),a.calls+=t.calls,a.input+=t.input,a.output+=t.output,a.cacheRead+=t.cacheRead,a.cacheWrite+=t.cacheWrite,a.reasoning+=t.reasoning,d.calls+=Number.isFinite(t.calls)?t.calls:0,z(a.cost,t.cost)}}return d.sessions=h.size,{totals:d,perWs:Array.from(p.values()),perModel:Array.from(m.values())}}(M,$,p,va),[M,$,p,Sa]),Aa=r.useMemo(()=>Na?{totals:Ce.totals,perWs:Ce.perWorkspace||[],perModel:Ce.perModel||[]}:za,[Na,Ce,za]),Ta=Z(Aa.totals.input+Aa.totals.output+Aa.totals.cacheRead+Aa.totals.cacheWrite+Aa.totals.reasoning,t),Ra=(Z(Math.round(10*x(Aa.totals.input,Aa.totals.cacheRead)),t),null!==ke||null!==Ne),ja=Number.isFinite(Aa.totals.calls)&&Aa.totals.calls>0?Aa.totals.calls:Aa.totals.turns,Da=Z(Ra?ja:Aa.totals.turns,t),Ia=Z(ja,t),Ua=e=>e.input+e.output+e.cacheRead+e.cacheWrite+e.reasoning,Fa=r.useMemo(()=>(Array.isArray(Aa.perWs)?Aa.perWs:[]).slice().sort((e,t)=>Ua(t)-Ua(e)),[Aa.perWs]),Oa=r.useMemo(()=>function(e,t,a,r){if("route"===t)return e.slice();const n=new Map;for(const i of e){const e=X(i,a,r),s="model"===t?e.model:e.provider;let l=n.get(s);void 0===l&&(l={model:s,provider:"provider"===t?s:e.provider,calls:0,input:0,output:0,cacheRead:0,cacheWrite:0,reasoning:0,cost:C()},n.set(s,l)),l.calls+=i.calls,l.input+=i.input,l.output+=i.output,l.cacheRead+=i.cacheRead,l.cacheWrite+=i.cacheWrite,l.reasoning+=i.reasoning,z(l.cost,i.cost)}return Array.from(n.values())}(Aa.perModel||[],me,n("未知供应商","Unknown provider"),n("未知模型","Unknown model")).sort((e,t)=>Ua(t)-Ua(e)),[Aa.perModel,me,a]),Wa=M&&Array.isArray(M.workspaces)?M.workspaces:[],La=M&&M.aliases&&"object"==typeof M.aliases?M.aliases:{},qa=r.useMemo(()=>{const e=Array.isArray(za.perModel)?za.perModel.filter(e=>e&&"object"==typeof e):[],t=Array.from(new Set(e.map(e=>"string"==typeof e.provider&&""!==e.provider?e.provider:null).filter(e=>null!==e))).sort(),r=Array.from(new Set(e.map(e=>{const r="string"==typeof e.actualModel&&""!==e.actualModel?e.actualModel:"string"==typeof e.requestedModel&&""!==e.requestedModel?e.requestedModel:null;if(null!==r)return r;const n="string"==typeof e.model&&""!==e.model?e.model:"en"===a?"Unknown model":"未知模型",i=n.indexOf(" / "),s=i>0?n.slice(0,i):"";return i>0&&t.includes(s)?n.slice(i+3):n}))).sort((e,t)=>e.localeCompare(t)),n=new Map((Array.isArray(za.perWs)?za.perWs:[]).map(e=>[e.workspaceId,e])),i=Wa.filter(e=>{return void 0!==(t=n.get(e.id))&&(Number(t.turns)>0||Number(t.calls)>0||Number(t.input)>0||Number(t.output)>0||Number(t.cacheRead)>0||Number(t.cacheWrite)>0||Number(t.reasoning)>0);var t});return{providerOptions:t,modelOptions:r,rangeWorkspaceOptions:i,rangeWorkspaceIds:new Set(i.map(e=>e.id))}},[za.perModel,za.perWs,Wa,a]),{providerOptions:Pa,modelOptions:Ka,rangeWorkspaceOptions:Ba,rangeWorkspaceIds:Ha}=qa,Va=r.useMemo(()=>{const e=new Map,t=new Map;return Wa.forEach((a,r)=>{e.set(a.id,a),t.set(a.id,r)}),{byId:e,indexes:t}},[Wa]),Ya=Va.byId,Ja=Va.indexes,Xa=r.useCallback(e=>{const t=La[e];if("string"==typeof t&&""!==t)return t;const r=Ya.get(e);return r?r.title:"en"===a?"Unknown workspace":"未知工作区"},[La,Ya,a]),Za=r.useMemo(()=>{const e=new Map;for(const t of xa)e.set(t.date,t);return e},[xa]),Ga=r.useMemo(()=>function(e,t){const a=new Date;let r=0;for(let n=0;n<371;n++){const l=s(a,-n,t),o=e.get(i(l,t));if(void 0!==o&&o.turns>0)r+=1;else if(n>0)break}let n=0,l=0;for(let r=0;r<371;r++){const o=s(a,-r,t),c=e.get(i(o,t));void 0!==c&&c.turns>0?(l+=1,l>n&&(n=l)):l=0}return{streak:r,best:n}}(Za,p),[Za,p,N]),$a=M&&M.pricing&&"object"==typeof M.pricing?M.pricing:{},_a=ot&&"object"==typeof ot?ot:$a,Qa=r.useMemo(()=>st?function(e){const t=new Map,a=e&&Array.isArray(e.tierSchedules)?e.tierSchedules:[];for(const e of a)e&&"string"==typeof e.id&&Array.isArray(e.tiers)&&t.set(e.id,e.tiers);return e&&Array.isArray(e.usedModels)?e.usedModels.map(e=>{const a=e&&"string"==typeof e.tierScheduleId?t.get(e.tierScheduleId):null,r=Array.isArray(a)?a:e&&Array.isArray(e.tiers)?e.tiers:[];return Object.assign({},e,{tiers:r.map(e=>Object.assign({},e))})}):[]}(_a):[],[st,_a]),er=r.useMemo(()=>Qa.slice().sort((e,t)=>{const a={unpriced:0,ambiguous:1,unsupported:2,priced:3};return(void 0===a[e.status]?9:a[e.status])-(void 0===a[t.status]?9:a[t.status])||String(e.model||"").localeCompare(String(t.model||""))}).map(e=>({value:String(e.identityKey||e.model||""),label:(e.model||("en"===a?"Unknown model":"未知模型"))+" · "+j(e.status,a),model:U(e.actualModel||e.requestedModel||e.pricingModel),officialModel:U(e.pricingModel)})).filter(e=>""!==e.value),[Qa,a]),tr=r.useMemo(()=>{const e=null!==na?{start:na.start,end:na.end}:K(M,$,p,va),t=Na&&null!==na&&na.start===na.end&&Ce&&Array.isArray(Ce.hourly)?function(e,t){const a=[];for(const r of Array.isArray(e)?e:[]){if(null===r||"object"!=typeof r)continue;const e=Number.isFinite(r.time)?r.time:"string"==typeof r.date?Date.parse(r.date):NaN;if(!Number.isFinite(e))continue;const n=H(r);a.push({date:i(new Date(e),t),time:e,turns:Number.isFinite(r.turns)?r.turns:0,calls:Number.isFinite(r.calls)?r.calls:0,sessions:Number.isFinite(r.sessions)?r.sessions:0,tokens:n,cost:S(r),total:n.input+n.output+n.cacheRead+n.cacheWrite+n.reasoning})}return a.sort((e,t)=>e.time-t.time)}(Ce.hourly,p):[];return t.length>0?t:function(e,t,a){if(null===t||"object"!=typeof t)return[];const r=new Map((Array.isArray(e)?e:[]).filter(e=>e&&"string"==typeof e.date).map(e=>[e.date,e])),n=t.start.split("-").map(Number),s=t.end.split("-").map(Number),l=a?new Date(Date.UTC(n[0],n[1]-1,n[2])):new Date(n[0],n[1]-1,n[2]),o=a?new Date(Date.UTC(s[0],s[1]-1,s[2])):new Date(s[0],s[1]-1,s[2]),c=[];for(;l.getTime()<=o.getTime();){const e=i(l,a),t=r.get(e),n=H(t);c.push({date:e,turns:t&&Number.isFinite(t.turns)?t.turns:0,calls:t&&Number.isFinite(t.calls)?t.calls:0,sessions:t&&Number.isFinite(t.sessions)?t.sessions:0,tokens:n,cost:S(t),total:n.input+n.output+n.cacheRead+n.cacheWrite+n.reasoning}),a?l.setUTCDate(l.getUTCDate()+1):l.setDate(l.getDate()+1)}return c}(Na&&Ce&&Array.isArray(Ce.daily)?Ce.daily:xa,e,p)},[na,Na,Ce,xa,M,$,p,Sa]),ar=Na&&Ce?ia+":"+(h(Ce)||"query"):ia,rr=r.useMemo(()=>({}),[st,ut,pt,ht,bt,xt,vt,_a,$a,Qa,er,zt,jt,Ct,Tt,Nt,kt,M,a]),nr=r.useMemo(()=>Oa.map((e,t)=>({label:e.model,value:Ua(e),cost:e.cost,color:_[t%_.length]})),[Oa]),ir=r.useMemo(()=>Fa.map((e,t)=>({label:Xa(e.workspaceId),value:Ua(e),cost:e.cost,color:_[t%_.length]})),[Fa,Xa]),sr=r.useCallback(e=>{Ue(t=>t.includes(e)?t.length<=1?t:t.filter(t=>t!==e):t.concat(e))},[]);if(r.useEffect(()=>{null===xe||Ha.has(xe)||ye(null),null===ke||Pa.includes(ke)||Ee(null),null===Ne||Ka.includes(Ne)||Me(null)},[xe,ke,Ne,Ha,Pa,Ka]),null===M){const e=""!==L;return r.createElement("div",{className:"uh-page"},r.createElement("div",{className:"uh-panel"},r.createElement("div",{className:"uh-empty"},e?n("无法加载用量统计。请重试。","Unable to load usage statistics. Please retry."):n("正在加载用量统计…","Loading usage statistics…")),e?r.createElement("div",{style:{textAlign:"center"}},r.createElement("button",{className:"uh-refresh",onClick:ma},r.createElement(y,{name:"refresh",size:14}),n("重试","Retry"))):null))}const lr=null!==O&&"object"==typeof O?O:M,or=lr&&lr.scan?lr.scan:M.scan||{done:!0,started:!0,scanned:0,total:0,failed:0},cr=lr&&lr.sync?lr.sync:M.sync||{},ur=Ma,dr=(e,t)=>{const a="string"==typeof M.requestToken?M.requestToken:"";var r,n,i;""!==a&&(r=e,n=String(void 0===t?"":t).trim(),i=a,fetch("/api/all-usage/alias",{method:"POST",headers:{"content-type":"application/json","x-all-usage-request-token":i},body:JSON.stringify({workspaceId:r,alias:n})}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()})).then(e=>{e&&e.ok&&e.aliases&&F(t=>null===t?t:Object.assign({},t,{aliases:e.aliases}))},()=>{})},pr=()=>{ht||bt||xt||(Lt(),ta.next(),mt(!1),lt(!1),Ot.current=!1)},mr=()=>{const e=ta.next();ct(null),dt(null),mt(!0),St({}),Rt({}),At(null),Dt(null),Mt(null),Lt(),wt(""),lt(!0),Ot.current=!0,rt(!1),ge().then(t=>{if(!ta.isCurrent(e))return;if(!t||"object"!=typeof t||!t.config)return void wt("load");const a=T(t);ct(t),dt(a)},()=>{ta.isCurrent(e)&&wt("load")}).finally(()=>{ta.isCurrent(e)&&mt(!1)})},hr=e=>{if(null===ut||ht||bt||xt)return;const t=function(e){const t=e&&Array.isArray(e.mappings)?e.mappings:[];for(const e of t){if(!e||""===String(e.identityKey||e.usageIdentityKey||e.model||"").trim()||""===String(e.catalogModelId||"").trim())return"mapping";if(!["fresh","total","legacy"].includes(e.inputTokenSemantics||"fresh")||!D(void 0===e.multiplier?"1":e.multiplier))return"mapping"}const a=e&&Array.isArray(e.overrides)?e.overrides:[];for(const e of a){if(!e||""===String(e.modelId||"").trim())return"override";if(!["input","output","cacheRead","cacheWrite"].every(t=>D(e[t])))return"override";let t=0;const a=Array.isArray(e.tiers)?e.tiers:[];if(a.length>32)return"tier";for(const e of a){if(!I(e,t))return"tier";t=Number(e.size)}if(!0===e.tiered&&0===a.length)return"tier"}return""}(ut);if(""!==t)return void wt(t);const a="string"==typeof M.requestToken?M.requestToken:"";if(""===a)return void wt("token");const r=ta.next();gt(!0),wt(""),be(ut,e,a).then(e=>{ta.isCurrent(r)&&(e&&!0===e.ok&&e.pricing?(F(t=>null===t?t:Object.assign({},t,{pricing:e.pricing})),ct(e.pricing),dt(T(e.pricing)),St({}),Rt({}),At(null),Dt(null),pr(),aa.current()):wt("save"))},e=>{ta.isCurrent(r)&&wt(e&&403===e.status?"forbidden":"save")}).finally(()=>gt(!1))},gr=()=>{if(ht||bt||xt)return;const e="string"==typeof M.requestToken?M.requestToken:"";var t;""!==e?(ft(!0),wt(""),(t=e,fetch("/api/all-usage/pricing/sync",{method:"POST",headers:{"content-type":"application/json","x-all-usage-request-token":t},body:"{}"}).then(e=>{if(!e.ok){const t=new Error("HTTP "+e.status);throw t.status=e.status,t}return e.json()})).then(e=>{e&&!0===e.ok&&e.pricing?(Lt(),F(t=>null===t?t:Object.assign({},t,{pricing:e.pricing})),ct(e.pricing),dt(t=>R(t,e.pricing)),St({}),Rt({}),At(null),Dt(null),aa.current()):wt("sync")},e=>{wt(e&&403===e.status?"forbidden":"sync")}).finally(()=>ft(!1))):wt("token")},br=(e,t,a)=>{dt(r=>{if(null===r||!Array.isArray(r.mappings)||!r.mappings[e])return r;const n=r.mappings.slice();return n[e]=Object.assign({},n[e],{[t]:a}),Object.assign({},r,{mappings:n})})},fr=(e,t)=>{const a={};for(const r of Object.keys(e)){const n=Number(r);!Number.isInteger(n)||n<0||n===t||(a[String(n>t?n-1:n)]=e[r])}return a},xr=()=>{dt(e=>null===e?e:Object.assign({},e,{mappings:e.mappings.concat([{identityKey:"",model:"",catalogProviderId:"",catalogModelId:"",inputTokenSemantics:"fresh",multiplier:"1"}])}))},yr=(e,t,a)=>{dt(r=>{if(null===r||!Array.isArray(r.overrides)||!r.overrides[e])return r;const n=r.overrides.slice();return n[e]=Object.assign({},n[e],{[t]:a}),Object.assign({},r,{overrides:n})})},vr=(e,t,a,r)=>{dt(n=>{if(null===n||!Array.isArray(n.overrides)||!n.overrides[e])return n;const i=n.overrides.slice(),s=Object.assign({},i[e]),l=Array.isArray(s.tiers)?s.tiers.map(e=>Object.assign({},e)):[];return l[t]?(l[t]=Object.assign({},l[t],{[a]:r}),i[e]=Object.assign({},s,{tiered:l.length>0,tiers:l}),Object.assign({},n,{overrides:i})):n}),wt("")},wr=()=>{dt(e=>null===e?e:Object.assign({},e,{overrides:e.overrides.concat([{providerId:"",modelId:"",displayName:"",input:"",output:"",cacheRead:"",cacheWrite:"",tiered:!1,tiers:[]}])})),wt("")},kr=Aa.totals.input+Aa.totals.output+Aa.totals.cacheRead+Aa.totals.cacheWrite+Aa.totals.reasoning,Er=x(Aa.totals.input,Aa.totals.cacheRead),Nr=(S(Aa.totals),A(Aa.totals,a)),Mr=function(e,t){const a=S(e);if(a.pricedCalls>0&&0===a.unpricedCalls&&0===a.ambiguousCalls&&0===a.unsupportedCalls)return"en"===t?a.pricedCalls+" priced":a.pricedCalls+" 次已计价";const r=a.unpricedCalls+a.ambiguousCalls+a.unsupportedCalls;return r>0?"en"===t?r+" unpriced":r+" 次未计价":"en"===t?"No pricing":"暂无价格"}(Aa.totals,a);let Cr="—",Sr=n("查询中…","Checking…");if(null!=J)if("missing-key"===J.status)Cr=n("未配置","Not configured"),Sr=n("在 设置 → 模型 中填写 DeepSeek API Key 后可见","Available after you enter a DeepSeek API key in Settings → Models");else if("unavailable"===J.status)Cr=n("不可用","Unavailable"),Sr=J.message||n("DeepSeek 接口返回余额不可用","The DeepSeek API reported that balance information is unavailable");else if("error"===J.status){Cr=n("查询失败","Lookup failed");const e=J.detail?"en"===a?" ("+String(J.detail).slice(0,90)+")":"("+String(J.detail).slice(0,90)+")":"";Sr=(J.message||"")+e+n(" 点“刷新”重试"," Click Refresh to try again")}else if("ok"===J.status&&Array.isArray(J.currencies)&&J.currencies.length>0){const e=J.currencies,t=e.find(e=>"CNY"===e.currency)||e[0],r=e.filter(e=>e!==t);Cr=k(t.currency,t.total,a);let i=null!==t.total?n("赠送 ","Granted ")+k(t.currency,t.granted,a)+" · "+n("充值 ","Top-up ")+k(t.currency,t.toppedUp,a):"";r.length>0&&(i+=(i?" | ":"")+r.map(e=>k(e.currency,e.total,a)).join(" ")),Sr=i}else Cr=n("无数据","No data"),Sr="";const zr=(e,t,a,n,i)=>r.createElement("div",{className:"uh-card",style:{animationDelay:70*n+"ms"}},r.createElement("div",{className:"uh-card-label"},i?r.createElement(y,{name:i,size:14}):null,e),r.createElement("div",{className:"uh-card-value"},t),r.createElement("div",{className:"uh-card-sub"},a)),Ar=r.createElement("div",{className:"uh-ios-metric uh-ios-metric-rate",style:{animationDelay:"280ms"}},r.createElement("div",{className:"uh-ios-metric-rate-head"},r.createElement("div",{className:"uh-ios-metric-label"},r.createElement(y,{name:"cache",size:18}),n("缓存命中率","Cache Hit Rate")),r.createElement("div",{className:"uh-ios-metric-rate-value"},Er.toFixed(1)+"%")),r.createElement("div",{className:"uh-ios-metric-bar"},r.createElement("div",{className:"uh-ios-metric-fill",style:{width:Math.max(0,Math.min(100,Er))+"%"}})),r.createElement("div",{className:"uh-ios-metric-rate-detail"},"en"===a?"Context reused "+b(Aa.totals.cacheRead)+" tokens":"复用上下文 "+b(Aa.totals.cacheRead)+" Token")),Tr=Fa.length>0?Ua(Fa[0]):0,Rr=Fa.slice(0,3).map(e=>{const t=Ua(e),n=Ja.get(e.workspaceId),i=P(void 0===n?0:n),s=xe===e.workspaceId;return r.createElement("div",{key:e.workspaceId,className:"uh-wsbar"+(s?" uh-sel":""),onClick:()=>ha(e.workspaceId)},r.createElement("div",{className:"uh-wsbar-top"},r.createElement("span",{className:"uh-dot",style:{background:i}}),r.createElement("span",{className:"uh-wsbar-title"},Xa(e.workspaceId)),r.createElement("span",{className:"uh-wsbar-num"},w(b(t),t,a))),r.createElement("div",{className:"uh-barwrap uh-bar-thin"},r.createElement("div",{className:"uh-barfill",style:{width:Tr>0?Math.max(2,t/Tr*100)+"%":"0%",background:i}})))}),jr=r.createElement("div",{className:"uh-card",style:{animationDelay:"210ms"}},r.createElement("div",{className:"uh-card-label"},r.createElement(y,{name:"folder",size:14}),n("各工作区总处理量","Total Tokens Processed by Workspace")),0===Fa.length?r.createElement("div",{className:"uh-empty",style:{padding:"8px 0"}},n("暂无数据","No data yet")):r.createElement("div",{className:"uh-wsbars"},Rr,Fa.length>3?r.createElement("div",{className:"uh-card-sub"},"en"===a?"See the details table for the other "+(Fa.length-3)+" workspaces":"其余 "+(Fa.length-3)+" 个工作区见明细表"):null)),Dr=Fa.map(e=>{const t=Ya.get(e.workspaceId),i="string"==typeof La[e.workspaceId]?La[e.workspaceId]:"",s=t?t.title:n("未知工作区","Unknown workspace"),l=t?t.path:"",o=""!==i?i:s,c=""!==i?s+" · "+l:l,u=Ua(e),d=x(e.input,e.cacheRead),p=Ja.get(e.workspaceId),m=P(void 0===p?0:p),h=xe===e.workspaceId;return r.createElement("div",{key:e.workspaceId,className:"uh-row"+(h?" uh-sel":""),onClick:()=>ha(e.workspaceId)},r.createElement("div",{className:"uh-row-title-wrap"},r.createElement("div",{className:"uh-ws-title"},o),r.createElement("div",{className:"uh-ws-path"},c)),r.createElement("div",{className:"uh-num"},b(e.turns)),r.createElement("div",{className:"uh-num"},b(e.input)),r.createElement("div",{className:"uh-num"},b(e.cacheRead)),r.createElement("div",{className:"uh-num"},b(e.output)),r.createElement("div",{className:"uh-num"},b(e.reasoning)),r.createElement("div",{},r.createElement("div",{className:"uh-num"},b(u)),r.createElement("div",{className:"uh-barwrap"},r.createElement("div",{className:"uh-barfill",style:{width:Tr>0?Math.max(2,u/Tr*100)+"%":"0%",background:m}}))),r.createElement("div",{className:"uh-num uh-cost-num"},A(e,a)),r.createElement("div",{className:"uh-num"},d.toFixed(1)+"%"),r.createElement("div",{className:"uh-num"},Tr>0?(u/Tr*100).toFixed(0)+"%":"0%"))}),Ir="route"===me?n("混合查看","Combined View"):"model"===me?n("按模型合并","Grouped by Model"):n("按供应商汇总","Grouped by Provider"),Ur="route"===me?n("供应商 / 模型","Provider / Model"):"model"===me?n("模型","Model"):n("供应商","Provider"),Fr="model"!==Fe||0===Oa.length?null:r.createElement(ne,{key:"model-donut-"+Fe+":"+ia+":"+(sa||"query")+":"+me,title:"provider"===me?n("供应商用量","Provider Usage"):n("模型用量","Model Usage"),icon:"chart",language:a,items:nr}),Or="workspace"!==Fe||0===Fa.length?null:r.createElement(ne,{key:"workspace-donut-"+Fe+":"+ia+":"+(sa||"query"),title:n("工作区用量","Workspace Usage"),icon:"folder",language:a,items:ir}),Wr="model"===Fe?Oa.map(e=>{const t=Ua(e),n=x(e.input,e.cacheRead);return r.createElement("div",{key:e.identityKey||e.model,className:"uh-model-row uh-row"},r.createElement("div",{className:"uh-row-title-wrap"},r.createElement("div",{className:"uh-ws-title",title:e.model},e.model)),r.createElement("div",{className:"uh-num"},b(e.calls)),r.createElement("div",{className:"uh-num"},b(e.input)),r.createElement("div",{className:"uh-num"},b(e.cacheRead)),r.createElement("div",{className:"uh-num"},b(e.output)),r.createElement("div",{className:"uh-num"},b(e.reasoning)),r.createElement("div",{className:"uh-num"},b(t)),r.createElement("div",{className:"uh-num uh-cost-num"},A(e,a)),r.createElement("div",{className:"uh-num"},n.toFixed(1)+"%"))}):[],Lr=at?r.createElement("div",{className:"uh-panel uh-anim-panel"},r.createElement("div",{className:"uh-alias-panel-head"},r.createElement("span",{},n("工作区别名","Workspace Aliases")),r.createElement("button",{className:"uh-alias-close",onClick:()=>rt(!1)},n("关闭","Close"))),0===Wa.length?r.createElement("div",{className:"uh-empty",style:{padding:"10px 0"}},n("暂无工作区","No workspaces yet")):r.createElement("div",{className:"uh-alias-list"},Wa.map((e,t)=>r.createElement("div",{key:e.id,className:"uh-alias-item"},r.createElement("span",{className:"uh-dot",style:{background:P(t)}}),r.createElement("span",{className:"uh-alias-folder",title:e.path},e.title||e.path),r.createElement("input",{className:"uh-alias-input",value:void 0!==nt[e.id]?nt[e.id]:"",placeholder:n("项目别名","Project alias"),onChange:t=>it(a=>Object.assign({},a,{[e.id]:t.target.value})),onKeyDown:t=>{"Enter"===t.key&&dr(e.id,t.target.value)}})))),r.createElement("div",{className:"uh-alias-panel-foot"},r.createElement("span",{className:"uh-note"},n("回车保存单个;清空别名还原文件夹名","Press Enter to save one; clear an alias to restore the folder name")),r.createElement("button",{className:"uh-alias-ok",onClick:()=>{for(const e of Object.keys(nt)){const t="string"==typeof La[e]?La[e]:"";nt[e]!==t&&dr(e,nt[e])}rt(!1)}},n("全部保存","Save All")))):null,qr=_a.sync&&"object"==typeof _a.sync?_a.sync:{},Pr=st?r.createElement(ue,{revision:rr,render:()=>null!==ut?r.createElement("div",{className:"uh-panel uh-pricing-panel uh-anim-panel"},r.createElement("div",{className:"uh-pricing-head"},r.createElement("div",{className:"uh-title-with-icon"},r.createElement(y,{name:"wallet",size:16}),r.createElement("strong",{},n("成本统计设置","Cost Statistics"))),r.createElement("button",{type:"button",className:"uh-refresh uh-icon-button",title:n("关闭成本设置","Close cost settings"),"aria-label":n("关闭成本设置","Close cost settings"),disabled:ht||bt||xt,onClick:pr},r.createElement(y,{name:"close",size:16}))),r.createElement("div",{className:"uh-pricing-note"},n("价格单位为 USD / 1M Token。输入上下文严格超过档位阈值时,整次请求的输入、输出和缓存均使用该档费率;可展开查看 models.dev 档位,也可在价格覆盖中自定义。","Prices are USD per 1M tokens. When input context strictly exceeds a band threshold, that band’s input, output, and cache rates apply to the whole request. Expand models.dev schedules or define custom override bands below.")),r.createElement("div",{className:"uh-pricing-toolbar"},r.createElement("label",{className:"uh-pricing-switch"},r.createElement("input",{type:"checkbox",checked:!0===ut.sync.autoEnabled,disabled:ht||bt||xt,onChange:e=>(e=>{if(null===ut||xt||ht||bt)return;const t=!0===e,a=ut.sync&&!0===ut.sync.autoEnabled;dt(e=>null===e?e:Object.assign({},e,{sync:Object.assign({},e.sync,{autoEnabled:t})})),we({pricingAutoSync:t});const r="string"==typeof M.requestToken?M.requestToken:"";if(""===r)return dt(e=>null===e?e:Object.assign({},e,{sync:Object.assign({},e.sync,{autoEnabled:a})})),we({pricingAutoSync:a}),void wt("token");const n=e=>{dt(e=>null===e?e:Object.assign({},e,{sync:Object.assign({},e.sync,{autoEnabled:a})})),we({pricingAutoSync:a}),wt(e)};yt(!0),wt(""),be({sync:{autoEnabled:t}},!1,r).then(e=>{if(!e||!0!==e.ok||!e.pricing)return void n("save");const t=e.pricing.sync&&!0===e.pricing.sync.autoEnabled;F(t=>null===t?t:Object.assign({},t,{pricing:e.pricing})),ct(e.pricing),dt(a=>null===a?a:Object.assign({},a,{sync:Object.assign({},a.sync,{autoEnabled:t,intervalMs:e.pricing.sync&&e.pricing.sync.intervalMs})})),we({pricingAutoSync:t})},e=>n(e&&403===e.status?"forbidden":"save")).finally(()=>yt(!1))})(e.target.checked)}),r.createElement("span",{},n("启用 6 小时自动同步","Enable 6-hour automatic sync"))),r.createElement("span",{className:"uh-note"},xt?n("保存中…","Saving…"):qr.lastSuccessAt>0?n("上次成功:","Last success: ")+new Date(qr.lastSuccessAt).toLocaleString():n("尚未同步","Not synced yet")),r.createElement("button",{type:"button",className:"uh-refresh",onClick:gr,disabled:bt||ht||xt},r.createElement(y,{name:"refresh",size:14}),bt?n("同步中…","Syncing…"):n("立即同步","Sync now"))),qr.lastError?r.createElement("div",{className:"uh-pricing-error",role:"alert"},n("上次同步失败:","Last sync failed: ")+qr.lastError):null,r.createElement("div",{className:"uh-pricing-section"},r.createElement("div",{className:"uh-pricing-section-head"},r.createElement("strong",{},n("当前用量匹配","Usage matches")),r.createElement("span",{className:"uh-note"},Qa.length+" "+n("个模型","models"))),0===Qa.length?r.createElement("div",{className:"uh-empty",style:{padding:"12px 0"}},n("暂无模型用量","No model usage yet")):r.createElement("div",{className:"uh-pricing-table-wrap"},r.createElement("table",{className:"uh-pricing-model-table"},r.createElement("thead",{},r.createElement("tr",{},r.createElement("th",{scope:"col"},n("当前模型","Usage model")),r.createElement("th",{scope:"col"},n("状态","Status")),r.createElement("th",{scope:"col"},n("官方模型","Official model")),r.createElement("th",{scope:"col"},n("费率档位","Rate bands")),r.createElement("th",{scope:"col",title:n("基础输入价格(USD / 1M)","Base input price (USD / 1M)")},n("输入","Input")),r.createElement("th",{scope:"col",title:n("基础输出价格(USD / 1M)","Base output price (USD / 1M)")},n("输出","Output")),r.createElement("th",{scope:"col",title:n("基础缓存读取价格(USD / 1M)","Base cache read price (USD / 1M)")},n("缓存读","Cache read")),r.createElement("th",{scope:"col",title:n("基础缓存写入价格(USD / 1M)","Base cache write price (USD / 1M)")},n("缓存写","Cache write")))),r.createElement("tbody",{},Qa.map(e=>{const t=Array.isArray(e.tiers)?e.tiers:[],i=!0===e.tiered&&!0!==e.tieredInvalid&&t.length>0&&e.rates,s=!0===e.tieredInvalid?n("档位异常","Invalid tiers"):!0===e.tiered?n("分层 · ","Tiered · ")+t.length:n("固定","Flat"),l=i?[Object.assign({type:"context",size:0},e.rates)].concat(t):[];return r.createElement(r.Fragment,{key:e.identityKey},r.createElement("tr",{},r.createElement("td",{className:"uh-pricing-model-name",title:e.model},e.model||n("未知模型","Unknown model")),r.createElement("td",{title:e.reason||""},r.createElement("span",{className:"uh-pricing-status uh-pricing-status-"+(e.status||"unpriced")},j(e.status||"unpriced",a))),r.createElement("td",{className:"uh-pricing-model-target",title:e.pricingModel||""},e.pricingModel||n("未匹配","No match")),r.createElement("td",{},r.createElement("span",{className:"uh-pricing-tier-badge"+(!0===e.tiered?"":" uh-flat")},s)),r.createElement("td",{className:"uh-pricing-model-rate"},"priced"===e.status&&e.rates?e.rates.input:"—"),r.createElement("td",{className:"uh-pricing-model-rate"},"priced"===e.status&&e.rates?e.rates.output:"—"),r.createElement("td",{className:"uh-pricing-model-rate"},"priced"===e.status&&e.rates?e.rates.cacheRead:"—"),r.createElement("td",{className:"uh-pricing-model-rate"},"priced"===e.status&&e.rates?e.rates.cacheWrite:"—")),i?r.createElement("tr",{className:"uh-pricing-tier-row"},r.createElement("td",{colSpan:8},r.createElement("details",{className:"uh-pricing-tier-details"},r.createElement("summary",{},r.createElement(y,{name:"chevron",size:13,className:"uh-pricing-tier-caret"}),n("查看完整费率表","View full rate table"),r.createElement("span",{className:"uh-pricing-tier-context"},function(e,t){const a={fresh:["Fresh:输入 + 缓存读写","Fresh: input + cache read/write"],total:["Total:输入已含缓存","Total: input already includes cache"],legacy:["Legacy:输入 + 缓存写","Legacy: input + cache write"]},r=a[e]||a.fresh;return"en"===t?r[1]:r[0]}(e.inputTokenSemantics,a)+(e.multiplier&&"1"!==e.multiplier?" · ×"+e.multiplier:""))),r.createElement("table",{className:"uh-pricing-tier-table"},r.createElement("thead",{},r.createElement("tr",{},r.createElement("th",{scope:"col"},n("输入上下文范围","Input context range")),r.createElement("th",{scope:"col"},n("输入","Input")),r.createElement("th",{scope:"col"},n("输出","Output")),r.createElement("th",{scope:"col"},n("缓存读","Cache read")),r.createElement("th",{scope:"col"},n("缓存写","Cache write")))),r.createElement("tbody",{},l.map((e,n)=>r.createElement("tr",{key:n},r.createElement("td",{},function(e,t,a){const r=Array.isArray(e)?e:[];if(t<0)return r.length>0?"≤ "+f(r[0].size,a):"en"===a?"All contexts":"全部上下文";const n=r[t];if(!n)return"";const i="> "+f(n.size,a),s=r[t+1];return s?i+("en"===a?" and ≤ ":" 且 ≤ ")+f(s.size,a):i}(t,n-1,a)),r.createElement("td",{},e.input),r.createElement("td",{},e.output),r.createElement("td",{},e.cacheRead),r.createElement("td",{},e.cacheWrite)))))))):null)}))))),r.createElement("div",{className:"uh-pricing-section"},r.createElement("div",{className:"uh-pricing-section-head"},r.createElement("strong",{},n("模型映射","Model mappings")),r.createElement("button",{type:"button",className:"uh-refresh",onClick:xr},r.createElement(y,{name:"plus",size:13}),n("添加映射","Add mapping"))),0===ut.mappings.length?r.createElement("div",{className:"uh-empty",style:{padding:"12px 0"}},n("选择当前模型后,再指定对应的官方模型。DSH Provider 不参与计价。","Select a used model, then choose its official model. The DSH provider is ignored.")):ut.mappings.map((e,t)=>{const a=String(Ct[t]||"").trim().toLowerCase(),i=U(e.model),s=U(e.catalogModelId),l=er.find(t=>t.value===String(e.identityKey||e.usageIdentityKey||""))||er.find(e=>""!==i&&e.model===i)||er.find(e=>""!==s&&e.officialModel===s),o=er.filter(e=>""===a||e.label.toLowerCase().includes(a));return r.createElement("div",{key:t,className:"uh-pricing-edit-row"},r.createElement("div",{className:"uh-pricing-used-model-picker"},r.createElement("input",{type:"text",className:"uh-pricing-used-model-input",placeholder:n("选择当前用过的模型","Select a used model"),value:void 0!==Ct[t]?Ct[t]:l?l.label:"","aria-label":n("当前用过的模型","Used model"),"aria-haspopup":"listbox","aria-expanded":zt===t,onFocus:()=>{At(t),St(e=>Object.assign({},e,{[t]:""}))},onClick:()=>At(t),onBlur:()=>setTimeout(()=>{At(e=>e===t?null:e),l&&St(e=>Object.assign({},e,{[t]:l.label}))},120),onKeyDown:e=>{"Escape"===e.key&&At(null)},onChange:e=>((e,t)=>{St(a=>Object.assign({},a,{[e]:t})),At(e),dt(t=>{if(null===t||!Array.isArray(t.mappings)||!t.mappings[e])return t;const a=t.mappings.slice();return a[e]=Object.assign({},a[e],{identityKey:"",model:"",catalogModelId:"",catalogProviderId:""}),Object.assign({},t,{mappings:a})})})(t,e.target.value)}),zt===t&&o.length>0?r.createElement("div",{className:"uh-language-options uh-pricing-used-model-options",role:"listbox","aria-label":n("当前用过的模型","Used models")},o.map(e=>r.createElement("button",{key:e.value,type:"button",role:"option",className:"uh-language-option uh-pricing-model-option",onMouseDown:e=>e.preventDefault(),onClick:()=>((e,t)=>{const a=Qa.find(e=>String(e.identityKey||e.model||"")===String(t));if(!a)return;const r=a.actualModel||a.requestedModel||a.pricingModel||"",n="priced"===a.status&&a.pricingModel||"";dt(i=>{if(null===i||!Array.isArray(i.mappings)||!i.mappings[e])return i;const s=i.mappings.slice();return s[e]=Object.assign({},s[e],{identityKey:t,model:r,catalogModelId:n,catalogProviderId:a.providerId||""}),Object.assign({},i,{mappings:s})}),St(t=>Object.assign({},t,{[e]:a.model||r})),At(null),Mt(null)})(t,e.value)},r.createElement(y,{name:"list",size:14}),r.createElement("span",{className:"uh-pricing-model-option-name"},e.label)))):null),r.createElement("div",{className:"uh-pricing-model-search"},r.createElement("input",{type:"text",className:"uh-pricing-model-search-input",placeholder:n("输入官方模型 ID 检索","Type official model ID to search"),value:e.catalogModelId||"","aria-label":n("官方模型 ID","Official model ID"),"aria-autocomplete":"list",onFocus:()=>Mt(t),onBlur:()=>setTimeout(()=>Mt(e=>e===t?null:e),120),onKeyDown:e=>{"Escape"===e.key&&Mt(null)},onChange:e=>((e,t)=>{const a=Ut.current;br(e,"catalogModelId",t),Mt(e);const r=Ft.current[e];void 0!==r&&(clearTimeout(r),delete Ft.current[e]);const n=(It.current[e]||0)+1;if(It.current[e]=n,""===String(t||"").trim())return void Et(t=>Object.assign({},t,{[e]:[]}));const i=setTimeout(()=>{delete Ft.current[e],(e=>{const t=new URLSearchParams({q:String(e||"").slice(0,120),limit:"30"});return fetch("/api/all-usage/pricing/models?"+t.toString(),{headers:{accept:"application/json"}}).then(e=>{if(!e.ok)throw new Error("HTTP "+e.status);return e.json()})})(t).then(t=>{Ut.current===a&&It.current[e]===n&&Et(a=>Object.assign({},a,{[e]:Array.isArray(t&&t.items)?t.items:[]}))},()=>{Ut.current===a&&It.current[e]===n&&Et(t=>Object.assign({},t,{[e]:[]}))})},180);Ft.current[e]=i})(t,e.target.value)}),Nt===t&&Array.isArray(kt[t])&&kt[t].length>0?r.createElement("div",{className:"uh-language-options uh-pricing-model-options",role:"listbox","aria-label":n("官方模型匹配结果","Official model matches")},kt[t].map(e=>r.createElement("button",{key:e.value,type:"button",role:"option",className:"uh-language-option uh-pricing-model-option",onMouseDown:e=>e.preventDefault(),onClick:()=>((e,t)=>{if(!t||"string"!=typeof t.value)return;const a=Ft.current[e];void 0!==a&&(clearTimeout(a),delete Ft.current[e]),It.current[e]=(It.current[e]||0)+1,dt(a=>{if(null===a||!Array.isArray(a.mappings)||!a.mappings[e])return a;const r=a.mappings.slice();return r[e]=Object.assign({},r[e],{catalogModelId:t.value,catalogProviderId:t.providerId||""}),Object.assign({},a,{mappings:r})}),Mt(null)})(t,e)},r.createElement(y,{name:"list",size:14}),r.createElement("span",{className:"uh-pricing-model-option-name"},e.label||e.value),r.createElement("span",{className:"uh-pricing-model-option-id"},e.value+(!0===e.tiered?" · "+n("分层 ","tiered ")+e.tierCount:""))))):null),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("倍率","Multiplier"),title:n("成本倍率","Cost multiplier"),"aria-label":n("成本倍率","Cost multiplier"),value:e.multiplier||"1",onChange:e=>br(t,"multiplier",e.target.value)}),r.createElement("button",{type:"button",className:"uh-refresh uh-icon-button",title:n("删除映射","Remove mapping"),"aria-label":n("删除映射","Remove mapping"),onClick:()=>(e=>{Ut.current+=1;const t=Ft.current;for(const e of Object.keys(t))clearTimeout(t[e]);Ft.current=fr(t,e),It.current=fr(It.current,e),Et(t=>fr(t,e)),St(t=>fr(t,e)),Rt(t=>fr(t,e));const a=t=>null==t?t:t===e?null:Number.isInteger(t)&&t>e?t-1:t;Mt(e=>a(e)),At(e=>a(e)),Dt(e=>a(e)),dt(t=>null===t?t:Object.assign({},t,{mappings:t.mappings.filter((t,a)=>a!==e)}))})(t)},r.createElement(y,{name:"close",size:14})))})),r.createElement("div",{className:"uh-pricing-section"},r.createElement("div",{className:"uh-pricing-section-head"},r.createElement("strong",{},n("显式价格覆盖","Explicit price overrides")),r.createElement("button",{type:"button",className:"uh-refresh",onClick:wr},r.createElement(y,{name:"plus",size:13}),n("添加价格","Add price"))),0===ut.overrides.length?r.createElement("div",{className:"uh-empty",style:{padding:"12px 0"}},n("仅在官方目录未覆盖或有明确官方账单时添加;可配置基础价格和上下文费率档位。","Add an override only when the official catalog lacks the model or you have an authoritative official price. Base rates and context tiers are supported.")):r.createElement(r.Fragment,null,r.createElement("div",{className:"uh-pricing-price-head"},r.createElement("span",{},n("官方模型 ID","Official model ID")),r.createElement("span",{},n("基础输入 / 1M","Base input / 1M")),r.createElement("span",{},n("基础输出 / 1M","Base output / 1M")),r.createElement("span",{},n("基础缓存读 / 1M","Base cache read / 1M")),r.createElement("span",{},n("基础缓存写 / 1M","Base cache write / 1M")),r.createElement("span",{},"")),r.createElement("div",{className:"uh-pricing-overrides"},ut.overrides.map((e,t)=>{const a=String(Tt[t]||"").trim().toLowerCase(),i=er.filter(e=>""===a||e.label.toLowerCase().includes(a)),s=Array.isArray(e.tiers)?e.tiers:[];let l=0;const o=s.map((e,a)=>{const i=I(e,l),s=Number(e&&e.size);return Number.isSafeInteger(s)&&(l=s),r.createElement("div",{key:a,className:"uh-pricing-tier-edit-row"+(i?"":" uh-invalid")},r.createElement("input",{type:"number",min:"1",max:"1000000000",step:"1",placeholder:n("阈值 Token","Token threshold"),title:n("上下文超过此 Token 数时启用本档","Use this band when context exceeds this token count"),"aria-label":n("上下文阈值 Token","Context threshold tokens"),value:void 0===e.size?"":e.size,onChange:e=>vr(t,a,"size",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("输入价 / 1M","Input / 1M"),"aria-label":n("档位输入价格 / 1M","Tier input price / 1M"),value:void 0===e.input?"":e.input,onChange:e=>vr(t,a,"input",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("输出价 / 1M","Output / 1M"),"aria-label":n("档位输出价格 / 1M","Tier output price / 1M"),value:void 0===e.output?"":e.output,onChange:e=>vr(t,a,"output",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("缓存读 / 1M","Cache read / 1M"),"aria-label":n("档位缓存读取价格 / 1M","Tier cache read price / 1M"),value:void 0===e.cacheRead?"":e.cacheRead,onChange:e=>vr(t,a,"cacheRead",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("缓存写 / 1M","Cache write / 1M"),"aria-label":n("档位缓存写入价格 / 1M","Tier cache write price / 1M"),value:void 0===e.cacheWrite?"":e.cacheWrite,onChange:e=>vr(t,a,"cacheWrite",e.target.value)}),r.createElement("button",{type:"button",className:"uh-refresh uh-icon-button",title:n("删除费率档位","Remove rate band"),"aria-label":n("删除费率档位","Remove rate band"),onClick:()=>((e,t)=>{dt(a=>{if(null===a||!Array.isArray(a.overrides)||!a.overrides[e])return a;const r=a.overrides.slice(),n=Object.assign({},r[e]),i=(Array.isArray(n.tiers)?n.tiers:[]).filter((e,a)=>a!==t).map(e=>Object.assign({},e));return r[e]=Object.assign({},n,{tiered:i.length>0,tiers:i}),Object.assign({},a,{overrides:r})}),wt("")})(t,a)},r.createElement(y,{name:"close",size:14})))});return r.createElement("div",{key:t,className:"uh-pricing-override"},r.createElement("div",{className:"uh-pricing-edit-row uh-pricing-price-row"},r.createElement("div",{className:"uh-pricing-used-model-picker"},r.createElement("input",{type:"text",className:"uh-pricing-used-model-input",placeholder:n("选择当前用过的模型","Select a used model"),value:void 0!==Tt[t]?Tt[t]:e.modelId||"","aria-label":n("覆盖模型 ID","Override model ID"),"aria-haspopup":"listbox","aria-expanded":jt===t,onFocus:()=>{Dt(t),Rt(e=>Object.assign({},e,{[t]:""}))},onClick:()=>Dt(t),onBlur:()=>setTimeout(()=>{Dt(e=>e===t?null:e),e.modelId&&Rt(a=>Object.assign({},a,{[t]:e.modelId}))},120),onKeyDown:e=>{"Escape"===e.key&&Dt(null)},onChange:e=>((e,t)=>{Rt(a=>Object.assign({},a,{[e]:t})),Dt(e),yr(e,"modelId",t)})(t,e.target.value)}),jt===t&&i.length>0?r.createElement("div",{className:"uh-language-options uh-pricing-used-model-options",role:"listbox","aria-label":n("当前用过的模型","Used models")},i.map(e=>r.createElement("button",{key:e.value,type:"button",role:"option",className:"uh-language-option uh-pricing-model-option",onMouseDown:e=>e.preventDefault(),onClick:()=>((e,t)=>{const a=Qa.find(e=>String(e.identityKey||e.model||"")===String(t));if(!a)return;const r=a.pricingModel||a.actualModel||a.requestedModel||"";dt(t=>{if(null===t||!Array.isArray(t.overrides)||!t.overrides[e])return t;const n=t.overrides.slice(),i={modelId:r};return"priced"===a.status&&a.rates&&(Object.assign(i,a.rates),i.tiers=Array.isArray(a.tiers)?a.tiers.map(e=>Object.assign({},e)):[],i.tiered=i.tiers.length>0),n[e]=Object.assign({},n[e],i),Object.assign({},t,{overrides:n})}),Rt(t=>Object.assign({},t,{[e]:r})),Dt(null)})(t,e.value)},r.createElement(y,{name:"list",size:14}),r.createElement("span",{className:"uh-pricing-model-option-name"},e.label)))):null),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("输入价 / 1M","Input / 1M"),title:n("基础输入价格,美元 / 100 万 Token","Base input price, USD / 1M tokens"),"aria-label":n("基础输入价格 / 1M","Base input price / 1M"),value:void 0===e.input?"":e.input,onChange:e=>yr(t,"input",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("输出价 / 1M","Output / 1M"),title:n("基础输出价格,美元 / 100 万 Token","Base output price, USD / 1M tokens"),"aria-label":n("基础输出价格 / 1M","Base output price / 1M"),value:void 0===e.output?"":e.output,onChange:e=>yr(t,"output",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("缓存读 / 1M","Cache read / 1M"),title:n("基础缓存读取价格,美元 / 100 万 Token","Base cache read price, USD / 1M tokens"),"aria-label":n("基础缓存读取价格 / 1M","Base cache read price / 1M"),value:void 0===e.cacheRead?"":e.cacheRead,onChange:e=>yr(t,"cacheRead",e.target.value)}),r.createElement("input",{type:"number",min:"0",step:"any",placeholder:n("缓存写 / 1M","Cache write / 1M"),title:n("基础缓存写入价格,美元 / 100 万 Token","Base cache write price, USD / 1M tokens"),"aria-label":n("基础缓存写入价格 / 1M","Base cache write price / 1M"),value:void 0===e.cacheWrite?"":e.cacheWrite,onChange:e=>yr(t,"cacheWrite",e.target.value)}),r.createElement("button",{type:"button",className:"uh-refresh uh-icon-button",title:n("删除价格覆盖","Remove price override"),"aria-label":n("删除价格覆盖","Remove price override"),onClick:()=>(e=>{dt(t=>null===t?t:Object.assign({},t,{overrides:t.overrides.filter((t,a)=>a!==e)}))})(t)},r.createElement(y,{name:"close",size:14}))),r.createElement("div",{className:"uh-pricing-tier-editor"},r.createElement("div",{className:"uh-pricing-tier-editor-head"},r.createElement("div",{className:"uh-pricing-tier-editor-title"},r.createElement("strong",{},n("上下文费率档位","Context rate bands")),r.createElement("span",{},n("超过阈值后,整次请求使用该档四项费率","Above a threshold, all four rates apply to the whole request"))),r.createElement("button",{type:"button",className:"uh-refresh",disabled:s.length>=32,title:s.length>=32?n("每个模型最多 32 个档位","Maximum 32 bands per model"):n("添加上下文费率档位","Add context rate band"),onClick:()=>(e=>{dt(t=>{if(null===t||!Array.isArray(t.overrides)||!t.overrides[e])return t;const a=t.overrides.slice(),r=Object.assign({},a[e]),n=Array.isArray(r.tiers)?r.tiers.map(e=>Object.assign({},e)):[];if(n.length>=32)return t;const i=n.length>0?n[n.length-1]:null,s=i&&Number.isFinite(Number(i.size))?Number(i.size):1e5,l=i||r;return n.push({type:"context",size:Math.min(1e9,s+1e5),input:void 0===l.input?"":l.input,output:void 0===l.output?"":l.output,cacheRead:void 0===l.cacheRead?"":l.cacheRead,cacheWrite:void 0===l.cacheWrite?"":l.cacheWrite}),a[e]=Object.assign({},r,{tiered:!0,tiers:n}),Object.assign({},t,{overrides:a})}),wt("")})(t)},r.createElement(y,{name:"plus",size:13}),n("添加档位","Add band"))),0===s.length?r.createElement("div",{className:"uh-pricing-tier-empty"},n("未配置档位,所有上下文使用基础费率。","No bands configured; base rates apply to every context.")):r.createElement(r.Fragment,null,r.createElement("div",{className:"uh-pricing-tier-edit-head"},r.createElement("span",{},n("超过 Token","Above tokens")),r.createElement("span",{},n("输入 / 1M","Input / 1M")),r.createElement("span",{},n("输出 / 1M","Output / 1M")),r.createElement("span",{},n("缓存读 / 1M","Cache read / 1M")),r.createElement("span",{},n("缓存写 / 1M","Cache write / 1M")),r.createElement("span",{},"")),o)))})))),""!==vt?r.createElement("div",{className:"uh-pricing-error",role:"alert"},"forbidden"===vt?n("没有权限保存成本设置","Not allowed to save cost settings"):"token"===vt?n("当前进程令牌不可用,请刷新看板","The process capability is unavailable; refresh the dashboard"):"sync"===vt?n("models.dev 同步失败,已保留上次成功目录和未保存编辑","models.dev sync failed; the last good catalog and unsaved edits were kept"):"mapping"===vt?n("模型映射无效:请选择当前模型、官方模型并填写有效倍率","Invalid model mapping: select a used model, an official model, and a valid multiplier"):"tier"===vt?n("费率档位无效:最多 32 档;阈值必须为递增的正整数,四项费率必须完整且非负","Invalid rate bands: maximum 32; thresholds must be increasing positive integers and all four rates must be complete and non-negative"):"override"===vt?n("价格覆盖无效:请选择模型并填写完整的非负基础费率","Invalid price override: select a model and enter all non-negative base rates"):n("成本设置保存失败,请检查输入","Cost settings could not be saved; check the inputs")):null,r.createElement("div",{className:"uh-pricing-foot"},r.createElement("span",{className:"uh-note"},n("保存不会重算已有正成本;回填只处理未计价调用。","Saving does not recalculate existing positive costs; backfill only handles unpriced calls.")),r.createElement("div",{className:"uh-actions"},r.createElement("button",{type:"button",className:"uh-refresh",disabled:ht||bt||xt,onClick:pr},n("取消","Cancel")),r.createElement("button",{type:"button",className:"uh-refresh",disabled:ht||bt||xt,onClick:()=>hr(!1)},ht?n("保存中…","Saving…"):n("保存","Save")),r.createElement("button",{type:"button",className:"uh-refresh uh-pricing-backfill",disabled:ht||bt||xt,onClick:()=>hr(!0)},n("保存并回填","Save and backfill"))))):r.createElement("div",{className:"uh-panel uh-pricing-panel uh-anim-panel"},r.createElement("div",{className:"uh-pricing-head"},r.createElement("div",{className:"uh-title-with-icon"},r.createElement(y,{name:"wallet",size:16}),r.createElement("strong",{},n("成本统计设置","Cost Statistics"))),r.createElement("button",{type:"button",className:"uh-refresh uh-icon-button",title:n("关闭成本设置","Close cost settings"),"aria-label":n("关闭成本设置","Close cost settings"),disabled:ht||bt||xt,onClick:pr},r.createElement(y,{name:"close",size:16}))),pt?r.createElement("div",{className:"uh-empty",role:"status",style:{display:"flex",alignItems:"center",justifyContent:"center",gap:10}},r.createElement("span",{className:"uh-trend-spinner","aria-hidden":!0}),r.createElement("span",{},n("正在加载完整费率设置…","Loading full pricing settings…"))):r.createElement("div",{className:"uh-empty",role:"alert"},r.createElement("div",{},n("完整费率设置加载失败","Full pricing settings could not be loaded")),r.createElement("button",{type:"button",className:"uh-refresh",style:{marginTop:10},onClick:mr},r.createElement(y,{name:"refresh",size:14}),n("重试","Retry"))))}):null,Kr="custom"===$&&null!==va?"en"===a?va.start+" to "+va.end+" (UTC)":va.start+" 至 "+va.end:"today"===$?n("今日","Today"):"30d"===$?n("近 30 天","Last 30 Days"):"90d"===$?n("近 90 天","Last 90 Days"):n("全部","All Time"),Br=function(e,t,a){if("custom"!==e)return e;const r=o(t,a);return null===r?"custom":"custom-"+r.start+"-to-"+r.end}($,va,p),Hr="invalid"===wa?n("请选择有效的开始日期和结束日期","Choose valid start and end dates"):"order"===wa?n("结束日期不能早于开始日期","End date must be on or after the start date"):"bounds"===wa?n("可选范围为 "+ya+" 至 "+N,"Choose a date from "+ya+" to "+N):"",Vr=ie?r.createElement("div",{className:"uh-custom-range",role:"group","aria-label":n("自定义时间范围","Custom date range")},r.createElement("div",{className:"uh-custom-range-meta"},r.createElement("div",{className:"uh-custom-range-title"},r.createElement(y,{name:"calendar",size:15}),n("自定义时间范围","Custom date range")),r.createElement("div",{className:"uh-custom-range-note"},n("可查看全部可扫描历史日数据;中文按本地日期,English 按 UTC。热力图始终展示最近 53 周。","All available historical daily data can be selected. Chinese uses local dates; English uses UTC. The heatmap always shows the latest 53 weeks."))),r.createElement("div",{className:"uh-custom-range-fields"},r.createElement("label",{className:"uh-custom-range-field"},r.createElement("span",{},n("开始日期","Start date")),r.createElement("input",{type:"date",value:ae.start,min:ya,max:N,onChange:e=>re(t=>Object.assign({},t,{start:e.target.value}))})),r.createElement("label",{className:"uh-custom-range-field"},r.createElement("span",{},n("结束日期","End date")),r.createElement("input",{type:"date",value:ae.end,min:ya,max:N,onChange:e=>re(t=>Object.assign({},t,{end:e.target.value}))}))),r.createElement("div",{className:"uh-custom-range-actions"},r.createElement("button",{type:"button",className:"uh-custom-range-cancel",onClick:()=>le(!1)},n("取消","Cancel")),r.createElement("button",{type:"button",className:"uh-custom-range-apply",disabled:""!==wa,onClick:()=>{if(""!==wa)return;const e=o(ae,p);null!==e&&(te(e),Q("custom"),le(!1))}},n("应用","Apply"))),""!==Hr?r.createElement("div",{className:"uh-custom-range-error",role:"alert"},Hr):null):null,Yr=r.createElement(se,{key:ar,rows:tr,visible:Ie,language:a,rangeLabel:Kr,loading:Te&&!Ea,error:""===je||"stale"===je||Na?"":n("趋势数据加载失败","Trend data unavailable"),onToggle:sr,onPointClick:da}),Jr=null===la?Kr:la.start===la.end?la.start:la.start+" → "+la.end,Xr=r.createElement(de,{visible:"logs"===Fe,panelRef:Bt,scopeLabel:Jr,scopeUtc:!(!la||!la.utc),scopeAvailable:null!==la,loading:Ge,exporting:_e,error:et,rows:Ke,selectedId:qe,hasMore:Ye,language:a,actionKey:oa+":"+(sa||"")+":"+(He||""),onExport:async()=>{if("logs"!==Fe||null===la||_e)return;const e=sa;Qe(!0),tt("");try{let t=null;const a=[];for(let r=0;r<50;r+=1){const r=await he(la,t,200);if(null===r||"object"!=typeof r||!Array.isArray(r.items)||null===e||h(r)!==e)throw new Error("audit export stale");if(a.push(...r.items),!r.hasMore||!r.nextCursor)break;t=r.nextCursor}const r=e=>'"'+String(e??"").replace(/"/g,'""')+'"',i=e=>e.map(r).join(","),s=[n("时间","Time"),n("日期","Date"),n("Provider","Provider"),n("请求模型","Requested model"),n("实际模型","Actual model"),n("显示模型","Display model"),"turn","step","seq",n("输入","Input"),n("缓存命中","Cache read"),n("缓存写入","Cache write"),n("输出","Output"),n("推理","Reasoning"),n("成本","Cost"),n("计价状态","Cost status"),n("计价模型","Pricing model"),n("来源","Source")],l=[i([n("DSH 用量明细导出","DSH Usage Audit Export")]),i([n("范围","Scope"),la.start+" → "+la.end]),i([n("时区","Timezone"),la.utc?"UTC":n("本地","Local")]),i(s)];for(const e of a)l.push(i([e.time,e.date,e.provider,e.requestedModel,e.actualModel,e.model,e.turn,e.step,e.seq,ce(e,"input"),ce(e,"cacheRead"),ce(e,"cacheWrite"),ce(e,"output"),ce(e,"reasoning"),e.cost&&"priced"===e.cost.status?e.cost.total:"",e.cost&&e.cost.status?e.cost.status:"unpriced",e.cost&&e.cost.pricingModel?e.cost.pricingModel:"",e.materialization||"unknown"]));const o=new Blob(["\ufeff"+l.join("\r\n")],{type:"text/csv;charset=utf-8"}),c=URL.createObjectURL(o),u=document.createElement("a");u.href=c,u.download="dsh-all-usage-audit-"+la.start+"-to-"+la.end+".csv",document.body.appendChild(u),u.click(),u.remove(),URL.revokeObjectURL(c)}catch(e){tt("audit-export")}finally{Qe(!1)}},onLoadMore:()=>{if("logs"!==Fe||null===la||null===He||Ge)return;const e=ea.next(),t=sa;$e(!0),he(la,He,20).then(a=>{if(ea.isCurrent(e)){if(null===a||"object"!=typeof a||!Array.isArray(a.items)||null===t||h(a)!==t)return tt("stale"),$e(!1),void Ze(e=>e+1);Be(e=>e.concat(a.items)),Ve(a.nextCursor||null),Je(!0===a.hasMore),$e(!1),tt("")}},t=>{if(ea.isCurrent(e)){if(t&&409===t.status)return $e(!1),Ve(null),Je(!1),tt("stale"),void Ze(e=>e+1);$e(!1),tt("audit")}})},onSelect:Pe}),Zr=!or.done,Gr=or.total>0?Math.min(100,Math.round(or.scanned/or.total*100)):40,$r=or.done&&0===ur.length&&0===Aa.totals.turns&&0===Aa.totals.calls,_r="number"==typeof cr.lastCompletedAt&&cr.lastCompletedAt>0?new Date(cr.lastCompletedAt).toLocaleString("en"===a?"en-US":"zh-CN"):"",Qr=V>0?new Date(V).toLocaleString("en"===a?"en-US":"zh-CN"):"",en=""===_r?void 0:"en"===a?"Historical scan completed "+_r:"历史扫描完成于 "+_r,tn="en"===a?(""!==Qr?"Updated "+Qr:Zr?"Refreshing data":"Update state pending")+" · "+(cr.sessionsSkippedByRevision||0)+" revision reused · "+(cr.sessionsRead||0)+" read"+((cr.sessionsRestoredFromLedger||0)>0?" · "+cr.sessionsRestoredFromLedger+" ledger restored":"")+((cr.sessionsFailed||0)>0?" · "+cr.sessionsFailed+" failed":"")+" · "+(!0===cr.persistenceSnapshotsAvailable?"revision optimization on":"full-read fallback"):(""!==Qr?"已更新 "+Qr:Zr?"正在更新数据":"数据更新准备中")+" · revision 复用 "+(cr.sessionsSkippedByRevision||0)+" · 实际读取 "+(cr.sessionsRead||0)+((cr.sessionsRestoredFromLedger||0)>0?" · 账本恢复 "+cr.sessionsRestoredFromLedger:"")+((cr.sessionsFailed||0)>0?" · 失败 "+cr.sessionsFailed:"")+" · "+(!0===cr.persistenceSnapshotsAvailable?"免读优化已启用":"全量读取回退"),an=""===L?"":"en"===a?"Usage data may be stale"+(""!==Qr?"; last full update "+Qr:""):"用量数据可能已过期"+(""!==Qr?";上次完整更新 "+Qr:"");return r.createElement("div",{className:"uh-page"},r.createElement("div",{className:"uh-head"},r.createElement("div",{className:"uh-title-wrap"},r.createElement("h2",{className:"uh-title"},n("用量统计","Usage Statistics"))),r.createElement("div",{className:"uh-actions"},r.createElement("button",{className:"uh-refresh",title:n("管理工作区别名","Manage workspace aliases"),onClick:()=>{at?rt(!1):(()=>{const e={};Wa.forEach(t=>{e[t.id]="string"==typeof La[t.id]?La[t.id]:""}),it(e),rt(!0)})()}},r.createElement(y,{name:"edit",size:14}),n("工作区别名","Workspace Aliases")),r.createElement("button",{className:"uh-refresh",title:n("配置模型价格与同步","Configure model prices and sync"),disabled:ht||bt||xt,onClick:()=>{st?pr():mr()}},r.createElement(y,{name:"wallet",size:14}),n("成本设置","Cost Settings")),r.createElement("div",{className:"uh-language-menu"+(qt?" uh-open":""),ref:Kt,onKeyDown:e=>{"Escape"===e.key&&(e.preventDefault(),Pt(!1))}},r.createElement("button",{type:"button",className:"uh-language-trigger"+(qt?" uh-open":""),title:n("切换界面语言","Change interface language"),"aria-label":n("界面语言","Interface language"),"aria-haspopup":"menu","aria-expanded":qt,onClick:()=>Pt(e=>!e)},r.createElement(y,{name:"language",size:14}),r.createElement("span",{className:"uh-language-label"},"en"===a?"English":"中文"),r.createElement(y,{name:"chevron",size:13,className:"uh-language-caret"})),qt?r.createElement("div",{className:"uh-language-options",role:"menu","aria-label":n("界面语言","Interface language")},[["zh","中文"],["en","English"]].map(t=>r.createElement("button",{key:t[0],type:"button",role:"menuitemradio","aria-checked":a===t[0],className:"uh-language-option"+(a===t[0]?" uh-on":""),onClick:()=>((t=>{"function"==typeof e.onLanguageChange&&e.onLanguageChange("en"===t?"en":"zh")})(t[0]),void Pt(!1))},r.createElement(y,{name:"language",size:14}),r.createElement("span",{},t[1]),a===t[0]?r.createElement(y,{name:"check",size:14,className:"uh-language-option-check"}):null))):null),r.createElement("div",{className:"uh-range"},["today","30d","90d","all","custom"].map(e=>r.createElement("button",{key:e,type:"button",className:$===e?"uh-on":"",title:"custom"===e&&"custom"===$?Kr:void 0,onClick:()=>{"custom"===e?(()=>{const e=o(ee,p),t=i(s(E,-89,p),p);re(e||{start:t<ya?ya:t,end:N}),le(!0)})():(Q(e),le(!1))}},"today"===e?n("今日","Today"):"30d"===e?n("近 30 天","Last 30 Days"):"90d"===e?n("近 90 天","Last 90 Days"):"all"===e?n("全部","All Time"):n("自定义","Custom")))),r.createElement("button",{className:"uh-refresh",title:n("导出当前时间范围与模型查看模式的 CSV 数据","Export CSV data for the current time range and model view"),onClick:()=>{const e=e=>'"'+String(e??"").replace(/"/g,'""')+'"',t=t=>t.map(e).join(","),r=e=>e.input+e.output+e.cacheRead+e.cacheWrite+e.reasoning,s=[n("输入 Token","Input Tokens"),n("缓存命中 Token","Cache-Hit Tokens"),n("缓存写入 Token","Cache-Write Tokens"),n("输出 Token","Output Tokens"),n("推理 Token","Reasoning Tokens"),n("总处理 Token","Total Tokens Processed"),n("成本","Cost"),n("缓存命中率","Cache Hit Rate")],l=[t([n("DSH 用量统计导出","DSH Usage Statistics Export")]),t([n("导出时间","Exported At"),p?(new Date).toLocaleString("en-US",{timeZone:"UTC",timeZoneName:"short"}):(new Date).toLocaleString("zh-CN")]),t([n("时间范围","Time Range"),Kr]),t([n("时区","Timezone"),p?"UTC":n("本地","Local")]),t([n("工作区筛选","Workspace Filter"),xe||n("全部","All")]),t([n("供应商筛选","Provider Filter"),ke||n("全部","All")]),t([n("模型筛选","Model Filter"),Ne||n("全部","All")]),t([n("统计 revision","Stats Revision"),M.revision||""]),t([n("模型查看模式","Model View Mode"),Ir]),"",t([n("汇总","Summary")]),t([n("回合","Turns"),n("会话","Sessions"),...s]),t([Aa.totals.turns,Aa.totals.sessions,Aa.totals.input,Aa.totals.cacheRead,Aa.totals.cacheWrite,Aa.totals.output,Aa.totals.reasoning,r(Aa.totals),A(Aa.totals,a),x(Aa.totals.input,Aa.totals.cacheRead).toFixed(2)+"%"]),"",t([n("模型用量明细","Model Usage Details")]),t([Ur,n("调用","Calls"),...s]),...Oa.map(e=>t([e.model,e.calls,e.input,e.cacheRead,e.cacheWrite,e.output,e.reasoning,r(e),A(e,a),x(e.input,e.cacheRead).toFixed(2)+"%"])),"",t([n("工作区明细","Workspace Details")]),t([n("工作区","Workspace"),n("路径","Path"),n("回合","Turns"),...s]),...Fa.map(e=>{const n=Ya.get(e.workspaceId);return t([Xa(e.workspaceId),n?n.path:"",e.turns,e.input,e.cacheRead,e.cacheWrite,e.output,e.reasoning,r(e),A(e,a),x(e.input,e.cacheRead).toFixed(2)+"%"])})],o=new Blob(["\ufeff"+l.join("\r\n")],{type:"text/csv;charset=utf-8"}),c=URL.createObjectURL(o),u=document.createElement("a");u.href=c,u.download="dsh-all-usage-"+Br+"-"+me+"-"+i(new Date,p)+".csv",document.body.appendChild(u),u.click(),u.remove(),URL.revokeObjectURL(c)}},r.createElement(y,{name:"export",size:14}),n("导出数据","Export Data")),r.createElement("button",{className:"uh-refresh uh-icon-button",title:n("刷新统计数据","Refresh usage statistics"),"aria-label":n("刷新统计数据","Refresh usage statistics"),onClick:ma},r.createElement(y,{name:"refresh",size:16})))),r.createElement("div",{className:"uh-filter-bar",role:"group","aria-label":n("统一筛选","Unified filters")},r.createElement(pe,{label:n("全部工作区","All workspaces"),ariaLabel:n("工作区筛选","Workspace filter"),className:"uh-filter-workspace",icon:"folder",value:xe||"",options:[{value:"",label:n("全部工作区","All workspaces")}].concat(Ba.map(e=>({value:e.id,label:Xa(e.id)}))),onChange:e=>ye(e||null)}),r.createElement(pe,{label:n("全部供应商","All providers"),ariaLabel:n("供应商筛选","Provider filter"),className:"uh-filter-provider",icon:"chart",value:ke||"",options:[{value:"",label:n("全部供应商","All providers")}].concat(Pa.map(e=>({value:e,label:e}))),onChange:e=>ba(e)}),r.createElement(pe,{label:n("全部模型","All models"),ariaLabel:n("模型筛选","Model filter"),className:"uh-filter-model",icon:"cache",value:Ne||"",options:[{value:"",label:n("全部模型","All models")}].concat(Ka.map(e=>({value:e,label:e}))),onChange:e=>fa(e)}),null!==xe||null!==ke||null!==Ne?r.createElement("button",{type:"button",className:"uh-filter-clear",onClick:ga},n("清除筛选","Clear filters")):null,Te?r.createElement("span",{className:"uh-query-note"},n("正在更新筛选结果…","Updating filtered data…")):null,""!==je&&"stale"!==je?r.createElement("span",{className:"uh-query-note",role:"alert"},n("筛选结果加载失败","Filtered data unavailable")):null),at?Lr:null,Pr,Vr,Zr?r.createElement("div",{className:"uh-progress"},r.createElement("span",{},"en"===a?"Scanning historical sessions: "+or.scanned+" / "+or.total+(or.failed>0?" ("+or.failed+" failed to read)":""):"正在统计历史会话 "+or.scanned+" / "+or.total+(or.failed>0?"("+or.failed+" 个读取失败)":"")),r.createElement("div",{className:"uh-bar"},r.createElement("div",{className:"uh-fill",style:{width:Gr+"%"}}))):null,r.createElement("div",{className:"uh-sync-health"+(""!==an?" uh-stale":""),title:""!==an?void 0:en},r.createElement(y,{name:""!==an?"refresh":"clock",size:14}),r.createElement("span",{},""!==an?an:tn),""!==an?r.createElement("button",{className:"uh-sync-retry",onClick:ma},n("重试","Retry")):null),$r?r.createElement("div",{className:"uh-panel"},r.createElement("div",{className:"uh-empty"},n("还没有使用记录。开始对话后,这里会点亮。","No usage recorded yet. This area will light up after you start a conversation."))):r.createElement(r.Fragment,null,r.createElement(r.Fragment,null,r.createElement("div",{className:"uh-ios-summary"},r.createElement("div",{className:"uh-ios-summary-hero"},r.createElement("div",{className:"uh-ios-summary-total"},r.createElement("div",{className:"uh-ios-summary-total-icon"},r.createElement(y,{name:"chart",size:24})),r.createElement("div",{className:"uh-ios-summary-total-copy"},r.createElement("div",{className:"uh-ios-summary-label"},n("总处理 Token","Total Tokens Processed")),r.createElement("div",{className:"uh-ios-summary-value"},w(b(Ta),kr,a)),r.createElement("div",{className:"uh-ios-summary-caption"},"en"===a?Kr+" · "+b(Da)+(Ra?" calls":" uses")+" · includes cache reads/writes and reasoning":Kr+" · "+b(Da)+(Ra?" 次调用":" 次使用")+" · 含缓存读写与推理"))),r.createElement("div",{className:"uh-ios-summary-meta"},r.createElement("div",{className:"uh-ios-summary-meta-stat"},r.createElement("div",{className:"uh-ios-summary-meta-label"},r.createElement(y,{name:"chart",size:16}),n("总请求数","Total Requests")),r.createElement("div",{className:"uh-ios-summary-meta-value"},f(Ia,a))),r.createElement("div",{className:"uh-ios-summary-meta-stat uh-ios-summary-meta-cost"},r.createElement("div",{className:"uh-ios-summary-meta-label"},r.createElement(y,{name:"wallet",size:16}),n("估算成本","Estimated Cost")),r.createElement("div",{className:"uh-ios-summary-meta-value"},Nr),r.createElement("div",{className:"uh-ios-summary-meta-caption"},Mr)))),r.createElement("div",{className:"uh-ios-metrics"},zr(n("DeepSeek 账户余额","DeepSeek Account Balance"),Cr,Sr,0,"wallet"),zr(Ra?n("匹配调用次数","Matching Calls"):n("总使用次数","Total Uses"),b(Da),"all"!==$||Ra?"en"===a?(Ra?"Calls in ":"Turns in ")+Kr:Kr+(Ra?"内的调用数":"内的回合数"):"en"===a?Aa.totals.sessions+" sessions":Aa.totals.sessions+" 个会话",1,"chart"),zr(n("连续使用","Current Streak"),"en"===a?Ga.streak+" days":Ga.streak+" 天","en"===a?"Longest streak: "+Ga.best+" days":"最长连续 "+Ga.best+" 天",2,"clock"),jr,Ar)),r.createElement("div",{className:"uh-token-semantics"},r.createElement(y,{name:"cache",size:16}),n("总处理 Token = 输入 + 输出 + 缓存读写 + 推理。缓存命中代表复用上下文,不等于新生成 Token 或实际费用。","Total tokens processed = input + output + cache reads/writes + reasoning. Cache hits represent reused context; they are not newly generated tokens or actual cost.")),Yr,r.createElement(oe,{rows:Ca,workspaces:Wa,aliases:La,workspaceId:xe,queryUsable:Na,todayKey:N,utc:p,language:a,onWorkspaceSelect:ha,onDateClick:da}),r.createElement("div",{className:"uh-detail-tabs",role:"tablist","aria-label":n("用量明细视图","Usage detail views")},[["logs",n("请求日志","Request Logs"),"list"],["model",n("模型统计","Model Stats"),"chart"],["workspace",n("工作区统计","Workspace Stats"),"folder"]].map(e=>r.createElement("button",{key:e[0],type:"button",role:"tab","aria-selected":Fe===e[0],className:"uh-detail-tab"+(Fe===e[0]?" uh-on":""),onClick:()=>Oe(e[0])},r.createElement(y,{name:e[2],size:14}),e[1]))),Xr),"model"===Fe?r.createElement("div",{className:"uh-panel uh-ios-list-panel"},r.createElement("div",{className:"uh-hm-head"},r.createElement("h3",{className:"uh-tbl-title uh-title-with-icon",style:{margin:0}},r.createElement(y,{name:"chart",size:16}),"en"===a?"Model Usage Details ("+Kr+")":"模型用量明细("+Kr+")"),r.createElement("div",{className:"uh-range"},[["route",n("混合查看","Combined View")],["model",n("按模型","By Model")],["provider",n("按供应商","By Provider")]].map(e=>r.createElement("button",{key:e[0],className:me===e[0]?"uh-on":"",onClick:()=>fe(e[0])},e[1])))),0===Oa.length?r.createElement("div",{className:"uh-empty"},n("尚无带模型路由信息的用量记录","No usage records with model-routing information yet")):r.createElement(r.Fragment,null,Fr,r.createElement("div",{className:"uh-tbl-scroll"},r.createElement("div",{className:"uh-model-hrow uh-hrow"},r.createElement("div",{},Ur),r.createElement("div",{className:"uh-num"},n("调用","Calls")),r.createElement("div",{className:"uh-num"},n("输入","Input")),r.createElement("div",{className:"uh-num"},n("缓存命中","Cache Hits")),r.createElement("div",{className:"uh-num"},n("输出","Output")),r.createElement("div",{className:"uh-num"},n("推理","Reasoning")),r.createElement("div",{className:"uh-num"},n("总处理","Total Processed")),r.createElement("div",{className:"uh-num"},n("成本","Cost")),r.createElement("div",{className:"uh-num"},n("命中率","Hit Rate"))),Wr)),r.createElement("div",{className:"uh-note",style:{marginTop:10}},"en"===a?Ir+": Combined View distinguishes “Provider / Model”; By Model merges identically named models across providers; By Provider aggregates all of a provider’s models. Historical records without routing information are grouped as “Unknown.”":Ir+":混合查看按“供应商 / 模型”区分;按模型会跨供应商合并同名模型;按供应商则汇总其全部模型。缺少路由信息的历史记录会归为“未知”。")):null,"workspace"===Fe?r.createElement("div",{className:"uh-panel uh-ios-list-panel"},r.createElement("h3",{className:"uh-tbl-title uh-title-with-icon"},r.createElement(y,{name:"folder",size:16}),"en"===a?"Workspace Details ("+Kr+")":"工作区明细("+Kr+")"),0===Fa.length?r.createElement("div",{className:"uh-empty"},n("该时间范围内没有使用记录","No usage records in this time range")):r.createElement(r.Fragment,null,Or,r.createElement("div",{className:"uh-tbl-scroll"},r.createElement("div",{className:"uh-hrow"},r.createElement("div",{},n("工作区","Workspace")),r.createElement("div",{className:"uh-num"},n("回合","Turns")),r.createElement("div",{className:"uh-num"},n("输入","Input")),r.createElement("div",{className:"uh-num"},n("缓存命中","Cache Hits")),r.createElement("div",{className:"uh-num"},n("输出","Output")),r.createElement("div",{className:"uh-num"},n("推理","Reasoning")),r.createElement("div",{className:"uh-num"},n("总处理","Total Processed")),r.createElement("div",{className:"uh-num"},n("成本","Cost")),r.createElement("div",{className:"uh-num"},n("命中率","Hit Rate")),r.createElement("div",{className:"uh-num"},n("占比","Share"))),Dr))):null))}class Ee extends r.Component{constructor(e){super(e),this.state={error:null,resetKey:e.resetKey}}static getDerivedStateFromError(e){return{error:e}}componentDidUpdate(e){e.resetKey!==this.props.resetKey&&null!==this.state.error&&this.setState({error:null,resetKey:this.props.resetKey})}render(){return null!==this.state.error?this.props.fallback(this.state.error):this.props.children}}function Ne(e){const[t,a]=r.useState(!1),[n,i]=r.useState(0),[s,l]=r.useState(xe),o=(e,t)=>"en"===s?t:e;return r.useEffect(()=>{if(!t)return;const e=e=>{"Escape"===e.key&&a(!1)};return document.addEventListener("keydown",e),()=>document.removeEventListener("keydown",e)},[t]),r.createElement(r.Fragment,null,r.createElement("button",{type:"button",className:"uh-side-entry",title:o("用量统计","Usage Statistics"),"aria-label":o("用量统计","Usage Statistics"),onClick:()=>a(!0)},r.createElement("span",{className:"uh-side-entry-icon"},r.createElement(y,{name:"chart",size:17})),e.wide?r.createElement("span",{className:"uh-side-entry-label"},o("用量统计","Usage Statistics")):null),t?r.createElement("div",{className:"uh-side-modal",role:"presentation",onMouseDown:e=>{e.target===e.currentTarget&&a(!1)}},r.createElement("div",{className:"uh-side-dialog",role:"dialog","aria-modal":!0,"aria-label":o("用量统计","Usage Statistics")},r.createElement("div",{className:"uh-side-dialog-head"},r.createElement("button",{className:"uh-refresh uh-close-button",type:"button",title:o("关闭用量统计","Close Usage Statistics"),"aria-label":o("关闭用量统计","Close Usage Statistics"),onClick:()=>a(!1)},r.createElement(y,{name:"close",size:18}))),r.createElement(Ee,{resetKey:n,fallback:()=>r.createElement("div",{className:"uh-boundary-fallback",role:"alert"},r.createElement("div",{className:"uh-boundary-title"},o("用量统计暂时无法显示","Usage statistics is temporarily unavailable")),r.createElement("div",{className:"uh-boundary-note"},o("当前范围加载失败,入口仍然可用。","The selected range failed to render; the sidebar entry is still available.")),r.createElement("div",{className:"uh-actions"},r.createElement("button",{type:"button",className:"uh-refresh",onClick:()=>i(e=>e+1)},r.createElement(y,{name:"refresh",size:14}),o("重试","Retry")),r.createElement("button",{type:"button",className:"uh-refresh",onClick:()=>a(!1)},r.createElement(y,{name:"close",size:14}),o("关闭","Close"))))},r.createElement(ke,{timerCtx:e.timerCtx,language:s,onLanguageChange:e=>{const t="en"===e?"en":"zh";l(t),function(e){try{window.localStorage.setItem(fe,e)}catch(e){}}(t)}})))):null)}return a.inject=["timer","slots"],a.apply=e=>{const t=e.get("slots"),a=e.get("timer");void 0!==t&&void 0!==a&&t.inject("sidebar.footer.action",()=>t.register({name:"sidebar.footer.action",id:"all-usage",order:10},e=>r.createElement(Ne,{wide:e.wide,timerCtx:a})))},t}});
|