@@ -389,6 +381,27 @@
class="whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
+
+
+
{{ t('usage.costDetails') }}
+
+ {{ t('admin.usage.inputCost') }}
+ ${{ tooltipData.input_cost.toFixed(6) }}
+
+
+ {{ t('admin.usage.outputCost') }}
+ ${{ tooltipData.output_cost.toFixed(6) }}
+
+
+ {{ t('admin.usage.cacheCreationCost') }}
+ ${{ tooltipData.cache_creation_cost.toFixed(6) }}
+
+
+ {{ t('admin.usage.cacheReadCost') }}
+ ${{ tooltipData.cache_read_cost.toFixed(6) }}
+
+
+
{{ t('usage.rate') }}
(() => [
{ key: 'stream', label: t('usage.type'), sortable: false },
{ key: 'tokens', label: t('usage.tokens'), sortable: false },
{ key: 'cost', label: t('usage.cost'), sortable: false },
- { key: 'billing_type', label: t('usage.billingType'), sortable: false },
{ key: 'first_token', label: t('usage.firstToken'), sortable: false },
{ key: 'duration', label: t('usage.duration'), sortable: false },
- { key: 'created_at', label: t('usage.time'), sortable: true }
+ { key: 'created_at', label: t('usage.time'), sortable: true },
+ { key: 'user_agent', label: t('usage.userAgent'), sortable: false }
])
const usageLogs = ref([])
@@ -524,6 +537,19 @@ const formatDuration = (ms: number): string => {
return `${(ms / 1000).toFixed(2)}s`
}
+const formatUserAgent = (ua: string): string => {
+ // 提取主要客户端标识
+ if (ua.includes('claude-cli')) return ua.match(/claude-cli\/[\d.]+/)?.[0] || 'Claude CLI'
+ if (ua.includes('Cursor')) return 'Cursor'
+ if (ua.includes('VSCode') || ua.includes('vscode')) return 'VS Code'
+ if (ua.includes('Continue')) return 'Continue'
+ if (ua.includes('Cline')) return 'Cline'
+ if (ua.includes('OpenAI')) return 'OpenAI SDK'
+ if (ua.includes('anthropic')) return 'Anthropic SDK'
+ // 截断过长的 UA
+ return ua.length > 30 ? ua.substring(0, 30) + '...' : ua
+}
+
const formatTokens = (value: number): string => {
if (value >= 1_000_000_000) {
return `${(value / 1_000_000_000).toFixed(2)}B`
@@ -705,7 +731,6 @@ const exportToCSV = async () => {
'Rate Multiplier',
'Billed Cost',
'Original Cost',
- 'Billing Type',
'First Token (ms)',
'Duration (ms)'
]
@@ -722,7 +747,6 @@ const exportToCSV = async () => {
log.rate_multiplier,
log.actual_cost.toFixed(8),
log.total_cost.toFixed(8),
- log.billing_type === 1 ? 'Subscription' : 'Balance',
log.first_token_ms ?? '',
log.duration_ms
].map(escapeCSVValue)
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index b33ad876..a94323f5 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue'
import checker from 'vite-plugin-checker'
import { resolve } from 'path'
+
export default defineConfig({
plugins: [
vue(),
@@ -29,7 +30,7 @@ export default defineConfig({
},
server: {
host: '0.0.0.0',
- port: 3000,
+ port: Number(process.env.VITE_DEV_PORT || 3000),
proxy: {
'/api': {
target: process.env.VITE_DEV_PROXY_TARGET || 'http://localhost:8080',