mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 13:10:21 +08:00
Merge branch 'fawney19:main' into main
This commit is contained in:
@@ -597,42 +597,20 @@
|
||||
>({{ record.rate_multiplier }}x)</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 故障转移图标(优先显示) -->
|
||||
<svg
|
||||
<Shuffle
|
||||
v-if="record.has_fallback"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
data-usage-attempt-marker="fallback"
|
||||
class="w-3.5 h-3.5 text-amber-600 dark:text-amber-400 flex-shrink-0"
|
||||
title="此请求发生了 Provider 故障转移"
|
||||
>
|
||||
<path d="m16 3 4 4-4 4" />
|
||||
<path d="M20 7H4" />
|
||||
<path d="m8 21-4-4 4-4" />
|
||||
<path d="M4 17h16" />
|
||||
</svg>
|
||||
<!-- 重试图标(仅在无故障转移时显示) -->
|
||||
<svg
|
||||
v-else-if="record.has_retry"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-label="发生 Provider 故障转移"
|
||||
/>
|
||||
<RefreshCcw
|
||||
v-if="record.has_retry"
|
||||
data-usage-attempt-marker="retry"
|
||||
class="w-3.5 h-3.5 text-blue-600 dark:text-blue-400 flex-shrink-0"
|
||||
title="此请求发生了亲和缓存重试"
|
||||
>
|
||||
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
|
||||
<path d="M21 21v-5h-5" />
|
||||
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
||||
<path d="M3 3v5h5" />
|
||||
</svg>
|
||||
title="此请求发生了重试"
|
||||
aria-label="发生重试"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
@@ -871,7 +849,7 @@ import {
|
||||
SortableTableHead,
|
||||
TableFilterMenu,
|
||||
} from '@/components/ui'
|
||||
import { RefreshCcw, Search } from 'lucide-vue-next'
|
||||
import { RefreshCcw, Search, Shuffle } from 'lucide-vue-next'
|
||||
import { formatTokens, formatCurrency } from '@/utils/format'
|
||||
import { getCacheCreationTokens, getCacheReadTokens, getEffectiveInputTokens } from '../token-normalization'
|
||||
import {
|
||||
@@ -888,7 +866,7 @@ import {
|
||||
resolveUsageStreamLabelSegments
|
||||
} from '../utils/status'
|
||||
import { useRowClick } from '@/composables/useRowClick'
|
||||
import { formatApiFormat } from '@/api/endpoints/types/api-format'
|
||||
import { API_FORMAT_ORDER, 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'
|
||||
@@ -1014,20 +992,11 @@ const emit = defineEmits<{
|
||||
'prefetchDetail': [id: string]
|
||||
}>()
|
||||
|
||||
// 静态常量(放在 defineProps/defineEmits 之后)
|
||||
const AVAILABLE_API_FORMATS = [
|
||||
{ value: 'openai:chat', label: 'OpenAI Chat' },
|
||||
{ value: 'openai:responses', label: 'OpenAI Responses' },
|
||||
{ value: 'openai:responses:compact', label: 'OpenAI Responses Compact' },
|
||||
{ value: 'openai:video', label: 'OpenAI Video' },
|
||||
{ value: 'claude:messages', label: 'Claude Messages' },
|
||||
{ value: 'gemini:generate_content', label: 'Gemini Generate Content' },
|
||||
{ value: 'gemini:video', label: 'Gemini Video' },
|
||||
{ value: 'gemini:files', label: 'Gemini Files' },
|
||||
] as const
|
||||
|
||||
// 使用模块级常量
|
||||
const availableApiFormats = AVAILABLE_API_FORMATS
|
||||
// 使用统一 API 格式枚举,避免使用记录筛选项和系统格式列表漂移。
|
||||
const availableApiFormats = API_FORMAT_ORDER.map((value) => ({
|
||||
value,
|
||||
label: formatApiFormat(value),
|
||||
}))
|
||||
|
||||
const adminVisibleColumnIds = useLocalStorage<UsageRecordColumnId[]>(
|
||||
'usage-records-visible-columns-admin',
|
||||
|
||||
@@ -74,14 +74,15 @@ vi.mock('lucide-vue-next', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
const Icon = defineComponent({
|
||||
name: 'IconStub',
|
||||
setup() {
|
||||
return () => h('span')
|
||||
setup(_, { attrs }) {
|
||||
return () => h('span', attrs)
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
RefreshCcw: Icon,
|
||||
Search: Icon,
|
||||
Shuffle: Icon,
|
||||
ChevronDown: Icon,
|
||||
Check: Icon,
|
||||
}
|
||||
@@ -180,7 +181,8 @@ describe('UsageRecordsTable', () => {
|
||||
.filter((element) => element.textContent?.trim() === '100 tps')
|
||||
expect(tpsElements.some((element) => element.classList.contains('text-[11px]'))).toBe(false)
|
||||
|
||||
const titles = [...root.querySelectorAll<HTMLElement>('[title]')].map((element) => element.title)
|
||||
const titles = [...root.querySelectorAll<HTMLElement>('[title]')]
|
||||
.map((element) => element.getAttribute('title'))
|
||||
expect(titles).toContain([
|
||||
'首字: 0.50s',
|
||||
'总耗时: 1.00s',
|
||||
@@ -262,4 +264,23 @@ describe('UsageRecordsTable', () => {
|
||||
expect(root.textContent).toContain('0.50s / 1.00s')
|
||||
expect(root.textContent).toContain('gpt-5')
|
||||
})
|
||||
|
||||
it('offers embedding API formats in the usage record filter', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({ api_format: 'openai:chat' })])
|
||||
|
||||
expect(root.textContent).toContain('OpenAI Embedding')
|
||||
expect(root.textContent).toContain('Gemini Embedding')
|
||||
expect(root.textContent).toContain('Jina Embedding')
|
||||
expect(root.textContent).toContain('Doubao Embedding')
|
||||
})
|
||||
|
||||
it('shows retry and fallback markers together when both flags are set', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
has_fallback: true,
|
||||
has_retry: true,
|
||||
})])
|
||||
|
||||
expect(root.querySelector('[data-usage-attempt-marker="fallback"]')).not.toBeNull()
|
||||
expect(root.querySelector('[data-usage-attempt-marker="retry"]')).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user