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/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}});