File size: 15,630 Bytes
2986042 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | <!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/web/favicon.ico">
<link rel="apple-touch-icon" href="/web/apple-touch-icon.png">
<link rel="manifest" href="/web/manifest.json">
<meta name="theme-color" content="#3b82f6">
<title data-i18n="stats.title">调用统计 - Claude Code & Codex Proxy</title>
<link rel="stylesheet" href="/web/assets/css/styles.css?v=__VERSION__">
<link rel="stylesheet" href="/web/assets/css/channels.css?v=__VERSION__">
<script defer src="/web/assets/locales/zh-CN.js?v=__VERSION__"></script>
<script defer src="/web/assets/locales/en.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/i18n.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/echarts.min.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/template-engine.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/date-range-selector.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/filter-state.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/filter-query.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/page-filters.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/ui.js?v=__VERSION__"></script>
<script defer src="/web/assets/js/stats.js?v=__VERSION__"></script>
</head>
<body>
<div class="app-container">
<!-- 主内容区域 -->
<main class="main-content">
<div class="content-area">
<div data-page-filters="stats"></div>
<!-- 统计详情表格 -->
<section class="mb-8">
<div class="glass-card stats-detail-card">
<h3 class="section-title stats-detail-heading mb-6">
<span class="stats-detail-heading-main">
<svg class="inline-block w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<span data-i18n="stats.detailTitle">详细统计数据</span>
<small class="stats-detail-sort-hint" data-i18n="stats.sortByPriority">
按渠道类型、优先级、名称排序
</small>
</span>
<!-- 表格/图表切换按钮 -->
<div class="view-toggle-group" id="view-toggle-group">
<button type="button" class="view-toggle-btn active" data-view="table">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
<span data-i18n="stats.viewTable">表格</span>
</button>
<button type="button" class="view-toggle-btn" data-view="chart">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z"/>
</svg>
<span data-i18n="stats.viewChart">图表</span>
</button>
</div>
</h3>
<!-- 统计表格 -->
<script>
// 立即恢复视图状态,避免闪烁
(function() {
try {
var savedView = localStorage.getItem('stats.view');
if (savedView === 'chart') {
document.documentElement.classList.add('stats-view-init-chart');
}
} catch(_) {}
})();
</script>
<div class="table-container stats-table-container mobile-card-table-container" id="stats-table-view">
<table class="modern-table stats-table mobile-card-table">
<thead>
<tr>
<th class="sortable" data-column="channel_name">
<span data-i18n="stats.channelName">渠道名称</span>
<span class="sort-indicator" id="sort-channel_name"></span>
</th>
<th class="sortable" data-column="model">
<span data-i18n="common.model">模型</span>
<span class="sort-indicator" id="sort-model"></span>
</th>
<th class="sortable stats-header-accent--success" data-column="success">
<span data-i18n="common.success">成功</span>
<span class="sort-indicator" id="sort-success"></span>
</th>
<th class="sortable stats-header-accent--error" data-column="error">
<span data-i18n="common.failed">失败</span>
<span class="sort-indicator" id="sort-error"></span>
</th>
<th class="sortable" data-column="avg_first_byte_time">
<span data-i18n="stats.avgFirstByte">首字/耗时(秒)</span>
<span class="sort-indicator" id="sort-avg_first_byte_time"></span>
</th>
<th class="sortable" data-column="avg_speed">
<span data-i18n="stats.avgSpeed">Tok/s</span>
<span class="sort-indicator" id="sort-avg_speed"></span>
</th>
<th class="sortable" data-column="rpm" data-i18n-title="stats.rpmTitle" title="每分钟请求数(峰值/平均/最近)">
<span data-i18n="stats.rpm">RPM(峰/均/近)</span>
<span class="sort-indicator" id="sort-rpm"></span>
</th>
<th class="sortable" data-column="total_input_tokens">
<span data-i18n="stats.inputTokens">输入</span>
<span class="sort-indicator" id="sort-total_input_tokens"></span>
</th>
<th class="sortable" data-column="total_output_tokens">
<span data-i18n="stats.outputTokens">输出</span>
<span class="sort-indicator" id="sort-total_output_tokens"></span>
</th>
<th class="sortable stats-header-accent--cache-read" data-column="total_cache_read">
<span data-i18n="stats.cacheRead">缓存读取</span>
<span class="sort-indicator" id="sort-total_cache_read"></span>
</th>
<th class="sortable stats-header-accent--cache-create" data-column="total_cache_creation">
<span data-i18n="stats.cacheCreation">缓存创建</span>
<span class="sort-indicator" id="sort-total_cache_creation"></span>
</th>
<th data-column="cache_util">
<span data-i18n="stats.cacheUtil">缓存命中</span>
</th>
<th class="sortable stats-header-accent--cost" data-column="total_cost">
<span data-i18n="stats.costUsd">成本</span>
<span class="sort-indicator" id="sort-total_cost"></span>
</th>
</tr>
</thead>
<tbody id="stats_tbody">
<tr>
<td colspan="13" class="loading-state">
<div class="loading-spinner loading-spinner--block"></div>
<span data-i18n="stats.loading">正在加载统计数据...</span>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 图表视图 -->
<div id="stats-chart-view" class="hidden">
<div class="charts-grid">
<!-- 第1行:调用次数 -->
<!-- 渠道调用次数饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartChannelCalls">渠道调用次数</h4>
<div id="chart-channel-calls" class="pie-chart-container"></div>
</div>
<!-- 模型调用次数饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartModelCalls">模型调用次数</h4>
<div id="chart-model-calls" class="pie-chart-container"></div>
</div>
<!-- 第2行:成本 -->
<!-- 渠道成本饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartChannelCost">渠道成本</h4>
<div id="chart-channel-cost" class="pie-chart-container"></div>
</div>
<!-- 模型成本饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartModelCost">模型成本</h4>
<div id="chart-model-cost" class="pie-chart-container"></div>
</div>
<!-- 第3行:Token用量 -->
<!-- 渠道Token用量饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartChannelTokens">渠道Token用量</h4>
<div id="chart-channel-tokens" class="pie-chart-container"></div>
</div>
<!-- 模型Token用量饼图 -->
<div class="chart-card">
<h4 class="chart-title" data-i18n="stats.chartModelTokens">模型Token用量</h4>
<div id="chart-model-tokens" class="pie-chart-container"></div>
</div>
</div>
</div>
</div>
</section>
</div>
</main>
</div>
<!-- 加载状态模板 -->
<template id="tpl-stats-loading">
<tr>
<td colspan="{{colspan}}" class="loading-state">
<div class="loading-spinner loading-spinner--block"></div>
<span data-i18n="stats.loading">正在加载统计数据...</span>
</td>
</tr>
</template>
<!-- 错误状态模板 -->
<template id="tpl-stats-error">
<tr>
<td colspan="{{colspan}}" class="empty-state">
<svg class="w-12 h-12 mx-auto mb-4 empty-state-icon--error" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.864-.833-2.634 0L4.18 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div class="empty-state-title empty-state-title--error" data-i18n="stats.loadFailed">加载失败</div>
<div data-i18n="stats.checkNetwork">请检查网络连接或重试</div>
</td>
</tr>
</template>
<!-- 空数据状态模板 -->
<template id="tpl-stats-empty">
<tr>
<td colspan="{{colspan}}" class="empty-state">
<svg class="w-12 h-12 mx-auto mb-4 empty-state-icon--neutral" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
<div class="empty-state-title" data-i18n="stats.noData">暂无统计数据</div>
<div data-i18n="stats.adjustFilter">请调整筛选条件或检查时间范围</div>
</td>
</tr>
</template>
<!-- 数据行模板 -->
<template id="tpl-stats-row">
<tr class="mobile-card-row stats-data-row">
<td class="stats-col-channel" data-mobile-label="{{mobileLabelChannel}}">
<a href="#" class="config-name channel-link" data-channel-name="{{channelNameAttr}}" title="查看该渠道的日志">{{channelName}}</a>
{{{channelIdBadge}}}
{{{healthIndicator}}}
</td>
<td class="stats-col-model" data-mobile-label="{{mobileLabelModel}}">{{{modelDisplay}}}</td>
<td class="stats-col-success" data-mobile-label="{{mobileLabelSuccess}}">{{{successDisplay}}}</td>
<td class="stats-col-error" data-mobile-label="{{mobileLabelError}}"><span class="error-count">{{errorCount}}</span></td>
<td class="stats-col-timing {{timingCellClass}}" data-mobile-label="{{mobileLabelTiming}}">{{{avgFirstByteTime}}}</td>
<td class="stats-col-speed {{speedCellClass}}" data-mobile-label="{{mobileLabelSpeed}}">{{{avgSpeed}}}</td>
<td class="stats-col-rpm" data-mobile-label="{{mobileLabelRpm}}">{{{rpm}}}</td>
<td class="stats-col-input {{inputCellClass}}" data-mobile-label="{{mobileLabelInput}}">{{{inputTokens}}}</td>
<td class="stats-col-output {{outputCellClass}}" data-mobile-label="{{mobileLabelOutput}}">{{{outputTokens}}}</td>
<td class="stats-col-cache-read {{cacheReadCellClass}}" data-mobile-label="{{mobileLabelCacheRead}}">{{{cacheReadTokens}}}</td>
<td class="stats-col-cache-create {{cacheCreateCellClass}}" data-mobile-label="{{mobileLabelCacheCreate}}">{{{cacheCreationTokens}}}</td>
<td class="stats-col-cache-util {{cacheUtilCellClass}}" data-mobile-label="{{mobileLabelCacheUtil}}">{{{cacheUtilText}}}</td>
<td class="stats-col-cost {{costCellClass}}" data-mobile-label="{{mobileLabelCost}}">{{{costText}}}</td>
</tr>
</template>
<!-- 合计行模板 -->
<template id="tpl-stats-total">
<tr class="mobile-card-row stats-total-row">
<td colspan="2" class="stats-col-total-label" data-mobile-label="{{mobileLabelSummary}}" data-i18n="stats.total">合计</td>
<td class="stats-col-success" data-mobile-label="{{mobileLabelSuccess}}">{{{successDisplay}}}</td>
<td class="stats-col-error" data-mobile-label="{{mobileLabelError}}"><span class="error-count">{{errorCount}}</span></td>
<td class="stats-col-timing {{timingCellClass}}" data-mobile-label="{{mobileLabelTiming}}">{{{avgFirstByteTime}}}</td>
<td class="stats-col-speed {{speedCellClass}}" data-mobile-label="{{mobileLabelSpeed}}">{{{avgSpeed}}}</td>
<td class="stats-col-rpm" data-mobile-label="{{mobileLabelRpm}}">{{{rpm}}}</td>
<td class="stats-col-input" data-mobile-label="{{mobileLabelInput}}">{{inputTokens}}</td>
<td class="stats-col-output" data-mobile-label="{{mobileLabelOutput}}">{{outputTokens}}</td>
<td class="stats-col-cache-read" data-mobile-label="{{mobileLabelCacheRead}}"><span class="stats-value-success">{{cacheReadTokens}}</span></td>
<td class="stats-col-cache-create" data-mobile-label="{{mobileLabelCacheCreate}}"><span class="stats-value-primary">{{cacheCreationTokens}}</span></td>
<td class="stats-col-cache-util" data-mobile-label="{{mobileLabelCacheUtil}}">{{{cacheUtilText}}}</td>
<td class="stats-col-cost" data-mobile-label="{{mobileLabelCost}}">{{{costText}}}</td>
</tr>
</template>
</body>
</html>
|