dsh-key-usage 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +187 -2
- package/client/client.js +2280 -0
- package/client/client.js.map +1 -0
- package/cordis.patch.yml +4 -0
- package/docs/images/usage-daily.png +0 -0
- package/docs/images/usage-overview.png +0 -0
- package/docs/images/usage-quota.png +0 -0
- package/lib/collect.js +237 -0
- package/lib/index.js +42 -0
- package/lib/ledger.js +163 -0
- package/lib/live-quota.js +225 -0
- package/lib/merge.js +156 -0
- package/lib/routes.js +351 -0
- package/lib/session-usage.js +235 -0
- package/lib/sessions.js +289 -0
- package/package.json +78 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useCallback","useRef","styles","useMemo","styles","shared","styles","overviewStyles","styles","detailStyles","styles","styles","hitRateOf","styles","Fragment","detailStyles","styles","detailStyles","useState","useMemo","styles","listStyles","quotaStyles","styles","typed","useState","useMemo","styles","listStyles","Fragment","useState","useCallback","useMemo","styles","styles","h"],"sources":["../src/client/UsageIcon.tsx","../src/client/api.ts","../src/client/format.ts","../src/client/tooltip.tsx","../src/client/channels.ts","../src/client/components/Heatmap.tsx","../src/client/tabs/OverviewTab.tsx","../src/client/components/ShareBar.tsx","../src/client/components/StatCard.tsx","../src/client/components/TokenBars.tsx","../src/client/components/DailyDetailTable.tsx","../src/client/tabs/DetailTab.tsx","../src/client/tabs/ProjectsTab.tsx","../src/client/tabs/QuotaTab.tsx","../src/client/tabs/SessionsTab.tsx","../src/client/KeyUsageSection.tsx","../src/client/UsagePage.tsx","../src/client/index.tsx"],"sourcesContent":["/**\n * 侧边栏导航图标(`sidebar.panellist`)。\n * 该 slot 由宿主自持整行(标签、hover/active 配色),插件只提供字形,\n * 因此用 currentColor 让宿主的行样式直接染上图标。\n */\nimport type { ReactNode } from 'react'\n\nexport function UsageIcon({ size = 18 }: { size?: number }): ReactNode {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n style={{ display: 'block' }}\n >\n <rect x=\"2.6\" y=\"8.8\" width=\"2.6\" height=\"4.2\" rx=\"0.7\" fill=\"currentColor\" />\n <rect x=\"6.7\" y=\"5.6\" width=\"2.6\" height=\"7.4\" rx=\"0.7\" fill=\"currentColor\" />\n <rect x=\"10.8\" y=\"2.8\" width=\"2.6\" height=\"10.2\" rx=\"0.7\" fill=\"currentColor\" />\n </svg>\n )\n}\n","/** 同源只读端点的取数封装:任何失败都返回 null,由调用方决定空态。 */\nimport type {\n Channel,\n HeatmapDay,\n ProjectsResponse,\n QuotaProvider,\n SessionsResponse,\n SummaryResponse,\n UsageResponse,\n} from './types.ts'\n\nasync function fetchJson<T>(path: string): Promise<T | null> {\n try {\n const response = await fetch(path, { headers: { Accept: 'application/json' } })\n if (!response.ok) return null\n return await response.json() as T\n } catch {\n return null\n }\n}\n\nexport async function loadChannels(): Promise<Channel[] | null> {\n const result = await fetchJson<{ channels: Channel[] }>('/dsh-key-usage/channels')\n return result === null ? null : result.channels\n}\n\nexport async function loadUsage(from: string, to: string, provider: string): Promise<UsageResponse | null> {\n const params = new URLSearchParams({ from, to })\n if (provider !== 'ALL') params.set('provider', provider)\n return fetchJson<UsageResponse>('/dsh-key-usage/usage?' + params.toString())\n}\n\nexport async function loadHeatmap(): Promise<HeatmapDay[] | null> {\n const result = await fetchJson<{ days: HeatmapDay[] }>('/dsh-key-usage/heatmap')\n return result === null ? null : result.days\n}\n\n/**\n * 余额与订阅窗口:服务端实时优先、失败回落本机快照。\n * @param fresh - 用户手动刷新时传 true,强制重新查一次官方接口\n */\nexport async function loadQuota(fresh = false): Promise<QuotaProvider[] | null> {\n const query = fresh ? '?fresh=1' : ''\n const result = await fetchJson<{ providers: QuotaProvider[] }>('/dsh-key-usage/quota' + query)\n return result === null ? null : result.providers\n}\n\nexport function loadSummary(): Promise<SummaryResponse | null> {\n return fetchJson<SummaryResponse>('/dsh-key-usage/summary')\n}\n\nexport function loadSessions(limit = 300): Promise<SessionsResponse | null> {\n return fetchJson<SessionsResponse>('/dsh-key-usage/sessions?limit=' + String(limit))\n}\n\nexport function loadProjects(limit = 200): Promise<ProjectsResponse | null> {\n return fetchJson<ProjectsResponse>('/dsh-key-usage/projects?limit=' + String(limit))\n}\n","/** 展示层格式化:全部与统计口径无关,只负责把数字变成界面文本。 */\n\n/** 本地日历 `YYYY-MM-DD`。 */\nexport function isoDate(date: Date): string {\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${date.getFullYear()}-${month}-${day}`\n}\n\nexport function shiftedIsoDate(offsetDays: number): string {\n return isoDate(new Date(Date.now() + offsetDays * 86400000))\n}\n\nexport function fmtTokens(count: number): string {\n if (count >= 1e9) return (count / 1e9).toFixed(2) + 'B'\n if (count >= 1e6) return (count / 1e6).toFixed(1) + 'M'\n if (count >= 1e3) return (count / 1e3).toFixed(0) + 'K'\n return String(count)\n}\n\nexport function fmtCost(amount: number): string {\n return '¥' + amount.toFixed(2)\n}\n\nexport function fmtCalls(count: number): string {\n return count.toLocaleString('en-US').replace(/,/g, ' ')\n}\n\n/** `MM-DD`(图表坐标轴与范围文案用)。 */\nexport function fmtShortDate(dateKey: string): string {\n return dateKey.slice(5)\n}\n\n/** 毫秒时间戳 → `YYYY-MM-DD HH:mm`(本地时区)。 */\nexport function fmtDateTime(timeMs: number | null): string {\n if (timeMs === null) return '—'\n const date = new Date(timeMs)\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n return `${isoDate(date)} ${hours}:${minutes}`\n}\n\n/** 毫秒时间戳 → `HH:mm`。 */\nexport function fmtTime(timeMs: number | null): string {\n if (timeMs === null) return '—'\n const date = new Date(timeMs)\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`\n}\n\n/** 时长(毫秒)→ `2h 15m` / `43m` / `—`。 */\nexport function fmtDuration(milliseconds: number | null): string {\n if (milliseconds === null || milliseconds <= 0) return '—'\n const minutes = Math.max(1, Math.round(milliseconds / 60000))\n if (minutes < 60) return `${minutes}m`\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`\n}\n\n/** 最近活跃:刚刚 / N 分钟前 / N 小时前 / N 天前。 */\nexport function fmtRelative(timeMs: number | null, nowMs: number): string {\n if (timeMs === null) return '—'\n const minutes = Math.round((nowMs - timeMs) / 60000)\n if (minutes < 1) return '刚刚'\n if (minutes < 60) return `${minutes} 分钟前`\n if (minutes < 60 * 24) return `${Math.round(minutes / 60)} 小时前`\n return `${Math.round(minutes / (60 * 24))} 天前`\n}\n\n/** 命中率:一位小数百分数,无提示侧 token 时返回 —。 */\nexport function fmtHitRate(cacheReadTokens: number, promptTokens: number): string {\n if (promptTokens <= 0) return '—'\n return (cacheReadTokens / promptTokens * 100).toFixed(1) + '%'\n}\n","/** 共享浮层:跟随鼠标的 fixed 层,内容用 React 节点渲染(不再拼接 innerHTML)。 */\nimport { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from 'react'\nimport styles from './styles/shared.module.css'\n\nexport interface TooltipContent {\n x: number\n y: number\n node: ReactNode\n}\n\nexport interface TooltipApi {\n tooltip: TooltipContent | null\n show: (event: React.MouseEvent, node: ReactNode) => void\n hide: () => void\n}\n\nexport function useTooltip(): TooltipApi {\n const [tooltip, setTooltip] = useState<TooltipContent | null>(null)\n const show = useCallback((event: React.MouseEvent, node: ReactNode) => {\n setTooltip({ x: event.clientX, y: event.clientY, node })\n }, [])\n const hide = useCallback(() => setTooltip(null), [])\n return { tooltip, show, hide }\n}\n\nexport function TooltipView({ tooltip }: { tooltip: TooltipContent | null }): ReactNode {\n const elementRef = useRef<HTMLDivElement | null>(null)\n useLayoutEffect(() => {\n const element = elementRef.current\n if (element === null || tooltip === null) return\n const maxX = window.innerWidth - element.offsetWidth - 12\n const maxY = window.innerHeight - element.offsetHeight - 12\n element.style.left = Math.max(8, Math.min(tooltip.x + 14, maxX)) + 'px'\n element.style.top = Math.max(8, Math.min(tooltip.y + 14, maxY)) + 'px'\n }, [tooltip])\n if (tooltip === null) return null\n return <div ref={elementRef} className={styles.tooltip}>{tooltip.node}</div>\n}\n\nexport function TipTitle({ children }: { children: ReactNode }): ReactNode {\n return <div className={styles.tipTitle}>{children}</div>\n}\n\nexport function TipSub({ children }: { children: ReactNode }): ReactNode {\n return <div className={styles.tipSub}>{children}</div>\n}\n\nexport function TipRow(props: { color?: string; label: ReactNode; amount?: ReactNode; rate?: ReactNode }): ReactNode {\n return (\n <div className={styles.tipRow}>\n {props.color !== undefined && <i style={{ background: props.color }} />}\n <span className={styles.tipName}>{props.label}</span>\n {props.amount !== undefined && <span className={styles.tipAmount}>{props.amount}</span>}\n {props.rate !== undefined && <span className={styles.tipRate}>{props.rate}</span>}\n </div>\n )\n}\n\nexport function TipBar({ percent, color }: { percent: number; color: string }): ReactNode {\n return <div className={styles.tipBar}><i style={{ width: Math.max(2, percent) + '%', background: color }} /></div>\n}\n\nexport function TipDivider({ children }: { children: ReactNode }): ReactNode {\n return <div className={styles.tipDivider}>{children}</div>\n}\n","/** 通道展示工具:颜色、显示名、状态与计费模式文案。 */\nimport type { Channel, ChannelCostMode, ChannelStatus } from './types.ts'\n\nconst CHANNEL_COLORS = ['#5b8cff', '#9a7bff', '#3fb96f', '#e0a13c', '#e05a5a', '#38bdc8']\n\nexport const STATUS_LABELS: Record<ChannelStatus, string> = {\n ok: '正常',\n warn: '偏高',\n danger: '告警',\n off: '未配置',\n}\n\nexport function channelIndexOf(channels: Channel[], provider: string): number {\n return channels.findIndex((channel) => channel.provider === provider)\n}\n\nexport function channelColorOf(channels: Channel[], provider: string): string {\n const index = channelIndexOf(channels, provider)\n return CHANNEL_COLORS[(index >= 0 ? index : 0) % CHANNEL_COLORS.length]\n}\n\nexport function displayNameOf(channels: Channel[], provider: string): string {\n return channels.find((channel) => channel.provider === provider)?.displayName ?? provider\n}\n\nexport function channelOf(channels: Channel[], provider: string | null): Channel | null {\n if (provider === null) return null\n return channels.find((channel) => channel.provider === provider) ?? null\n}\n\n/** 成本单元格:按计费模式给出文案,无法判定时回落金额或 —。 */\nexport function costCellText(channel: Channel | null, cost: number): { text: string; dim: boolean } {\n if (channel === null) return cost > 0 ? { text: '¥' + cost.toFixed(2), dim: false } : { text: '—', dim: true }\n if (channel.costMode === 'sub') return { text: '订阅内', dim: true }\n if (channel.costMode === 'free') return { text: '¥0.00', dim: true }\n if (channel.costMode === 'none') return { text: '无计费', dim: true }\n return cost > 0 ? { text: '¥' + cost.toFixed(2), dim: false } : { text: '—', dim: true }\n}\n\n/** 状态点的颜色(走宿主告警/成功令牌)。 */\nexport function statusColorOf(status: ChannelStatus): string {\n if (status === 'ok') return 'var(--dsw-alias-state-success-primary, #3fb96f)'\n if (status === 'warn') return 'var(--dsw-alias-state-warning-primary, #e0a13c)'\n if (status === 'danger') return 'var(--dsw-alias-state-danger-primary, #e05a5a)'\n return 'var(--dsw-alias-label-tertiary, #67676f)'\n}\n\n/** 窗口占用进度条颜色:≥90 危险、≥60 警告,其余正常。 */\nexport function quotaBarColor(percent: number): string {\n if (percent >= 90) return 'var(--dsw-alias-state-danger-primary, #e05a5a)'\n if (percent >= 60) return 'var(--dsw-alias-state-warning-primary, #e0a13c)'\n return 'var(--dsw-alias-state-success-primary, #3fb96f)'\n}\n\nexport const COST_MODE_LABELS: Record<ChannelCostMode, string> = {\n sub: '订阅制',\n perToken: '按量计费',\n free: '免费额度',\n none: '本地端点',\n unknown: '未查询',\n}\n","/**\n * 活跃热力图:最近约 9 个月的滚动窗口(含未来格占位)。\n * 强度分档取活跃日的三分位(p33 / p67),阈值随可见区间自动变化,\n * 具体数值并入页面底部的口径说明,不再单独占一行。\n * 格子用固定方形尺寸 + 横向滚动,保证「有数据 / 无数据 / 无用量」三类格子完全等大。\n */\nimport { useMemo, type ReactNode } from 'react'\nimport { channelColorOf, displayNameOf } from '../channels.ts'\nimport { fmtTokens, isoDate } from '../format.ts'\nimport type { Channel, HeatmapDay } from '../types.ts'\nimport { TipBar, TipRow, TipSub, TipTitle, type TooltipApi } from '../tooltip.tsx'\nimport styles from '../styles/overview.module.css'\nimport shared from '../styles/shared.module.css'\n\n/** 9 个月 ≈ 39 周;配合 18px 方形格子,窄窗口下横向滚动。 */\nconst WEEK_COUNT = 39\nconst DAY_MS = 86400000\n\ninterface HeatCell {\n date: string\n state: 'future' | 'empty' | 'idle' | 'active'\n level: number\n}\n\nfunction levelThresholds(days: HeatmapDay[]): [number, number] {\n const active = days.map((day) => day.totalTokens).filter((tokens) => tokens > 0).sort((first, second) => first - second)\n if (active.length < 3) return [5e7, 2e8]\n const pick = (fraction: number): number => active[Math.min(active.length - 1, Math.floor(active.length * fraction))]\n return [pick(0.33), pick(0.67)]\n}\n\nfunction levelOf(tokens: number, thresholds: [number, number]): number {\n if (tokens <= 0) return 0\n if (tokens < thresholds[0]) return 1\n if (tokens < thresholds[1]) return 2\n return 3\n}\n\nconst LEVEL_COLORS = ['rgba(128,128,140,.22)', 'rgba(91,140,255,.25)', 'rgba(91,140,255,.55)', '#5b8cff']\n\nexport function Heatmap(props: { days: HeatmapDay[]; channels: Channel[]; tooltip: TooltipApi }): ReactNode {\n const { days, channels, tooltip } = props\n const heatmapByDate = useMemo(() => new Map(days.map((day) => [day.date, day])), [days])\n const thresholds = useMemo(() => levelThresholds(days), [days])\n\n const { columns, monthLabels } = useMemo(() => {\n const today = new Date()\n today.setHours(0, 0, 0, 0)\n const endSaturday = new Date(today.getTime() + (6 - today.getDay()) * DAY_MS)\n const start = new Date(endSaturday.getTime() - (WEEK_COUNT * 7 - 1) * DAY_MS)\n const dataStart = days.length > 0 ? days[0].date : null\n const cells: HeatCell[] = []\n for (let offset = 0; offset < WEEK_COUNT * 7; offset += 1) {\n const date = new Date(start.getTime() + offset * DAY_MS)\n const dateKey = isoDate(date)\n if (date.getTime() > today.getTime()) {\n cells.push({ date: dateKey, state: 'future', level: 0 })\n continue\n }\n // 台账覆盖之前的日子用 ghost 占位,与「无用量」的灰区分开\n if (dataStart === null || dateKey < dataStart) {\n cells.push({ date: dateKey, state: 'empty', level: 0 })\n continue\n }\n const day = heatmapByDate.get(dateKey)\n if (day === undefined || day.totalTokens <= 0) {\n cells.push({ date: dateKey, state: 'idle', level: 0 })\n continue\n }\n cells.push({ date: dateKey, state: 'active', level: levelOf(day.totalTokens, thresholds) })\n }\n const weeks: HeatCell[][] = []\n for (let index = 0; index < cells.length; index += 7) weeks.push(cells.slice(index, index + 7))\n const labels: string[] = []\n let previousMonth = -1\n for (const week of weeks) {\n const month = Number(week[0].date.slice(5, 7))\n labels.push(month !== previousMonth ? month + '月' : '')\n previousMonth = month\n }\n return { columns: weeks, monthLabels: labels }\n }, [days, heatmapByDate])\n\n return (\n <div className={styles.heatmapCard}>\n <div className={styles.heatmapHead}>\n <span className={styles.heatmapTitle}>活跃热力图 · 近 9 个月 · 按当日 Tokens 总量 · 悬停查看当日各模型占比</span>\n <span className={shared.legend}>\n <span><i className=\"ghost\" />无数据</span>\n <span><i style={{ background: LEVEL_COLORS[0] }} />无用量</span>\n <span><i style={{ background: LEVEL_COLORS[1] }} />少</span>\n <span><i style={{ background: LEVEL_COLORS[2] }} />中</span>\n <span><i style={{ background: LEVEL_COLORS[3] }} />高</span>\n </span>\n </div>\n <div className={styles.heatmapScroll}>\n <div className={styles.heatmapBody}>\n <div className={styles.heatWeekdays}>\n <span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>\n </div>\n <div className={styles.heatGridCol}>\n <div className={styles.heatGrid}>\n {columns.map((week, weekIndex) => (\n <div className={styles.heatWeek} key={weekIndex}>\n {week.map((cell) => {\n const day = cell.state === 'active' ? heatmapByDate.get(cell.date) : undefined\n const className = cell.state === 'active'\n ? styles.heatCell\n : cell.state === 'idle' ? styles.heatCell + ' ' + styles.idle : styles.heatCell + ' ' + styles.empty\n return (\n <div\n key={cell.date}\n className={className}\n style={cell.state === 'active' ? { background: LEVEL_COLORS[cell.level] } : undefined}\n onMouseMove={(event) => tooltip.show(event, tipOf(cell, day, channels))}\n onMouseLeave={tooltip.hide}\n />\n )\n })}\n </div>\n ))}\n </div>\n <div className={styles.heatMonths}>\n {monthLabels.map((label, index) => <span key={index}>{label}</span>)}\n </div>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nfunction tipOf(cell: HeatCell, day: HeatmapDay | undefined, channels: Channel[]): ReactNode {\n if (cell.state !== 'active' || day === undefined) {\n return (\n <>\n <TipTitle>{cell.date}</TipTitle>\n <TipSub>{cell.state === 'idle' ? '当日无用量记录' : '台账尚未覆盖'}</TipSub>\n </>\n )\n }\n return (\n <>\n <TipTitle>{cell.date}</TipTitle>\n <TipSub>合计 <b>{fmtTokens(day.totalTokens)}</b> tokens</TipSub>\n {day.models.map((model) => {\n const percent = day.totalTokens > 0 ? Math.round(model.totalTokens / day.totalTokens * 100) : 0\n const color = channelColorOf(channels, model.provider)\n return (\n <div key={model.provider + '|' + model.model}>\n <TipRow\n color={color}\n label={<>{model.model}<span className={shared.dim}> · {displayNameOf(channels, model.provider)}</span></>}\n amount={fmtTokens(model.totalTokens)}\n rate={percent + '%'}\n />\n <TipBar percent={percent} color={color} />\n </div>\n )\n })}\n </>\n )\n}\n","/** 总览:Key 清单表(原型列结构)+ 年度热力图。 */\nimport type { ReactNode } from 'react'\nimport { costCellText, statusColorOf } from '../channels.ts'\nimport { fmtCalls, fmtTokens } from '../format.ts'\nimport type { Channel, HeatmapDay, QuotaProvider, SummaryResponse } from '../types.ts'\nimport type { TooltipApi } from '../tooltip.tsx'\nimport { Heatmap } from '../components/Heatmap.tsx'\nimport styles from '../styles/shared.module.css'\nimport overviewStyles from '../styles/overview.module.css'\n\nexport function OverviewTab(props: {\n channels: Channel[]\n summary: SummaryResponse | null\n quotas: QuotaProvider[]\n heatmap: HeatmapDay[]\n tooltip: TooltipApi\n onOpenDetail: (provider: string) => void\n}): ReactNode {\n const { channels, summary } = props\n return (\n <div>\n <div className={styles.groupLabel}>Key 清单</div>\n <table className={overviewStyles.keyTable}>\n <thead>\n <tr>\n <th>通道</th><th>类型</th><th>今日</th><th>近 30 天</th><th>余额 / 窗口</th><th>30 天成本</th><th></th>\n </tr>\n </thead>\n <tbody>\n {channels.map((channel) => {\n const row = summary?.channels.find((item) => item.provider === channel.provider) ?? null\n const todayTokens = row === null ? 0 : row.today.tokens\n const monthTokens = row === null ? 0 : row.month.tokens\n const monthCost = row === null ? 0 : row.month.cost\n const costCell = costCellText(channel, monthCost)\n const disabled = channel.status === 'off'\n return (\n <tr\n key={channel.provider}\n className={disabled ? overviewStyles.disabledRow : overviewStyles.clickable}\n onClick={disabled ? undefined : () => props.onOpenDetail(channel.provider)}\n >\n <td>\n <div className={overviewStyles.cellKey}>\n <span className={styles.statusDot} style={{ background: statusColorOf(channel.status) }} />\n <div className={overviewStyles.keyDisplay}>{channel.displayName}</div>\n </div>\n </td>\n <td><span className={tagClassOf(channel)}>{channel.status === 'off' ? '未配置' : channel.costLabel}</span></td>\n <td>{todayTokens > 0 ? fmtTokens(todayTokens) : <span className={styles.dim}>—</span>}</td>\n <td>{monthTokens > 0 ? fmtTokens(monthTokens) : <span className={styles.dim}>—</span>}</td>\n <td>{quotaCellOf(channel)}</td>\n <td>{costCell.dim ? <span className={styles.dim}>{costCell.text}</span> : costCell.text}</td>\n <td>{disabled ? null : <span className={overviewStyles.rowLink}>明细 →</span>}</td>\n </tr>\n )\n })}\n {channels.length === 0 && <tr><td colSpan={7} className={styles.empty}>未读取到通道配置</td></tr>}\n </tbody>\n </table>\n\n <Heatmap days={props.heatmap} channels={channels} tooltip={props.tooltip} />\n\n {summary === null && (\n <div className={styles.empty}>台账读取中或不可用,今日 / 近 30 天列为空</div>\n )}\n {props.quotas.length === 0 && channels.length > 0 && (\n <div className={styles.empty}>未读取到余额快照,余额 / 窗口列显示为 —</div>\n )}\n </div>\n )\n}\n\nfunction tagClassOf(channel: Channel): string {\n const base = overviewStyles.tag\n if (channel.status === 'off') return base + ' ' + overviewStyles.off\n if (channel.costMode === 'free') return base + ' ' + overviewStyles.free\n if (channel.costMode === 'sub') return base + ' ' + overviewStyles.sub\n return base\n}\n\nfunction quotaCellOf(channel: Channel): ReactNode {\n if (channel.costMode === 'sub' && channel.quotaPercent !== null) {\n const percent = channel.quotaPercent\n const levelClass = percent >= 90 ? overviewStyles.danger : percent >= 60 ? overviewStyles.warn : ''\n const windowLabel = channel.quotaWindowLabel ?? channel.quotaWindowKey ?? ''\n return (\n <span className={overviewStyles.quotaCell}>\n <span className={overviewStyles.miniBar}>\n <i style={{ width: Math.max(2, percent) + '%', background: percent >= 60 ? 'var(--dsw-alias-state-warning-primary, #e0a13c)' : 'var(--dsw-alias-state-success-primary, #3fb96f)' }} />\n </span>\n <span className={[overviewStyles.quotaText, levelClass].join(' ').trim()}>{windowLabel} {percent}%</span>\n </span>\n )\n }\n if (channel.balanceText !== null) {\n const levelClass = channel.status === 'danger' ? overviewStyles.danger : channel.status === 'warn' ? overviewStyles.warn : ''\n return (\n <span className={overviewStyles.quotaCell}>\n <span className={[overviewStyles.quotaText, levelClass].join(' ').trim()}>{channel.balanceText}</span>\n </span>\n )\n }\n const fallback = channel.costMode === 'none' ? '无计费' : channel.costMode === 'free' ? '—' : '未查询'\n return <span className={[overviewStyles.quotaText, overviewStyles.dim].join(' ')}>{fallback}</span>\n}\n\n/** 供标题行使用的调用汇总(占位保持导出面稳定)。 */\nexport function overviewCaptionOf(summary: SummaryResponse | null): string {\n if (summary === null) return '台账不可用'\n const calls = summary.channels.reduce((total, row) => total + row.month.calls, 0)\n return fmtCalls(calls) + ' 次调用'\n}\n","/**\n * 占比条:全部通道→通道占比;单通道→模型占比。\n * 只有一条堆叠横条,名字与百分比走悬停 tip(不再列表展示)。\n */\nimport type { ReactNode } from 'react'\nimport { channelColorOf, displayNameOf } from '../channels.ts'\nimport { fmtTokens } from '../format.ts'\nimport type { Channel, UsageResponse } from '../types.ts'\nimport { TipRow, TipSub, TipTitle, type TooltipApi } from '../tooltip.tsx'\nimport styles from '../styles/shared.module.css'\nimport detailStyles from '../styles/detail.module.css'\n\ninterface ShareRow {\n key: string\n label: string\n tokens: number\n color: string\n}\n\nexport function ShareBar(props: {\n usage: UsageResponse | null\n totalTokens: number\n channelFilter: string\n channels: Channel[]\n tooltip: TooltipApi\n}): ReactNode {\n const { usage, totalTokens, channels } = props\n if (usage === null || totalTokens <= 0) {\n return (\n <div className={styles.card}>\n <div className={styles.statLabel}>占比 · 按 Tokens</div>\n <div className={styles.empty} style={{ padding: '8px 0' }}>所选范围内无用量记录</div>\n </div>\n )\n }\n let rows: ShareRow[] = []\n let title = '通道占比 · 按 Tokens'\n if (props.channelFilter === 'ALL') {\n const byProvider = new Map<string, number>()\n for (const day of usage.days) {\n for (const model of day.models) {\n byProvider.set(model.provider, (byProvider.get(model.provider) ?? 0) + model.inputTokens + model.outputTokens + model.cacheReadTokens)\n }\n }\n rows = [...byProvider.entries()].map(([provider, tokens]) => ({\n key: provider, label: displayNameOf(channels, provider), tokens, color: channelColorOf(channels, provider),\n }))\n } else {\n title = '模型占比 · 按 Tokens'\n const byModel = new Map<string, { tokens: number; provider: string }>()\n for (const day of usage.days) {\n for (const model of day.models) {\n const bucket = byModel.get(model.model)\n const tokens = model.inputTokens + model.outputTokens + model.cacheReadTokens\n if (bucket === undefined) byModel.set(model.model, { tokens, provider: model.provider })\n else bucket.tokens += tokens\n }\n }\n rows = [...byModel.entries()].map(([model, value]) => ({\n key: model, label: model, tokens: value.tokens, color: channelColorOf(channels, value.provider),\n }))\n }\n rows.sort((first, second) => second.tokens - first.tokens)\n return (\n <div className={styles.card}>\n <div className={styles.statLabel}>{title}</div>\n <div className={detailStyles.shareBar}>\n {rows.map((row) => {\n const percent = totalTokens > 0 ? Math.round(row.tokens / totalTokens * 100) : 0\n return (\n <i\n key={row.key}\n style={{ width: (row.tokens / totalTokens * 100) + '%', background: row.color }}\n onMouseMove={(event) => props.tooltip.show(event, (\n <>\n <TipTitle>{row.label}</TipTitle>\n <TipSub>占比 <b>{percent}%</b></TipSub>\n <TipRow color={row.color} label=\"Tokens\" amount={fmtTokens(row.tokens)} rate={percent + '%'} />\n </>\n ))}\n onMouseLeave={props.tooltip.hide}\n />\n )\n })}\n </div>\n </div>\n )\n}\n","/** 统计卡:标签 + 主数值 + 副文案(原型 .stat-card)。 */\nimport type { ReactNode } from 'react'\nimport styles from '../styles/shared.module.css'\n\nexport function StatCard(props: { label: string; value: ReactNode; extra?: ReactNode }): ReactNode {\n return (\n <div className={styles.card}>\n <div className={styles.statLabel}>{props.label}</div>\n <div className={styles.statValue}>{props.value}</div>\n {props.extra !== undefined && <div className={styles.statExtra}>{props.extra}</div>}\n </div>\n )\n}\n","/** 堆叠柱状图:按天或按小时,含 x 轴标签(原型 .bars/.bar-labels)。 */\nimport type { ReactNode } from 'react'\nimport type { TooltipApi } from '../tooltip.tsx'\nimport styles from '../styles/detail.module.css'\n\nexport interface TokenBarRow {\n key: string\n axisLabel: string\n cacheReadTokens: number\n inputTokens: number\n outputTokens: number\n tip: ReactNode\n}\n\nexport function TokenBars(props: {\n rows: TokenBarRow[]\n tooltip: TooltipApi\n hidden?: boolean\n}): ReactNode {\n const { rows, tooltip } = props\n if (props.hidden === true || rows.length === 0) return null\n const peak = Math.max(1, ...rows.map((row) => row.cacheReadTokens + row.inputTokens + row.outputTokens))\n const heightOf = (value: number): string => (peak > 0 ? Math.max(1, Math.round(value / peak * 100)) : 1) + '%'\n // 标签抽稀:列数多时每列都写会糊成一片,只保留首列、末列与等距采样\n const labelEvery = Math.max(1, Math.ceil(rows.length / 14))\n return (\n <>\n <div className={rows.length > 20 ? styles.bars + ' ' + styles.barsDense : styles.bars}>\n {rows.map((row) => {\n const rowTotal = row.cacheReadTokens + row.inputTokens + row.outputTokens\n return (\n <div\n key={row.key}\n className={styles.barColumn}\n onMouseMove={(event) => tooltip.show(event, row.tip)}\n onMouseLeave={tooltip.hide}\n >\n {rowTotal > 0 && (\n <>\n <span className={styles.segCache} style={{ height: heightOf(row.cacheReadTokens) }} />\n <span className={styles.segIn} style={{ height: heightOf(row.inputTokens) }} />\n <span className={styles.segOut} style={{ height: heightOf(row.outputTokens) }} />\n </>\n )}\n </div>\n )\n })}\n </div>\n <div className={styles.barLabels}>\n {rows.map((row, index) => (\n <span key={row.key}>{index % labelEvery === 0 || index === rows.length - 1 ? row.axisLabel : ''}</span>\n ))}\n </div>\n </>\n )\n}\n","/**\n * 每日细目:以「天」为折叠单位,展开行用原型子表格展示模型维度\n * (模型 + 调用 / 输入 / 输出 / 缓存读 / 命中率,不占主表列)。\n */\nimport { Fragment, type ReactNode } from 'react'\nimport { displayNameOf } from '../channels.ts'\nimport { fmtCalls, fmtTokens } from '../format.ts'\nimport type { Channel, UsageResponse } from '../types.ts'\nimport styles from '../styles/shared.module.css'\nimport detailStyles from '../styles/detail.module.css'\n\nexport interface DetailSortState {\n key: string\n dir: 'asc' | 'desc'\n}\n\ninterface ModelRow {\n period: string\n model: string\n provider: string\n calls: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cost: number\n totalTokens: number\n}\n\ninterface PeriodRow {\n period: string\n calls: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cost: number\n totalTokens: number\n}\n\nfunction hitRateOf(cacheReadTokens: number, inputTokens: number, outputTokens: number): string {\n const total = inputTokens + outputTokens + cacheReadTokens\n if (total <= 0) return '—'\n return (cacheReadTokens / total * 100).toFixed(1) + '%'\n}\n\nconst COMPARERS: Record<string, (first: PeriodRow, second: PeriodRow) => number> = {\n period: (first, second) => (first.period < second.period ? -1 : first.period > second.period ? 1 : 0),\n total: (first, second) => first.totalTokens - second.totalTokens,\n calls: (first, second) => first.calls - second.calls,\n input: (first, second) => first.inputTokens - second.inputTokens,\n output: (first, second) => first.outputTokens - second.outputTokens,\n cacheRead: (first, second) => first.cacheReadTokens - second.cacheReadTokens,\n hit: (first, second) => {\n const firstTotal = first.inputTokens + first.outputTokens + first.cacheReadTokens\n const secondTotal = second.inputTokens + second.outputTokens + second.cacheReadTokens\n return (firstTotal > 0 ? first.cacheReadTokens / firstTotal : -1) - (secondTotal > 0 ? second.cacheReadTokens / secondTotal : -1)\n },\n cost: (first, second) => first.cost - second.cost,\n}\n\nexport function DailyDetailTable(props: {\n usage: UsageResponse\n channels: Channel[]\n channelFilter: string\n expandedPeriods: Record<string, boolean>\n onTogglePeriod: (periodKey: string) => void\n sort: DetailSortState\n onSort: (sort: DetailSortState) => void\n}): ReactNode {\n const { usage, channels, channelFilter, expandedPeriods, onTogglePeriod, sort, onSort } = props\n const modelRows: ModelRow[] = []\n for (const day of usage.days) {\n for (const model of day.models) {\n const totalTokens = model.inputTokens + model.outputTokens + model.cacheReadTokens\n modelRows.push({\n period: day.date, model: model.model, provider: model.provider, calls: model.calls,\n inputTokens: model.inputTokens, outputTokens: model.outputTokens, cacheReadTokens: model.cacheReadTokens,\n cost: model.cost, totalTokens,\n })\n }\n }\n const aggregates = new Map<string, PeriodRow>()\n for (const row of modelRows) {\n const bucket = aggregates.get(row.period)\n if (bucket === undefined) {\n aggregates.set(row.period, {\n period: row.period, calls: row.calls, inputTokens: row.inputTokens, outputTokens: row.outputTokens,\n cacheReadTokens: row.cacheReadTokens, cost: row.cost, totalTokens: row.totalTokens,\n })\n } else {\n bucket.calls += row.calls\n bucket.inputTokens += row.inputTokens\n bucket.outputTokens += row.outputTokens\n bucket.cacheReadTokens += row.cacheReadTokens\n bucket.cost += row.cost\n bucket.totalTokens += row.totalTokens\n }\n }\n const comparer = COMPARERS[sort.key] ?? COMPARERS.period\n const ordered = [...aggregates.values()].sort((first, second) => {\n const compared = comparer(first, second)\n return sort.dir === 'asc' ? compared : -compared\n })\n\n const header = (key: string, label: string): ReactNode => {\n const isActive = sort.key === key\n return (\n <th\n className={isActive ? styles.sortActive : styles.sortable}\n onClick={() => onSort(isActive ? { key, dir: sort.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'desc' })}\n >\n {label}<span className={styles.sortArrow}>{isActive ? (sort.dir === 'asc' ? '▲' : '▼') : '↕'}</span>\n </th>\n )\n }\n\n const totals = usage.totals\n const totalTokens = totals === null ? 0 : totals.inputTokens + totals.outputTokens + totals.cacheReadTokens\n const periodCount = ordered.length\n\n if (periodCount === 0) {\n return (\n <div>\n <div className={styles.blockLabel}>每日细目</div>\n <div className={styles.empty}>所选范围内无用量记录</div>\n </div>\n )\n }\n\n return (\n <div>\n <div className={styles.blockLabel}>每日细目</div>\n <table className={styles.table}>\n <thead>\n <tr>\n {header('period', '日期')}\n {header('total', '总计')}\n {header('calls', '调用')}\n {header('input', '输入')}\n {header('output', '输出')}\n {header('cacheRead', '缓存读')}\n {header('hit', '命中率')}\n </tr>\n </thead>\n <tbody>\n {ordered.map((period) => {\n const modelsInPeriod = modelRows\n .filter((row) => row.period === period.period)\n .sort((first, second) => second.totalTokens - first.totalTokens)\n const expandable = modelsInPeriod.length > 1\n const isOpen = expandable && expandedPeriods[period.period] === true\n return (\n <Fragment key={period.period}>\n <tr\n className={expandable ? styles.clickable : ''}\n onClick={expandable ? () => onTogglePeriod(period.period) : undefined}\n >\n <td>\n {expandable && <span className={detailStyles.toggleArrow}>{isOpen ? '▼' : '▶'}</span>}\n {period.period}\n </td>\n <td>{fmtTokens(period.totalTokens)}</td>\n <td>{fmtCalls(period.calls)}</td>\n <td>{fmtTokens(period.inputTokens)}</td>\n <td>{fmtTokens(period.outputTokens)}</td>\n <td>{fmtTokens(period.cacheReadTokens)}</td>\n <td>{hitRateOf(period.cacheReadTokens, period.inputTokens, period.outputTokens)}</td>\n </tr>\n {isOpen && (\n <tr>\n <td className={detailStyles.subRowCell} colSpan={7}>\n <div className={detailStyles.subHead}>\n <span />\n <span className={detailStyles.subNumber}>调用</span>\n <span className={detailStyles.subNumber}>输入</span>\n <span className={detailStyles.subNumber}>输出</span>\n <span className={detailStyles.subNumber}>缓存读</span>\n <span className={detailStyles.subNumber}>命中率</span>\n </div>\n {modelsInPeriod.map((row) => (\n <div className={detailStyles.subRow} key={row.period + '|' + row.model + '|' + row.provider}>\n <span className={detailStyles.subName}>\n <span className={detailStyles.subModel}>{row.model}</span>\n {channelFilter === 'ALL' && <span className={detailStyles.subChannel}>{displayNameOf(channels, row.provider)}</span>}\n </span>\n <span className={detailStyles.subNumber}>{fmtCalls(row.calls)}</span>\n <span className={detailStyles.subNumber}>{fmtTokens(row.inputTokens)}</span>\n <span className={detailStyles.subNumber}>{fmtTokens(row.outputTokens)}</span>\n <span className={detailStyles.subNumber}>{fmtTokens(row.cacheReadTokens)}</span>\n <span className={detailStyles.subNumber}>{hitRateOf(row.cacheReadTokens, row.inputTokens, row.outputTokens)}</span>\n </div>\n ))}\n </td>\n </tr>\n )}\n </Fragment>\n )\n })}\n <tr className={styles.total}>\n <td>合计</td>\n <td>{fmtTokens(totalTokens)}</td>\n <td>{fmtCalls(totals?.calls ?? 0)}</td>\n <td>{fmtTokens(totals?.inputTokens ?? 0)}</td>\n <td>{fmtTokens(totals?.outputTokens ?? 0)}</td>\n <td>{fmtTokens(totals?.cacheReadTokens ?? 0)}</td>\n <td>{hitRateOf(totals?.cacheReadTokens ?? 0, totals?.inputTokens ?? 0, totals?.outputTokens ?? 0)}</td>\n </tr>\n </tbody>\n </table>\n </div>\n )\n}\n","/** 使用明细:范围切换 + 三张统计卡 + 占比条 + 通道筛选 + 按天/按小时图表 + 每日细目。 */\nimport type { ReactNode } from 'react'\nimport { channelColorOf, channelOf, costCellText, displayNameOf, statusColorOf } from '../channels.ts'\nimport { fmtCalls, fmtCost, fmtShortDate, fmtTokens, isoDate } from '../format.ts'\nimport type { Channel, ChannelDailySummary, UsageResponse } from '../types.ts'\nimport { TipDivider, TipRow, TipSub, TipTitle, type TooltipApi } from '../tooltip.tsx'\nimport { ShareBar } from '../components/ShareBar.tsx'\nimport { StatCard } from '../components/StatCard.tsx'\nimport { TokenBars, type TokenBarRow } from '../components/TokenBars.tsx'\nimport { DailyDetailTable, type DetailSortState } from '../components/DailyDetailTable.tsx'\nimport styles from '../styles/shared.module.css'\nimport detailStyles from '../styles/detail.module.css'\n\nexport type RangeKey = 'today' | 'week' | 'month' | 'total' | 'custom'\n\nconst RANGE_TABS: Array<[RangeKey, string]> = [\n ['today', '今日'], ['week', '周'], ['month', '月'], ['total', '总计'], ['custom', '自定义'],\n]\n\n/** 图表最多铺开的列数:超过这个跨度的范围按天全画会挤成一团。 */\nconst MAX_AXIS_DAYS = 366\n\nfunction hitRateOf(cacheReadTokens: number, inputTokens: number, outputTokens: number): string {\n const total = inputTokens + outputTokens + cacheReadTokens\n return total > 0 ? (cacheReadTokens / total * 100).toFixed(1) + '%' : '—'\n}\n\nfunction daysBetween(from: string, to: string): number {\n const fromTime = new Date(from + 'T00:00:00').getTime()\n const toTime = new Date(to + 'T00:00:00').getTime()\n return Math.max(1, Math.round((toTime - fromTime) / 86400000) + 1)\n}\n\n/** 范围内每一天的日期键(无数据的日子也要出现在横轴上)。 */\nfunction dateKeysOfRange(from: string, to: string): string[] {\n const start = new Date(from + 'T00:00:00').getTime()\n const total = Math.min(daysBetween(from, to), MAX_AXIS_DAYS)\n const keys: string[] = []\n for (let offset = 0; offset < total; offset += 1) keys.push(isoDate(new Date(start + offset * 86400000)))\n return keys\n}\n\nexport function DetailTab(props: {\n usage: UsageResponse | null\n channels: Channel[]\n range: RangeKey\n onRange: (value: RangeKey) => void\n customFrom: string\n onCustomFrom: (value: string) => void\n customTo: string\n onCustomTo: (value: string) => void\n channelFilter: string\n onChannelFilter: (value: string) => void\n expandedPeriods: Record<string, boolean>\n onTogglePeriod: (periodKey: string) => void\n sort: DetailSortState\n onSort: (sort: DetailSortState) => void\n bounds: { from: string; to: string }\n tooltip: TooltipApi\n}): ReactNode {\n const { usage, channels, bounds, tooltip, channelFilter } = props\n if (usage === null) return <div className={styles.empty}>台账读取中或不可用…</div>\n\n const totals = usage.totals\n const safeTotals = totals ?? { date: '', calls: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, reasoningTokens: 0, cost: 0 }\n const totalTokens = safeTotals.inputTokens + safeTotals.outputTokens + safeTotals.cacheReadTokens\n const hitRate = hitRateOf(safeTotals.cacheReadTokens, safeTotals.inputTokens, safeTotals.outputTokens)\n const singleChannel = channelFilter === 'ALL' ? null : channelOf(channels, channelFilter)\n const costCell = costCellText(singleChannel, safeTotals.cost)\n const singleDay = bounds.from === bounds.to\n const rangeDays = Math.min(daysBetween(bounds.from, bounds.to), MAX_AXIS_DAYS)\n\n const activeProviders = new Set<string>()\n for (const day of usage.days) {\n for (const model of day.models) {\n if (model.inputTokens + model.outputTokens + model.cacheReadTokens > 0) activeProviders.add(model.provider)\n }\n }\n\n const hourlyRows: TokenBarRow[] = []\n if (singleDay && usage.hours !== null) {\n usage.hours.forEach((slot, hour) => {\n const slotTokens = slot.buckets.inputTokens + slot.buckets.outputTokens + slot.buckets.cacheReadTokens\n hourlyRows.push({\n key: String(hour),\n axisLabel: String(hour).padStart(2, '0') + ':00',\n cacheReadTokens: slot.buckets.cacheReadTokens,\n inputTokens: slot.buckets.inputTokens,\n outputTokens: slot.buckets.outputTokens,\n tip: (\n <>\n <TipTitle>{bounds.from + ' ' + String(hour).padStart(2, '0') + ':00'}</TipTitle>\n {slot.calls === 0 && slotTokens === 0\n ? <TipSub>该小时无用量记录</TipSub>\n : (\n <>\n <TipSub>调用 <b>{fmtCalls(slot.calls)}</b> · Tokens <b>{fmtTokens(slotTokens)}</b></TipSub>\n <TipRow color=\"var(--dsw-alias-state-accent-primary, #5b8cff)\" label=\"输入\" amount={fmtTokens(slot.buckets.inputTokens)} />\n <TipRow color=\"var(--dsw-alias-state-business-primary, #9a7bff)\" label=\"输出\" amount={fmtTokens(slot.buckets.outputTokens)} />\n <TipRow\n color=\"var(--dsw-alias-state-accent-tertiary, rgba(91,140,255,.28))\"\n label=\"缓存读\"\n amount={fmtTokens(slot.buckets.cacheReadTokens)}\n rate={hitRateOf(slot.buckets.cacheReadTokens, slot.buckets.inputTokens, slot.buckets.outputTokens)}\n />\n </>\n )}\n </>\n ),\n })\n })\n }\n\n // 横轴铺满所选范围:没有调用的日子留空位(不画柱子)\n const sessionDates = new Set(usage.sessionDates ?? [])\n const dayByDate = new Map<string, ChannelDailySummary>(usage.days.map((day) => [day.date, day]))\n const dailyRows: TokenBarRow[] = dateKeysOfRange(bounds.from, bounds.to).map((dateKey) => {\n const day = dayByDate.get(dateKey)\n if (day === undefined) {\n return {\n key: dateKey,\n axisLabel: fmtShortDate(dateKey),\n cacheReadTokens: 0,\n inputTokens: 0,\n outputTokens: 0,\n tip: (\n <>\n <TipTitle>{dateKey}</TipTitle>\n <TipSub>当日无用量记录</TipSub>\n </>\n ),\n }\n }\n const dayTotal = day.inputTokens + day.outputTokens + day.cacheReadTokens\n const fromSession = sessionDates.has(day.date)\n const topModels = [...day.models]\n .sort((first, second) => (second.inputTokens + second.outputTokens + second.cacheReadTokens) - (first.inputTokens + first.outputTokens + first.cacheReadTokens))\n .slice(0, 5)\n return {\n key: day.date,\n axisLabel: fmtShortDate(day.date),\n cacheReadTokens: day.cacheReadTokens,\n inputTokens: day.inputTokens,\n outputTokens: day.outputTokens,\n tip: (\n <>\n <TipTitle>{day.date}</TipTitle>\n <TipSub>调用 <b>{fmtCalls(day.calls)}</b> · Tokens <b>{fmtTokens(dayTotal)}</b></TipSub>\n <TipRow color=\"var(--dsw-alias-state-accent-primary, #5b8cff)\" label=\"输入\" amount={fmtTokens(day.inputTokens)} />\n <TipRow color=\"var(--dsw-alias-state-business-primary, #9a7bff)\" label=\"输出\" amount={fmtTokens(day.outputTokens)} />\n <TipRow\n color=\"var(--dsw-alias-state-accent-tertiary, rgba(91,140,255,.28))\"\n label=\"缓存读\"\n amount={fmtTokens(day.cacheReadTokens)}\n rate={hitRateOf(day.cacheReadTokens, day.inputTokens, day.outputTokens)}\n />\n <TipDivider>\n <span>成本</span>\n <b>{day.cost > 0 ? fmtCost(day.cost) : '—'}</b>\n </TipDivider>\n {fromSession && <TipSub>来源:会话事件(该日台账无记录,不含官方成本)</TipSub>}\n {topModels.map((model) => (\n <TipRow\n key={model.provider + '|' + model.model}\n label={<>{model.model}<span className={styles.dim}> · {displayNameOf(channels, model.provider)}</span></>}\n amount={fmtTokens(model.inputTokens + model.outputTokens + model.cacheReadTokens)}\n />\n ))}\n </>\n ),\n }\n })\n\n const useHourAxis = singleDay && usage.hours !== null\n const chartRows = useHourAxis ? hourlyRows : dailyRows\n const chartTitle = useHourAxis ? '按小时 Tokens(' + bounds.from + ')' : '按天 Tokens(' + rangeDays + ' 天)'\n\n return (\n <div>\n <div className={detailStyles.rangeBar}>\n <div className={detailStyles.rangeLeft}>\n <div className={detailStyles.rangeTabs}>\n {RANGE_TABS.map(([key, label]) => (\n <button key={key} className={props.range === key ? detailStyles.active : ''} onClick={() => props.onRange(key)}>{label}</button>\n ))}\n </div>\n {props.range === 'custom' && (\n <>\n <input type=\"date\" className={detailStyles.rangeInput} value={props.customFrom} onChange={(event) => props.onCustomFrom(event.target.value)} />\n <span className={styles.dim}>至</span>\n <input type=\"date\" className={detailStyles.rangeInput} value={props.customTo} onChange={(event) => props.onCustomTo(event.target.value)} />\n </>\n )}\n </div>\n </div>\n\n <div className={styles.cardGrid3}>\n <StatCard label=\"调用次数\" value={fmtCalls(safeTotals.calls)} extra={activeProviders.size + ' 个通道有用量'} />\n <StatCard label=\"Tokens\" value={fmtTokens(totalTokens)} extra={'缓存读 ' + fmtTokens(safeTotals.cacheReadTokens) + ' · 命中率 ' + hitRate} />\n <StatCard\n label=\"成本\"\n value={costCell.dim ? <span className={styles.dim}>{costCell.text}</span> : costCell.text}\n extra=\"按所选范围与筛选聚合\"\n />\n </div>\n\n <ShareBar usage={usage} totalTokens={totalTokens} channelFilter={channelFilter} channels={channels} tooltip={tooltip} />\n\n <div className={styles.chipRow} style={{ marginTop: 14 }}>\n <button className={channelFilter === 'ALL' ? styles.chip + ' ' + styles.active : styles.chip} onClick={() => props.onChannelFilter('ALL')}>\n <span className={styles.statusDot} style={{ background: 'var(--dsw-alias-state-accent-primary, #5b8cff)' }} />全部通道\n </button>\n {channels.map((channel) => (\n <button\n key={channel.provider}\n className={channelFilter === channel.provider ? styles.chip + ' ' + styles.active : styles.chip}\n onClick={() => props.onChannelFilter(channel.provider)}\n >\n <span className={styles.statusDot} style={{ background: statusColorOf(channel.status) }} />{channel.displayName}\n </button>\n ))}\n </div>\n\n <div className={styles.card}>\n <div className={detailStyles.chartTitle}>\n <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>\n {singleChannel === null\n ? <b>全部通道</b>\n : (\n <>\n <span className={styles.statusDot} style={{ background: statusColorOf(singleChannel.status) }} />\n <b>{singleChannel.displayName}</b>\n </>\n )}\n </span>\n <span className={styles.dim} style={{ fontSize: 11.5 }}>{bounds.from} ~ {bounds.to}</span>\n </div>\n <div className={detailStyles.chartTitle} style={{ marginTop: 10 }}>\n <span className={styles.statLabel}>{chartTitle}</span>\n <span className={styles.legend}>\n <span><i style={{ background: 'rgba(91,140,255,.28)' }} />缓存读</span>\n <span><i style={{ background: 'var(--dsw-alias-state-accent-primary, #5b8cff)' }} />输入</span>\n <span><i style={{ background: 'var(--dsw-alias-state-business-primary, #9a7bff)' }} />输出</span>\n </span>\n </div>\n {chartRows.length === 0\n ? <div className={styles.empty}>所选范围内无用量记录</div>\n : <TokenBars rows={chartRows} tooltip={tooltip} />}\n {useHourAxis && <div className={styles.statExtra}>按小时数据来自本地会话事件,不含成本</div>}\n </div>\n\n <DailyDetailTable\n usage={usage}\n channels={channels}\n channelFilter={channelFilter}\n expandedPeriods={props.expandedPeriods}\n onTogglePeriod={props.onTogglePeriod}\n sort={props.sort}\n onSort={props.onSort}\n />\n </div>\n )\n}\n\n/** 通道颜色(供外部复用,避免各 Tab 重复实现)。 */\nexport { channelColorOf }\n","/** 项目用量:按会话工作目录聚合(数据来自宿主会话事件折叠)。 */\nimport { useMemo, useState, type ReactNode } from 'react'\nimport { channelColorOf, channelOf, costCellText, displayNameOf } from '../channels.ts'\nimport { fmtCalls, fmtRelative, fmtTokens } from '../format.ts'\nimport type { Channel, ProjectRow, ProjectsResponse } from '../types.ts'\nimport styles from '../styles/shared.module.css'\nimport listStyles from '../styles/lists.module.css'\n\ntype SortKey = 'recent' | 'tokensDesc' | 'tokensAsc' | 'callsDesc'\n\nexport function ProjectsTab(props: { data: ProjectsResponse | null; channels: Channel[] }): ReactNode {\n const [query, setQuery] = useState('')\n const [channelFilter, setChannelFilter] = useState('ALL')\n const [sortKey, setSortKey] = useState<SortKey>('recent')\n const [nowMs] = useState(() => Date.now())\n const rows = props.data?.rows ?? []\n\n const visible = useMemo(() => {\n const keyword = query.trim().toLowerCase()\n const filtered = rows.filter((row) => {\n const matchesQuery = keyword === '' || row.workspace.toLowerCase().includes(keyword)\n const matchesChannel = channelFilter === 'ALL' || row.providers[channelFilter] !== undefined\n return matchesQuery && matchesChannel\n })\n const sorters: Record<SortKey, (first: ProjectRow, second: ProjectRow) => number> = {\n recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0),\n tokensDesc: (first, second) => second.tokens - first.tokens,\n tokensAsc: (first, second) => first.tokens - second.tokens,\n callsDesc: (first, second) => second.calls - first.calls,\n }\n return [...filtered].sort(sorters[sortKey])\n }, [rows, query, channelFilter, sortKey])\n\n const totalTokens = rows.reduce((total, row) => total + row.tokens, 0)\n\n if (props.data === null) return <div className={styles.empty}>会话数据读取中或不可用…</div>\n\n return (\n <div>\n {props.data.coverage.status !== 'complete' && (\n <div className={styles.warning}>{coverageText(props.data.coverage.status, props.data.coverage.skippedSessions)}</div>\n )}\n <div className={listStyles.filterBar}>\n <input className={listStyles.searchInput} placeholder=\"搜索项目路径…\" value={query} onChange={(event) => setQuery(event.target.value)} />\n <select className={listStyles.filterSelect} value={channelFilter} onChange={(event) => setChannelFilter(event.target.value)}>\n <option value=\"ALL\">全部通道</option>\n {props.channels.map((channel) => <option key={channel.provider} value={channel.provider}>{channel.displayName}</option>)}\n </select>\n <select className={listStyles.filterSelect} value={sortKey} onChange={(event) => setSortKey(event.target.value as SortKey)}>\n <option value=\"recent\">最近活跃</option>\n <option value=\"tokensDesc\">Tokens 最多</option>\n <option value=\"tokensAsc\">Tokens 最少</option>\n <option value=\"callsDesc\">调用最多</option>\n </select>\n </div>\n\n <table className={styles.table}>\n <thead>\n <tr><th>项目</th><th>调用</th><th>Tokens</th><th>占比</th><th>成本</th><th>最近活跃</th></tr>\n </thead>\n <tbody>\n {visible.map((row) => {\n const percent = totalTokens > 0 ? Math.round(row.tokens / totalTokens * 100) : 0\n const dominant = row.dominantProvider\n const channel = channelOf(props.channels, dominant)\n const costCell = costCellText(channel, row.cost ?? 0)\n return (\n <tr key={row.workspace}>\n <td>\n <div className={listStyles.listTitle}>\n <div>\n <div className={listStyles.listMain}>{baseNameOf(row.workspace)}</div>\n <div className={listStyles.listSub}>{row.workspace} · {row.sessionCount} 个会话</div>\n </div>\n </div>\n </td>\n <td>{fmtCalls(row.calls)}</td>\n <td>{fmtTokens(row.tokens)}</td>\n <td>\n <span className={listStyles.listBar}>\n <i style={{ width: Math.max(2, percent) + '%', background: channelColorOf(props.channels, dominant ?? '') }} />\n </span> {percent}%\n </td>\n <td>{row.cost === null || channel?.costMode === 'sub' || channel?.costMode === 'free' || channel?.costMode === 'none'\n ? <span className={styles.dim}>{costCell.text}</span>\n : costCell.text}</td>\n <td>{fmtRelative(row.lastTime, nowMs)}</td>\n </tr>\n )\n })}\n {visible.length === 0 && <tr><td colSpan={6} className={styles.empty}>没有符合条件的项目</td></tr>}\n </tbody>\n </table>\n <div className={styles.statExtra}>\n 成本按台账模型中位均价估算{props.data.priced ? '' : '(台账无成本数据,暂不可估)'};命中率 = 缓存读 /(输入 + 输出 + 缓存读)\n </div>\n </div>\n )\n}\n\nfunction coverageText(status: string, skipped: number): string {\n if (status === 'unavailable') return '会话数据不可用(宿主未提供会话服务或全部读取失败)'\n return '会话数据不完整:' + String(skipped) + ' 个会话读取失败,未计入统计'\n}\n\nexport function baseNameOf(path: string): string {\n const normalized = path.replace(/[\\\\/]+$/, '')\n const parts = normalized.split(/[\\\\/]/)\n return parts[parts.length - 1] === '' ? path : parts[parts.length - 1]\n}\n\n/** 通道显示名(列表里复用)。 */\nexport { displayNameOf }\n","/** 余额与配额:按通道展示官方余额快照与订阅窗口(原样数据,不换算不建议)。 */\nimport type { ReactNode } from 'react'\nimport { quotaBarColor, statusColorOf } from '../channels.ts'\nimport { fmtDateTime } from '../format.ts'\nimport type { Channel, QuotaProvider } from '../types.ts'\nimport styles from '../styles/shared.module.css'\nimport quotaStyles from '../styles/quota.module.css'\n\n/** 余额字段展示顺序(原型顺序);未收录字段按原键名追加在后。 */\nconst BALANCE_ORDER = ['totalBalance', 'topUpBalance', 'giftBalance', 'balance', 'currency']\nconst BALANCE_LABELS: Record<string, string> = {\n totalBalance: '总余额', topUpBalance: '充值余额', giftBalance: '赠送余额', balance: '余额', currency: '币种',\n}\n/** 订阅窗口固定展示顺序。 */\nconst WINDOW_ORDER = ['5h', 'week', 'month', 'day']\nconst WINDOW_LABELS: Record<string, string> = { '5h': '5 小时窗口', week: '本周窗口', month: '本月窗口', day: '当日窗口' }\n\nfunction resetTextOf(value: unknown): string {\n if (typeof value !== 'string' || value === '') return '重置时间 —'\n const time = new Date(value)\n if (Number.isNaN(time.getTime())) return '重置于 ' + value\n return '重置于 ' + fmtDateTime(time.getTime())\n}\n\nexport function QuotaTab(props: { channels: Channel[]; quotas: QuotaProvider[] }): ReactNode {\n const snapshotOf = (provider: string): QuotaProvider | null => props.quotas.find((item) => item.provider === provider) ?? null\n return (\n <div className={quotaStyles.quotaList}>\n {props.channels.map((channel) => {\n const snapshot = snapshotOf(channel.provider)\n const balance = snapshot?.balance ?? null\n const plan = snapshot?.plan ?? null\n const isLive = snapshot?.source === 'live'\n const windows = plan !== null && Array.isArray((plan as { windows?: unknown }).windows)\n ? (plan as { windows: Array<{ key?: unknown; percent?: unknown; resetsAt?: unknown }> }).windows\n : null\n return (\n <div className={quotaStyles.quotaCard} key={channel.provider}>\n <div className={quotaStyles.quotaHead}>\n <span className={styles.statusDot} style={{ background: statusColorOf(channel.status) }} />\n <span className={quotaStyles.quotaTitle}>{channel.displayName}</span>\n {isLive && <span className={quotaStyles.liveChip}>实时</span>}\n <span className={quotaStyles.quotaProvider}>{channel.provider} · {channel.costLabel}</span>\n </div>\n\n {balance !== null && (\n <div className={quotaStyles.balanceStrip}>\n {orderedBalanceEntries(balance).map(([key, value]) => (\n <div className={quotaStyles.balanceItem} key={key}>\n <div className={quotaStyles.bsLabel}>{BALANCE_LABELS[key] ?? key}</div>\n <div className={quotaStyles.bsValue}>{String(value)}</div>\n </div>\n ))}\n </div>\n )}\n\n {balance === null && windows !== null && windows.length > 0 && (\n <div className={quotaStyles.windowRow}>\n {orderedWindows(windows).map((window) => {\n const percent = typeof window.percent === 'number' ? window.percent : 0\n const key = typeof window.key === 'string' ? window.key : 'window'\n return (\n <div className={quotaStyles.windowItem} key={key}>\n <div className={quotaStyles.windowHead}>\n <span className={quotaStyles.windowName}>{WINDOW_LABELS[key] ?? key}</span>\n <span className={quotaStyles.windowValue}>{percent}%</span>\n </div>\n <div className={quotaStyles.windowBar}>\n <i style={{ width: Math.max(percent, 0) + '%', background: quotaBarColor(percent) }} />\n </div>\n <div className={quotaStyles.windowReset}>{resetTextOf(window.resetsAt)}</div>\n </div>\n )\n })}\n </div>\n )}\n\n {balance === null && (windows === null || windows.length === 0) && (\n <div className={styles.dim}>{emptyReasonOf(channel, snapshot)}</div>\n )}\n\n {isLive && (\n <div className={quotaStyles.snapshotHint}>\n 实时查询于 {freshTextOf(snapshot?.fetchedAt ?? null)}(来自官方接口,最多缓存 1 分钟)\n </div>\n )}\n {snapshot !== null && !isLive && (\n <div className={staleDaysOf(snapshot.updatedAt) >= 1 ? quotaStyles.snapshotStale : quotaStyles.snapshotHint}>\n 实时查询未成功({errorTextOf(snapshot.error)}),显示本机快照\n {snapshot.updatedAt === null ? '' : ' · 快照时间 ' + fmtDateTime(snapshot.updatedAt) + (staleDaysOf(snapshot.updatedAt) >= 1 ? ',已 ' + staleDaysOf(snapshot.updatedAt) + ' 天未更新' : '')}\n </div>\n )}\n </div>\n )\n })}\n {props.channels.length === 0 && <div className={styles.empty}>未读取到通道配置</div>}\n </div>\n )\n}\n\n/** 实时结果的相对时间文案。 */\nfunction freshTextOf(fetchedAt: number | null): string {\n if (fetchedAt === null) return '刚刚'\n const seconds = Math.max(0, Math.floor((Date.now() - fetchedAt) / 1000))\n if (seconds < 60) return '刚刚'\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return minutes + ' 分钟前'\n return Math.floor(minutes / 60) + ' 小时前'\n}\n\n/** 实时查询失败原因的展示文案。 */\nfunction errorTextOf(error: string | null): string {\n if (error === null) return '未启用实时查询'\n if (error === 'no-key') return '该通道未配置 API Key'\n if (error === 'no-snapshot') return '没有快照可用'\n if (error === 'network') return '网络不可达'\n if (error === 'timeout') return '请求超时'\n if (error === 'unauthorized') return '凭据被拒绝(401/403)'\n if (error === 'bad-json') return '接口响应无法解析'\n if (error === 'too-large') return '接口响应异常大'\n if (error.startsWith('http-')) return '接口返回 HTTP ' + error.slice(5)\n return error\n}\n\n/** 快照距今天数(向下取整);用于把「宿主很久没刷新」显式标出来。 */\nfunction staleDaysOf(updatedAt: number | null): number {\n if (updatedAt === null) return 0\n return Math.floor((Date.now() - updatedAt) / 86400000)\n}\n\nfunction orderedBalanceEntries(balance: Record<string, unknown>): Array<[string, unknown]> {\n const entries = Object.entries(balance).filter(([key]) => key !== 'updatedAt')\n const ordered: Array<[string, unknown]> = []\n for (const key of BALANCE_ORDER) {\n const found = entries.find(([entryKey]) => entryKey === key)\n if (found !== undefined) ordered.push(found)\n }\n for (const entry of entries) {\n if (!BALANCE_ORDER.includes(entry[0])) ordered.push(entry)\n }\n return ordered\n}\n\nfunction orderedWindows(windows: Array<{ key?: unknown }>): Array<{ key?: unknown; percent?: unknown; resetsAt?: unknown }> {\n const typed = windows as Array<{ key?: unknown; percent?: unknown; resetsAt?: unknown }>\n return [...typed].sort((first, second) => {\n const firstIndex = WINDOW_ORDER.indexOf(typeof first.key === 'string' ? first.key : '')\n const secondIndex = WINDOW_ORDER.indexOf(typeof second.key === 'string' ? second.key : '')\n return (firstIndex < 0 ? 99 : firstIndex) - (secondIndex < 0 ? 99 : secondIndex)\n })\n}\n\n/** 无数据时的分档说明:区分未配置凭据 / 免费额度 / 本地端点 / 无查询能力。 */\nfunction emptyReasonOf(channel: Channel, snapshot: QuotaProvider | null): string {\n if (snapshot?.source === 'snapshot' && snapshot.error === 'no-key') return '该通道未配置 API Key,无法查询余额'\n if (snapshot?.source === 'snapshot' && snapshot.error === 'unauthorized') return '凭据被通道拒绝,无法查询余额'\n if (channel.status === 'off') return '凭据引用存在,Key 未填写'\n if (channel.costMode === 'none') return '本地端点,无余额查询接口'\n if (channel.costMode === 'free') return '该通道无余额查询接口(免费额度)'\n return '该通道暂无可查询的余额 / 配额接口'\n}\n","/** 会话用量:按会话列出 tokens / 命中率 / 时长,按最近活跃时按日分组。 */\nimport { Fragment, useMemo, useState, type ReactNode } from 'react'\nimport { channelOf, costCellText, displayNameOf } from '../channels.ts'\nimport { fmtCalls, fmtDuration, fmtTime, fmtTokens, isoDate } from '../format.ts'\nimport type { Channel, SessionRow, SessionsResponse } from '../types.ts'\nimport styles from '../styles/shared.module.css'\nimport listStyles from '../styles/lists.module.css'\n\ntype SortKey = 'recent' | 'tokensDesc' | 'tokensAsc' | 'durationDesc'\n\nexport function SessionsTab(props: { data: SessionsResponse | null; channels: Channel[] }): ReactNode {\n const [query, setQuery] = useState('')\n const [channelFilter, setChannelFilter] = useState('ALL')\n const [daysFilter, setDaysFilter] = useState('30')\n const [sortKey, setSortKey] = useState<SortKey>('recent')\n const [nowMs] = useState(() => Date.now())\n const rows = props.data?.rows ?? []\n\n const visible = useMemo(() => {\n const keyword = query.trim().toLowerCase()\n const maxAgeDays = Number(daysFilter)\n const filtered = rows.filter((row) => {\n const title = (row.title ?? '') + ' ' + row.id + ' ' + (row.workspace ?? '')\n const matchesQuery = keyword === '' || title.toLowerCase().includes(keyword)\n const matchesChannel = channelFilter === 'ALL' || row.provider === channelFilter\n const matchesAge = maxAgeDays === 0 || row.lastTime === null\n || (nowMs - row.lastTime) <= maxAgeDays * 86400000\n return matchesQuery && matchesChannel && matchesAge\n })\n const sorters: Record<SortKey, (first: SessionRow, second: SessionRow) => number> = {\n recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0),\n tokensDesc: (first, second) => second.tokens - first.tokens,\n tokensAsc: (first, second) => first.tokens - second.tokens,\n durationDesc: (first, second) => durationOf(second) - durationOf(first),\n }\n return [...filtered].sort(sorters[sortKey])\n }, [rows, query, channelFilter, daysFilter, sortKey, nowMs])\n\n if (props.data === null) return <div className={styles.empty}>会话数据读取中或不可用…</div>\n\n const groups: Array<{ label: string; rows: SessionRow[] }> = []\n if (sortKey === 'recent') {\n for (const row of visible) {\n const label = dayLabelOf(row.lastTime, nowMs)\n const last = groups[groups.length - 1]\n if (last === undefined || last.label !== label) groups.push({ label, rows: [row] })\n else last.rows.push(row)\n }\n }\n\n return (\n <div>\n {props.data.coverage.status !== 'complete' && (\n <div className={styles.warning}>\n {props.data.coverage.status === 'unavailable'\n ? '会话数据不可用(宿主未提供会话服务或全部读取失败)'\n : '会话数据不完整:' + String(props.data.coverage.skippedSessions) + ' 个会话读取失败,未计入统计'}\n </div>\n )}\n <div className={listStyles.filterBar}>\n <input className={listStyles.searchInput} placeholder=\"搜索会话标题、session id、工作区…\" value={query} onChange={(event) => setQuery(event.target.value)} />\n <select className={listStyles.filterSelect} value={channelFilter} onChange={(event) => setChannelFilter(event.target.value)}>\n <option value=\"ALL\">全部通道</option>\n {props.channels.map((channel) => <option key={channel.provider} value={channel.provider}>{channel.displayName}</option>)}\n </select>\n <select className={listStyles.filterSelect} value={daysFilter} onChange={(event) => setDaysFilter(event.target.value)}>\n <option value=\"7\">近 7 天</option>\n <option value=\"30\">近 30 天</option>\n <option value=\"0\">全部</option>\n </select>\n <select className={listStyles.filterSelect} value={sortKey} onChange={(event) => setSortKey(event.target.value as SortKey)}>\n <option value=\"recent\">最新会话</option>\n <option value=\"tokensDesc\">Tokens 最多</option>\n <option value=\"tokensAsc\">Tokens 最少</option>\n <option value=\"durationDesc\">时长最长</option>\n </select>\n </div>\n\n <table className={styles.table}>\n <thead>\n <tr><th>会话</th><th>通道</th><th>时间 / 时长</th><th>Tokens</th><th>命中率</th><th>成本</th></tr>\n </thead>\n <tbody>\n {groups.map((group) => {\n const groupTokens = group.rows.reduce((total, row) => total + row.tokens, 0)\n return (\n <Fragment key={'group-' + group.label}>\n <tr className={listStyles.sessionDay}>\n <td colSpan={6}>\n {group.label}\n <span className={listStyles.dayCount}>{group.rows.length} 个会话 · {fmtTokens(groupTokens)}</span>\n </td>\n </tr>\n {group.rows.map((row) => sessionRowOf(row, props.channels))}\n </Fragment>\n )\n })}\n {sortKey !== 'recent' && visible.map((row) => sessionRowOf(row, props.channels))}\n {visible.length === 0 && <tr><td colSpan={6} className={styles.empty}>没有符合条件的会话</td></tr>}\n </tbody>\n </table>\n <div className={styles.statExtra}>\n 会话用量按宿主会话事件折叠(每个结算样本计一次调用);成本按台账模型均价估算\n {props.data.priced ? '' : '(台账无成本数据,暂不可估)'}\n </div>\n </div>\n )\n}\n\nfunction sessionRowOf(row: SessionRow, channels: Channel[]): ReactNode {\n const channel = channelOf(channels, row.provider)\n const costCell = costCellText(channel, row.cost ?? 0)\n return (\n <tr key={row.id}>\n <td>\n <div className={listStyles.listMain}>{row.title ?? '会话 ' + shortIdOf(row.id)}</div>\n <div className={listStyles.listSub}>{row.workspace ?? '未归属工作区'}</div>\n </td>\n <td>{row.providerLabel ?? displayNameOf(channels, row.provider ?? '')}</td>\n <td>\n <div>{row.lastTime === null ? '—' : dayLabelOf(row.lastTime, Date.now()) + ' ' + fmtTime(row.lastTime)}</div>\n <div className={listStyles.listSub}>时长 {fmtDuration(durationOf(row))}</div>\n </td>\n <td>{fmtTokens(row.tokens)}</td>\n <td>{row.cacheHitRate === null ? '—' : row.cacheHitRate.toFixed(1) + '%'}</td>\n <td>{row.cost === null || channel === null || channel.costMode !== 'perToken'\n ? <span className={styles.dim}>{costCell.text}</span>\n : costCell.text}</td>\n </tr>\n )\n}\n\nfunction durationOf(row: SessionRow): number {\n if (row.firstTime === null || row.lastTime === null) return 0\n return Math.max(0, row.lastTime - row.firstTime)\n}\n\n/** 会话头通常不带标题,用 id 的短片段做可读标识。 */\nfunction shortIdOf(id: string): string {\n const stripped = id.replace(/^session-/, '')\n return stripped.slice(0, 8)\n}\n\nfunction dayLabelOf(timeMs: number | null, nowMs: number): string {\n if (timeMs === null) return '未知日期'\n const dateKey = isoDate(new Date(timeMs))\n if (dateKey === isoDate(new Date(nowMs))) return '今日'\n if (dateKey === isoDate(new Date(nowMs - 86400000))) return '昨日'\n return dateKey\n}\n\n/** 保留调用计数格式,避免各 Tab 各写一份。 */\nexport { fmtCalls }\n","/**\n * 「使用情况」设置页外壳:Tab 路由、范围 / 通道筛选状态与取数。\n * 台账类数据(通道 / 台账 / 热力图 / 汇总 / 快照)来自本地文件端点;\n * 项目与会话数据来自宿主会话事件折叠,切到对应 Tab 时才首次拉取。\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { loadChannels, loadHeatmap, loadProjects, loadQuota, loadSessions, loadSummary, loadUsage } from './api.ts'\nimport { isoDate, shiftedIsoDate } from './format.ts'\nimport { TooltipView, useTooltip } from './tooltip.tsx'\nimport { OverviewTab } from './tabs/OverviewTab.tsx'\nimport { DetailTab, type RangeKey } from './tabs/DetailTab.tsx'\nimport { ProjectsTab } from './tabs/ProjectsTab.tsx'\nimport { QuotaTab } from './tabs/QuotaTab.tsx'\nimport { SessionsTab } from './tabs/SessionsTab.tsx'\nimport type { DetailSortState } from './components/DailyDetailTable.tsx'\nimport type {\n Channel,\n HeatmapDay,\n ProjectsResponse,\n QuotaProvider,\n SessionsResponse,\n SummaryResponse,\n UsageResponse,\n} from './types.ts'\nimport styles from './styles/shared.module.css'\n\ntype TabId = 'overview' | 'detail' | 'projects' | 'sessions' | 'quota'\n\nconst TABS: Array<[TabId, string]> = [\n ['overview', '总览'],\n ['detail', '使用明细'],\n ['projects', '项目用量'],\n ['sessions', '会话用量'],\n ['quota', '余额与配额'],\n]\n\nexport function KeyUsageSection() {\n const [activeTab, setActiveTab] = useState<TabId>('overview')\n const [range, setRange] = useState<RangeKey>('month')\n const [customFrom, setCustomFrom] = useState(() => shiftedIsoDate(-29))\n const [customTo, setCustomTo] = useState(() => isoDate(new Date()))\n const [channelFilter, setChannelFilter] = useState('ALL')\n const [expandedPeriods, setExpandedPeriods] = useState<Record<string, boolean>>({})\n const [detailSort, setDetailSort] = useState<DetailSortState>({ key: 'period', dir: 'desc' })\n\n const [channels, setChannels] = useState<Channel[]>([])\n const [quotas, setQuotas] = useState<QuotaProvider[]>([])\n const [summary, setSummary] = useState<SummaryResponse | null>(null)\n const [heatmap, setHeatmap] = useState<HeatmapDay[]>([])\n const [usage, setUsage] = useState<UsageResponse | null>(null)\n const [sessions, setSessions] = useState<SessionsResponse | null>(null)\n const [projects, setProjects] = useState<ProjectsResponse | null>(null)\n const [available, setAvailable] = useState<{ from: string; to: string } | null>(null)\n const [loading, setLoading] = useState(false)\n const tooltip = useTooltip()\n\n const reload = useCallback(async (includeSessionData: boolean, forceQuota = false) => {\n setLoading(true)\n // 先查余额:服务端会借这次调用预热实时结果,随后 /channels 的状态判定就能用上\n const quotaResult = await loadQuota(forceQuota)\n const [channelResult, summaryResult, heatmapResult] = await Promise.all([\n loadChannels(), loadSummary(), loadHeatmap(),\n ])\n setChannels(channelResult ?? [])\n setQuotas(quotaResult ?? [])\n setSummary(summaryResult)\n setHeatmap(heatmapResult ?? [])\n if (includeSessionData) {\n const [sessionResult, projectResult] = await Promise.all([loadSessions(), loadProjects()])\n setSessions(sessionResult)\n setProjects(projectResult)\n }\n setLoading(false)\n }, [])\n\n useEffect(() => { void reload(false) }, [reload])\n\n // 项目 / 会话数据按需加载(首次切到对应 Tab 时)\n useEffect(() => {\n if (activeTab === 'sessions' && sessions === null) void loadSessions().then(setSessions)\n if (activeTab === 'projects' && projects === null) void loadProjects().then(setProjects)\n }, [activeTab, sessions, projects])\n\n const bounds = useMemo(() => {\n const today = isoDate(new Date())\n if (range === 'today') return { from: today, to: today }\n if (range === 'week') return { from: shiftedIsoDate(-6), to: today }\n if (range === 'month') return { from: shiftedIsoDate(-29), to: today }\n if (range === 'custom') return { from: customFrom, to: customTo < customFrom ? customFrom : customTo }\n return { from: available?.from ?? today, to: available?.to ?? today }\n }, [range, customFrom, customTo, available])\n\n useEffect(() => {\n let cancelled = false\n void loadUsage(bounds.from, bounds.to, channelFilter).then((result) => {\n if (cancelled || result === null) return\n setUsage(result)\n if (result.availableFrom !== null && result.availableTo !== null) {\n setAvailable({ from: result.availableFrom, to: result.availableTo })\n }\n })\n return () => { cancelled = true }\n }, [bounds.from, bounds.to, channelFilter])\n\n const refresh = useCallback(() => {\n void reload(activeTab === 'sessions' || activeTab === 'projects' || sessions !== null || projects !== null, true)\n void loadUsage(bounds.from, bounds.to, channelFilter).then((result) => { if (result !== null) setUsage(result) })\n }, [reload, activeTab, sessions, projects, bounds.from, bounds.to, channelFilter])\n\n return (\n <div className={styles.section}>\n <div className={styles.title}>\n <span>使用情况</span>\n <span className={styles.titleActions}>\n <button className={styles.refreshBtn} disabled={loading} onClick={refresh}>\n {loading ? '刷新中…' : '↻ 刷新'}\n </button>\n </span>\n </div>\n\n <div className={styles.tabBar}>\n {TABS.map(([tabId, label]) => (\n <button key={tabId} className={activeTab === tabId ? styles.active : ''} onClick={() => setActiveTab(tabId)}>{label}</button>\n ))}\n </div>\n\n {activeTab === 'overview' && (\n <OverviewTab\n channels={channels}\n summary={summary}\n quotas={quotas}\n heatmap={heatmap}\n tooltip={tooltip}\n onOpenDetail={(provider) => { setChannelFilter(provider); setActiveTab('detail') }}\n />\n )}\n {activeTab === 'detail' && (\n <DetailTab\n usage={usage}\n channels={channels}\n range={range}\n onRange={setRange}\n customFrom={customFrom}\n onCustomFrom={setCustomFrom}\n customTo={customTo}\n onCustomTo={setCustomTo}\n channelFilter={channelFilter}\n onChannelFilter={setChannelFilter}\n expandedPeriods={expandedPeriods}\n onTogglePeriod={(periodKey) => setExpandedPeriods((previous) => ({ ...previous, [periodKey]: previous[periodKey] !== true }))}\n sort={detailSort}\n onSort={setDetailSort}\n bounds={bounds}\n tooltip={tooltip}\n />\n )}\n {activeTab === 'projects' && <ProjectsTab data={projects} channels={channels} />}\n {activeTab === 'sessions' && <SessionsTab data={sessions} channels={channels} />}\n {activeTab === 'quota' && <QuotaTab channels={channels} quotas={quotas} />}\n\n <div className={styles.footnote}>\n <b>口径说明</b>\n <ul className={styles.footnoteList}>\n <li>\n 按天数据优先取宿主用量台账(含官方成本);台账无记录的日期回退本地会话事件折叠,那些天没有官方成本\n {summary !== null && summary.todaySource === 'session' ? '(当前有 ' + summary.sessionDates.length + ' 天由会话补位)' : ''}\n 。两套来源按天取舍、不相加。\n </li>\n <li>热力图按可见区间内活跃日的三分位分档(少 / 中 / 高)。</li>\n <li>项目用量、会话用量与单日「按小时」均来自会话事件,每个结算样本计一次调用(不计成本)。</li>\n <li>命中率 = 缓存读 ÷(输入 + 输出 + 缓存读)。</li>\n <li>余额与订阅窗口实时查询官方接口(失败回落本机快照),原样展示,不做换算与建议。</li>\n </ul>\n </div>\n\n <TooltipView tooltip={tooltip.tooltip} />\n </div>\n )\n}\n","/**\n * 「使用情况」主区域页面(`main` slot,与 sidebar.panellist 的 id 同名配对)。\n * 点击侧边栏导航项后由宿主在主内容区内联渲染 —— 不是弹窗、不占设置页。\n * 滚动由本容器负责:宿主主内容区不会滚动,长表格必须靠自己滚。\n */\nimport type { ReactNode } from 'react'\nimport { KeyUsageSection } from './KeyUsageSection.tsx'\nimport styles from './styles/page.module.css'\n\n/** 宿主会传入根作用域的标准 kit 与绑定的 `t`;本页面当前不需要它们。 */\nexport function UsagePage(_props: Record<string, unknown>): ReactNode {\n return (\n <div className={styles.page}>\n <div className={styles.inner}>\n <KeyUsageSection />\n </div>\n </div>\n )\n}\n","/**\n * dsh-key-usage 客户端入口:注册「使用情况」侧边栏导航项 + 主区域页面。\n * UI 逻辑在 UsagePage / KeyUsageSection 里;本文件只做 cordis apply 接线。\n *\n * slot 契约(对齐已装的 dsh-context「上下文洞察」写法):\n * - `sidebar.panellist`:宿主自持整行(标签 / hover / active),插件只提供图标,`id` 是面板标识;\n * - `main`:同一标识放在 `key` 上,宿主在主内容区按它渲染整页 —— 因此不是弹窗、也不占设置页。\n */\nimport { createElement as h } from 'react'\nimport type { SectionContext } from './types.ts'\nimport { UsageIcon } from './UsageIcon.tsx'\nimport { UsagePage } from './UsagePage.tsx'\n\nconst NS = 'dsh-key-usage'\n/** 侧边栏条目与 main 页面共用的面板标识(宿主 MainPanelId 域,命名空间化避免撞车)。 */\nconst PANEL_ID = NS\n\nexport const name = NS\nexport const inject = ['slots']\n\nexport function apply(ctx: SectionContext): void {\n ctx.slots.inject('main', () => ctx.slots.register({\n name: 'main',\n key: PANEL_ID,\n }, UsagePage))\n\n ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 30,\n label: () => '使用情况',\n }, (props: { size?: number }) => h(UsageIcon, { size: props.size })))\n}\n"],"mappings":"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"}
|
package/cordis.patch.yml
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/collect.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通道(key)配置采集:从 profile 的 cordis.yml 提取 llm-pi-ai 供应商清单
|
|
3
|
+
* (displayName、apiKeyEnv、模型列表),从余额快照提取官方接口原样数据。
|
|
4
|
+
* 只读配置与快照文件,绝不读取凭据明文(.credentials.yaml 不打开)。
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
/**
|
|
8
|
+
* 快照判定不出来的通道在此显式登记计费模式(不把 provider 名单硬编码到客户端)。
|
|
9
|
+
* sensenova 为免费额度通道,宿主没有它的余额/配额快照。
|
|
10
|
+
*/
|
|
11
|
+
const FALLBACK_COST_MODES = {
|
|
12
|
+
sensenova: 'free',
|
|
13
|
+
};
|
|
14
|
+
const WINDOW_LABELS = { '5h': '5 小时', week: '本周', month: '本月', day: '当日' };
|
|
15
|
+
/** 从 cordis.yml 原文提取 LLM 通道清单:
|
|
16
|
+
* - llm-pi-ai 的 providers 块(多通道)
|
|
17
|
+
* - llm-deepseek(DeepSeek 官方插件,独立条目,模型在其 config.models 列表)
|
|
18
|
+
* cordis.yml 含 !!js 表达式,不能用通用 YAML 解析器解析,
|
|
19
|
+
* 这里只做结构化文本提取,避免执行任意表达式。 */
|
|
20
|
+
export function extractChannels(cordisYml) {
|
|
21
|
+
const channels = extractPiAiChannels(cordisYml);
|
|
22
|
+
const deepseekChannel = extractDeepseekChannel(cordisYml);
|
|
23
|
+
if (deepseekChannel !== null)
|
|
24
|
+
channels.unshift(deepseekChannel);
|
|
25
|
+
return channels;
|
|
26
|
+
}
|
|
27
|
+
/** llm-deepseek 条目:`- id: llm-deepseek` / `name: …llm-deepseek-api-key`,模型在其 config.models 下(10 空格「- id:」)。 */
|
|
28
|
+
function extractDeepseekChannel(cordisYml) {
|
|
29
|
+
const entryMatch = cordisYml.match(/^ {0,4}-? ?id: llm-deepseek\s*$(?<entry>[\s\S]*?)(?=^ {0,4}-? ?id: |(?![\s\S]))/m);
|
|
30
|
+
if (entryMatch === null)
|
|
31
|
+
return null;
|
|
32
|
+
const entry = entryMatch.groups?.entry ?? '';
|
|
33
|
+
const models = [];
|
|
34
|
+
const modelPattern = /^ {6}- id:\s*([^\n#]+)/gm;
|
|
35
|
+
for (const modelMatch of entry.matchAll(modelPattern)) {
|
|
36
|
+
const modelId = modelMatch[1].trim();
|
|
37
|
+
const rest = entry.slice(modelMatch.index);
|
|
38
|
+
const nameMatch = rest.match(new RegExp('- id:\\s*' + escapeRegExp(modelId) + '[\\s\\S]*?\\n\\s*name:\\s*([^\\n#]+)'));
|
|
39
|
+
const contextMatch = rest.match(new RegExp('- id:\\s*' + escapeRegExp(modelId) + '[\\s\\S]*?\\n\\s*contextWindow:\\s*(\\d+)'));
|
|
40
|
+
models.push({
|
|
41
|
+
id: modelId,
|
|
42
|
+
name: nameMatch?.[1] !== undefined ? unquoteYamlScalar(nameMatch[1]) : modelId,
|
|
43
|
+
contextWindow: contextMatch ? Number(contextMatch[1]) : null,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
provider: 'deepseek-official',
|
|
48
|
+
displayName: 'DeepSeek',
|
|
49
|
+
apiKeyEnv: entry.match(/apiKeyEnv:\s*([^\n#]+)/)?.[1]?.trim() ?? 'DEEPSEEK_API_KEY',
|
|
50
|
+
baseURL: entry.match(/baseURL:\s*([^\n#]+)/)?.[1]?.trim() ?? null,
|
|
51
|
+
models,
|
|
52
|
+
...unclassifiedChannel(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function extractPiAiChannels(cordisYml) {
|
|
56
|
+
const channels = [];
|
|
57
|
+
// llm-pi-ai 条目可能在根列表(0 列「- id:」)或嵌套列表(2~4 空格),providers: 固定在其 config 下 4 空格处
|
|
58
|
+
const providerSectionMatch = cordisYml.match(/^ {0,4}-? ?id: llm-pi-ai[\s\S]*?^ {4}providers:\s*$/m);
|
|
59
|
+
if (providerSectionMatch === null || providerSectionMatch.index === undefined)
|
|
60
|
+
return channels;
|
|
61
|
+
const sectionStart = providerSectionMatch.index + providerSectionMatch[0].length;
|
|
62
|
+
const remaining = cordisYml.slice(sectionStart);
|
|
63
|
+
// 段终止:下一个根列表插件条目「- id: 」(0 列)
|
|
64
|
+
const endMatch = remaining.match(/^- id: /m);
|
|
65
|
+
const section = endMatch !== null && endMatch.index !== undefined ? remaining.slice(0, endMatch.index) : remaining;
|
|
66
|
+
// provider 块以 6 空格键名开始(providers: 在 4 空格层级之下);
|
|
67
|
+
// 终止于下一个同层键或字符串末尾((?![\s\S]),JS 无 \z,\Z 是字面量 Z 不能用)
|
|
68
|
+
const providerPattern = /^ {6}([a-zA-Z0-9_-]+):\s*$(?<body>[\s\S]*?)(?=^ {6}[a-zA-Z0-9_-]+:\s*$|(?![\s\S]))/gm;
|
|
69
|
+
for (const match of section.matchAll(providerPattern)) {
|
|
70
|
+
const provider = match[1];
|
|
71
|
+
const body = match.groups?.body ?? '';
|
|
72
|
+
const displayName = body.match(/displayName:\s*([^\n#]+)/)?.[1]?.trim() ?? null;
|
|
73
|
+
const apiKeyEnv = body.match(/apiKeyEnv:\s*([^\n#]+)/)?.[1]?.trim() ?? null;
|
|
74
|
+
const baseURL = body.match(/baseURL:\s*([^\n#]+)/)?.[1]?.trim() ?? null;
|
|
75
|
+
const models = [];
|
|
76
|
+
const modelPattern = /^ {10}- id:\s*([^\n#]+)/gm;
|
|
77
|
+
for (const modelMatch of body.matchAll(modelPattern)) {
|
|
78
|
+
const modelId = modelMatch[1].trim();
|
|
79
|
+
const rest = body.slice(modelMatch.index);
|
|
80
|
+
const nameMatch = rest.match(new RegExp('- id:\\s*' + escapeRegExp(modelId) + '[\\s\\S]*?\\n\\s*name:\\s*([^\\n#]+)'));
|
|
81
|
+
const contextMatch = rest.match(new RegExp('- id:\\s*' + escapeRegExp(modelId) + '[\\s\\S]*?\\n\\s*contextWindow:\\s*(\\d+)'));
|
|
82
|
+
models.push({
|
|
83
|
+
id: modelId,
|
|
84
|
+
name: nameMatch?.[1] !== undefined ? unquoteYamlScalar(nameMatch[1]) : modelId,
|
|
85
|
+
contextWindow: contextMatch ? Number(contextMatch[1]) : null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
channels.push({
|
|
89
|
+
provider,
|
|
90
|
+
displayName: displayName !== null ? unquoteYamlScalar(displayName) : provider,
|
|
91
|
+
apiKeyEnv,
|
|
92
|
+
baseURL,
|
|
93
|
+
models,
|
|
94
|
+
...unclassifiedChannel(),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return channels;
|
|
98
|
+
}
|
|
99
|
+
function escapeRegExp(text) {
|
|
100
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
101
|
+
}
|
|
102
|
+
/** 剥掉 YAML 标量的成对引号('GLM-5.3-Flash ' → GLM-5.3-Flash)。 */
|
|
103
|
+
function unquoteYamlScalar(text) {
|
|
104
|
+
const trimmed = text.trim();
|
|
105
|
+
if (trimmed.length >= 2 && ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"')))) {
|
|
106
|
+
return trimmed.slice(1, -1);
|
|
107
|
+
}
|
|
108
|
+
return trimmed;
|
|
109
|
+
}
|
|
110
|
+
/** 读取余额快照并归一化为原样展示结构(不换算、不建议)。 */
|
|
111
|
+
export function readProviderSnapshots(snapshotPath) {
|
|
112
|
+
let parsed = {};
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(readFileSync(snapshotPath, 'utf8'));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { providers: [] };
|
|
118
|
+
}
|
|
119
|
+
const providers = [];
|
|
120
|
+
for (const [provider, raw] of Object.entries(parsed.providers ?? {})) {
|
|
121
|
+
providers.push({
|
|
122
|
+
provider,
|
|
123
|
+
displayName: typeof raw.displayName === 'string' ? raw.displayName : null,
|
|
124
|
+
credential: typeof raw.credential === 'string' ? raw.credential : null,
|
|
125
|
+
updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : null,
|
|
126
|
+
balance: isPlainObject(raw.balance) ? raw.balance : null,
|
|
127
|
+
plan: isPlainObject(raw.plan) ? raw.plan : null,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { providers };
|
|
131
|
+
}
|
|
132
|
+
function isPlainObject(value) {
|
|
133
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
134
|
+
}
|
|
135
|
+
/** extractChannels 阶段尚未判定计费模式与状态,先填默认值。 */
|
|
136
|
+
function unclassifiedChannel() {
|
|
137
|
+
return { costMode: 'unknown', status: 'ok', quotaPercent: null, quotaWindowKey: null, balanceText: null, snapshotUpdatedAt: null };
|
|
138
|
+
}
|
|
139
|
+
/** 订阅窗口:取占用最高的一个窗口(含窗口键)。 */
|
|
140
|
+
function peakWindowOf(plan) {
|
|
141
|
+
if (plan === null || !Array.isArray(plan.windows))
|
|
142
|
+
return null;
|
|
143
|
+
let peak = null;
|
|
144
|
+
for (const raw of plan.windows) {
|
|
145
|
+
const percent = typeof raw.percent === 'number' ? raw.percent : null;
|
|
146
|
+
const key = typeof raw.key === 'string' ? raw.key : null;
|
|
147
|
+
if (percent === null || key === null)
|
|
148
|
+
continue;
|
|
149
|
+
if (peak === null || percent > peak.percent)
|
|
150
|
+
peak = { percent, key };
|
|
151
|
+
}
|
|
152
|
+
return peak;
|
|
153
|
+
}
|
|
154
|
+
/** 余额文本:优先总余额,其次余额,保留快照原样字符串。 */
|
|
155
|
+
function balanceTextOf(balance) {
|
|
156
|
+
if (balance === null)
|
|
157
|
+
return null;
|
|
158
|
+
const value = balance.totalBalance ?? balance.balance;
|
|
159
|
+
if (value === undefined || value === null)
|
|
160
|
+
return null;
|
|
161
|
+
const currency = typeof balance.currency === 'string' ? balance.currency + ' ' : '';
|
|
162
|
+
return currency + String(value);
|
|
163
|
+
}
|
|
164
|
+
/** 数值化余额(用于状态判定);非数值返回 null。 */
|
|
165
|
+
function numericBalanceOf(balance) {
|
|
166
|
+
if (balance === null)
|
|
167
|
+
return null;
|
|
168
|
+
const raw = balance.totalBalance ?? balance.balance;
|
|
169
|
+
if (typeof raw === 'number')
|
|
170
|
+
return raw;
|
|
171
|
+
if (typeof raw === 'string') {
|
|
172
|
+
const parsed = Number.parseFloat(raw);
|
|
173
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
function isLocalEndpoint(channel) {
|
|
178
|
+
if (channel.provider === 'local')
|
|
179
|
+
return true;
|
|
180
|
+
const baseURL = channel.baseURL ?? '';
|
|
181
|
+
return baseURL.includes('127.0.0.1') || baseURL.includes('localhost') || baseURL.includes('0.0.0.0');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 判定通道的计费模式与状态(规则集中在这里,客户端只负责展示):
|
|
185
|
+
* 未配置 → off;本地端点 → none;有订阅窗口 → sub(占用 ≥90 告警 / ≥60 偏高);
|
|
186
|
+
* 有余额 → perToken(余额 ≤0 告警 / <10 偏高);其余按 FALLBACK_COST_MODES → 兜底 unknown。
|
|
187
|
+
*/
|
|
188
|
+
export function classifyChannels(channels, snapshots) {
|
|
189
|
+
const byProvider = new Map(snapshots.map((snapshot) => [snapshot.provider, snapshot]));
|
|
190
|
+
return channels.map((channel) => {
|
|
191
|
+
const snapshot = byProvider.get(channel.provider) ?? null;
|
|
192
|
+
const quota = peakWindowOf(snapshot?.plan ?? null);
|
|
193
|
+
const balanceText = balanceTextOf(snapshot?.balance ?? null);
|
|
194
|
+
const numericBalance = numericBalanceOf(snapshot?.balance ?? null);
|
|
195
|
+
const updatedAt = snapshot?.updatedAt ?? null;
|
|
196
|
+
const base = {
|
|
197
|
+
...channel,
|
|
198
|
+
quotaPercent: quota === null ? null : quota.percent,
|
|
199
|
+
quotaWindowKey: quota === null ? null : quota.key,
|
|
200
|
+
balanceText,
|
|
201
|
+
snapshotUpdatedAt: updatedAt,
|
|
202
|
+
};
|
|
203
|
+
// 本地端点先判:patch 层的 local 通道通常不写 apiKeyEnv,不该被当成「未配置」
|
|
204
|
+
if (isLocalEndpoint(channel)) {
|
|
205
|
+
return { ...base, costMode: 'none', status: 'ok' };
|
|
206
|
+
}
|
|
207
|
+
if (channel.apiKeyEnv === null || channel.apiKeyEnv.trim() === '') {
|
|
208
|
+
return { ...base, costMode: 'unknown', status: 'off' };
|
|
209
|
+
}
|
|
210
|
+
if (quota !== null) {
|
|
211
|
+
const status = quota.percent >= 90 ? 'danger' : quota.percent >= 60 ? 'warn' : 'ok';
|
|
212
|
+
return { ...base, costMode: 'sub', status };
|
|
213
|
+
}
|
|
214
|
+
if (numericBalance !== null) {
|
|
215
|
+
const status = numericBalance <= 0 ? 'danger' : numericBalance < 10 ? 'warn' : 'ok';
|
|
216
|
+
return { ...base, costMode: 'perToken', status };
|
|
217
|
+
}
|
|
218
|
+
const fallback = FALLBACK_COST_MODES[channel.provider];
|
|
219
|
+
return { ...base, costMode: fallback ?? 'unknown', status: 'ok' };
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/** 计费模式的中文标签(总览「类型」列、成本列与余额卡副标题共用)。 */
|
|
223
|
+
export function costModeLabel(costMode) {
|
|
224
|
+
if (costMode === 'sub')
|
|
225
|
+
return '订阅制';
|
|
226
|
+
if (costMode === 'perToken')
|
|
227
|
+
return '按量计费';
|
|
228
|
+
if (costMode === 'free')
|
|
229
|
+
return '免费额度';
|
|
230
|
+
if (costMode === 'none')
|
|
231
|
+
return '本地端点';
|
|
232
|
+
return '未查询';
|
|
233
|
+
}
|
|
234
|
+
/** 订阅窗口键的中文标签。 */
|
|
235
|
+
export function quotaWindowLabel(windowKey) {
|
|
236
|
+
return WINDOW_LABELS[windowKey] ?? windowKey;
|
|
237
|
+
}
|