feat(frontend): centralize client family labels

This commit is contained in:
RWDai
2026-05-22 15:28:19 +08:00
parent ca341703bc
commit 9633bce2e1
5 changed files with 60 additions and 34 deletions

View File

@@ -889,6 +889,7 @@ import {
} from '../utils/status'
import { useRowClick } from '@/composables/useRowClick'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import { formatClientFamily } from '@/features/usage/utils/clientFamily'
import type { DateRangeParams, UsageRecord } from '../types'
import { MultiSelect, TimeRangePicker } from '@/components/common'
import type { MultiSelectOption } from '@/components/common/MultiSelect.vue'
@@ -1112,19 +1113,6 @@ const providerFilterOptions = computed<FilterOption[]>(() => [
})),
])
function formatClientFamily(value: string | null | undefined): string {
const normalized = value?.trim().toLowerCase()
if (!normalized) return '-'
if (normalized === 'codex') return 'Codex'
if (normalized === 'codex_vscode') return 'Codex VS Code'
if (normalized === 'claude_code') return 'Claude Code'
if (normalized === 'opencode') return 'OpenCode'
if (normalized === 'gemini_cli') return 'Gemini CLI'
if (normalized === 'openai_js_sdk') return 'OpenAI JS SDK'
if (normalized === 'generic') return '通用客户端'
return value?.trim() || '-'
}
const clientFamilyFilterOptions = computed<FilterOption[]>(() => {
const families = new Set<string>(props.availableClientFamilies)
props.records.forEach((record) => {

View File

@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest'
import { formatClientFamily } from '../clientFamily'
describe('formatClientFamily', () => {
it('labels supported clients and SDKs', () => {
expect(formatClientFamily('qwen_code')).toBe('Qwen Code')
expect(formatClientFamily('roo_code')).toBe('Roo Code')
expect(formatClientFamily('kilocode')).toBe('KiloCode')
expect(formatClientFamily('cherrystudio')).toBe('Cherry Studio')
expect(formatClientFamily('openui')).toBe('OpenUI')
expect(formatClientFamily('openai_python_sdk')).toBe('OpenAI Python SDK')
expect(formatClientFamily('anthropic_js_sdk')).toBe('Anthropic JS SDK')
})
it('renders unrecognized families as unknown', () => {
expect(formatClientFamily(null)).toBe('unknown')
expect(formatClientFamily('')).toBe('unknown')
expect(formatClientFamily('custom-client')).toBe('unknown')
})
})

View File

@@ -0,0 +1,29 @@
export function formatClientFamily(value: string | null | undefined): string {
const normalized = value?.trim().toLowerCase()
if (!normalized) return 'unknown'
if (normalized === 'codex') return 'Codex'
if (normalized === 'codex_vscode') return 'Codex VS Code'
if (normalized === 'claude_code') return 'Claude Code'
if (normalized === 'opencode') return 'OpenCode'
if (normalized === 'gemini_cli') return 'Gemini CLI'
if (normalized === 'openai_js_sdk') return 'OpenAI JS SDK'
if (normalized === 'openai_python_sdk') return 'OpenAI Python SDK'
if (normalized === 'anthropic_js_sdk') return 'Anthropic JS SDK'
if (normalized === 'anthropic_python_sdk') return 'Anthropic Python SDK'
if (normalized === 'qwen_code') return 'Qwen Code'
if (normalized === 'roo_code') return 'Roo Code'
if (normalized === 'kilocode') return 'KiloCode'
if (normalized === 'cherrystudio') return 'Cherry Studio'
if (normalized === 'openui') return 'OpenUI'
if (normalized === 'cursor') return 'Cursor'
if (normalized === 'windsurf') return 'Windsurf'
if (normalized === 'continue') return 'Continue'
if (normalized === 'cline') return 'Cline'
if (normalized === 'aider') return 'Aider'
if (normalized === 'langchain') return 'LangChain'
if (normalized === 'llamaindex') return 'LlamaIndex'
if (normalized === 'sdk') return 'SDK'
if (normalized === 'generic') return 'generic'
if (normalized === 'unknown') return 'unknown'
return 'unknown'
}

View File

@@ -33,6 +33,7 @@ import {
} from '@/composables/useTTLAnalysis'
import { log } from '@/utils/logger'
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import { formatClientFamily } from '@/features/usage/utils/clientFamily'
// ==================== 缓存统计与亲和性列表 ====================
@@ -264,27 +265,6 @@ function affinityModelSubtitle(item: UserAffinity): string {
return item.model_name
}
function formatClientFamily(family?: string | null): string {
switch (family) {
case 'codex':
return 'Codex'
case 'opencode':
return 'OpenCode'
case 'claude_code':
return 'Claude Code'
case 'openai_js_sdk':
return 'OpenAI JS SDK'
case 'generic':
return '通用'
case undefined:
case null:
case '':
return '未知'
default:
return family
}
}
function providerKeyLabel(item: UserAffinity): string {
if (item.key_name) return item.key_name
if (item.key_prefix === '[OAuth Token]') return 'OAuth 认证'

View File

@@ -547,6 +547,15 @@ async function pollActiveRequests() {
? update.provider_key_name
: undefined
}
if ('client_family' in update) {
record.client_family = typeof update.client_family === 'string' ? update.client_family : null
}
if ('client_ip' in update) {
record.client_ip = typeof update.client_ip === 'string' ? update.client_ip : null
}
if ('user_agent' in update) {
record.user_agent = typeof update.user_agent === 'string' ? update.user_agent : null
}
}
}