mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat(frontend): centralize client family labels
This commit is contained in:
@@ -889,6 +889,7 @@ import {
|
|||||||
} from '../utils/status'
|
} from '../utils/status'
|
||||||
import { useRowClick } from '@/composables/useRowClick'
|
import { useRowClick } from '@/composables/useRowClick'
|
||||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||||
|
import { formatClientFamily } from '@/features/usage/utils/clientFamily'
|
||||||
import type { DateRangeParams, UsageRecord } from '../types'
|
import type { DateRangeParams, UsageRecord } from '../types'
|
||||||
import { MultiSelect, TimeRangePicker } from '@/components/common'
|
import { MultiSelect, TimeRangePicker } from '@/components/common'
|
||||||
import type { MultiSelectOption } from '@/components/common/MultiSelect.vue'
|
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 clientFamilyFilterOptions = computed<FilterOption[]>(() => {
|
||||||
const families = new Set<string>(props.availableClientFamilies)
|
const families = new Set<string>(props.availableClientFamilies)
|
||||||
props.records.forEach((record) => {
|
props.records.forEach((record) => {
|
||||||
|
|||||||
@@ -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')
|
||||||
|
})
|
||||||
|
})
|
||||||
29
frontend/src/features/usage/utils/clientFamily.ts
Normal file
29
frontend/src/features/usage/utils/clientFamily.ts
Normal 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'
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
} from '@/composables/useTTLAnalysis'
|
} from '@/composables/useTTLAnalysis'
|
||||||
import { log } from '@/utils/logger'
|
import { log } from '@/utils/logger'
|
||||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
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
|
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 {
|
function providerKeyLabel(item: UserAffinity): string {
|
||||||
if (item.key_name) return item.key_name
|
if (item.key_name) return item.key_name
|
||||||
if (item.key_prefix === '[OAuth Token]') return 'OAuth 认证'
|
if (item.key_prefix === '[OAuth Token]') return 'OAuth 认证'
|
||||||
|
|||||||
@@ -547,6 +547,15 @@ async function pollActiveRequests() {
|
|||||||
? update.provider_key_name
|
? update.provider_key_name
|
||||||
: undefined
|
: 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user