feat(usage): add User-Agent column to usage logs
- Add user_agent field to UsageLog DTO and mapper - Display User-Agent column in admin and user usage tables - Add formatUserAgent helper to show friendly client names - Include user_agent in Excel export - Remove request_id column from admin usage table
This commit is contained in:
@@ -96,7 +96,7 @@ const exportToExcel = async () => {
|
||||
t('admin.usage.cacheReadCost'), t('admin.usage.cacheCreationCost'),
|
||||
t('usage.rate'), t('usage.original'), t('usage.billed'),
|
||||
t('usage.billingType'), t('usage.firstToken'), t('usage.duration'),
|
||||
t('admin.usage.requestId')
|
||||
t('admin.usage.requestId'), t('usage.userAgent')
|
||||
]
|
||||
const rows = all.map(log => [
|
||||
log.created_at,
|
||||
@@ -120,7 +120,8 @@ const exportToExcel = async () => {
|
||||
log.billing_type === 1 ? t('usage.subscription') : t('usage.balance'),
|
||||
log.first_token_ms ?? '',
|
||||
log.duration_ms,
|
||||
log.request_id || ''
|
||||
log.request_id || '',
|
||||
log.user_agent || ''
|
||||
])
|
||||
const ws = XLSX.utils.aoa_to_sheet([headers, ...rows])
|
||||
const wb = XLSX.utils.book_new()
|
||||
|
||||
@@ -308,6 +308,11 @@
|
||||
}}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-user_agent="{ row }">
|
||||
<span v-if="row.user_agent" class="text-sm text-gray-600 dark:text-gray-400 max-w-[150px] truncate block" :title="row.user_agent">{{ formatUserAgent(row.user_agent) }}</span>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-gray-500">-</span>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<EmptyState :message="t('usage.noRecords')" />
|
||||
</template>
|
||||
@@ -480,7 +485,8 @@ const columns = computed<Column[]>(() => [
|
||||
{ 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<UsageLog[]>([])
|
||||
@@ -545,6 +551,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`
|
||||
|
||||
Reference in New Issue
Block a user