mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Fix/usage transfer filter (#307)
* fix(usage): 恢复 usage 列表中的 fallback 路由信号 * fix(admin): 支持 usage 记录展示和筛选 fallback 转移 * fix(usage): 在用户 usage 记录中暴露 fallback 标记 * fix(usage): 共享 usage 页面支持 fallback 筛选 * fix(usage): 对齐 fallback 筛选相关前端类型 * fix(usage): propagate has_fallback through active polling --------- Co-authored-by: fawney19 <elky0401@gmail.com>
This commit is contained in:
@@ -69,6 +69,7 @@ export interface UsageRecordDetail {
|
||||
cache_creation_price_per_1m?: number
|
||||
cache_read_price_per_1m?: number
|
||||
price_per_request?: number // 按次计费价格
|
||||
has_fallback?: boolean
|
||||
api_key?: {
|
||||
id: string
|
||||
name: string
|
||||
@@ -293,6 +294,7 @@ export const meApi = {
|
||||
api_format?: string | null
|
||||
endpoint_api_format?: string | null
|
||||
has_format_conversion?: boolean | null
|
||||
has_fallback?: boolean | null
|
||||
}>
|
||||
}> {
|
||||
const params = ids ? { ids } : {}
|
||||
|
||||
@@ -307,6 +307,7 @@ export const usageApi = {
|
||||
api_format?: string | null
|
||||
endpoint_api_format?: string | null
|
||||
has_format_conversion?: boolean | null
|
||||
has_fallback?: boolean | null
|
||||
target_model?: string | null
|
||||
}>
|
||||
}> {
|
||||
|
||||
@@ -401,6 +401,7 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
api_format: existing.api_format || record.api_format,
|
||||
endpoint_api_format: existing.endpoint_api_format || record.endpoint_api_format,
|
||||
has_format_conversion: existing.has_format_conversion ?? record.has_format_conversion,
|
||||
has_fallback: existing.has_fallback === true || record.has_fallback === true,
|
||||
api_key_name: existing.api_key_name || record.api_key_name,
|
||||
provider_key_name: existing.provider_key_name || record.provider_key_name,
|
||||
rate_multiplier: existing.rate_multiplier ?? record.rate_multiplier,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ref, computed, type Ref } from 'vue'
|
||||
import type { UsageRecord, FilterStatusValue } from '../types'
|
||||
import { isUsageRecordFailed } from '../utils/status'
|
||||
import { hasUsageFallback, isUsageRecordFailed } from '../utils/status'
|
||||
|
||||
export interface UseUsageFiltersOptions {
|
||||
/** 所有记录的响应式引用 */
|
||||
@@ -79,6 +79,8 @@ export function useUsageFilters(options: UseUsageFiltersOptions) {
|
||||
records = records.filter(record => isUsageRecordFailed(record))
|
||||
} else if (filterStatus.value === 'cancelled') {
|
||||
records = records.filter(record => record.status === 'cancelled')
|
||||
} else if (filterStatus.value === 'has_fallback') {
|
||||
records = records.filter(record => hasUsageFallback(record))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,14 @@ export interface DateRangeParams {
|
||||
export type PeriodValue = 'today' | 'yesterday' | 'last7days' | 'last30days' | 'last90days'
|
||||
|
||||
// 筛选状态(简化为常用维度)
|
||||
export type FilterStatusValue = '__all__' | 'stream' | 'standard' | 'active' | 'failed' | 'cancelled'
|
||||
export type FilterStatusValue =
|
||||
'__all__' |
|
||||
'stream' |
|
||||
'standard' |
|
||||
'active' |
|
||||
'failed' |
|
||||
'cancelled' |
|
||||
'has_fallback'
|
||||
|
||||
// 默认统计状态
|
||||
export function createDefaultStats(): UsageStatsState {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
hasUsageFallback,
|
||||
isUsageRecordFailed,
|
||||
isUsageRecordSuccessful,
|
||||
mapRequestStatusToTimelineStatus,
|
||||
@@ -91,6 +92,12 @@ describe('usage status helpers', () => {
|
||||
})).toBe('failed')
|
||||
})
|
||||
|
||||
it('uses explicit has_fallback flag for transfer filtering', () => {
|
||||
expect(hasUsageFallback(buildUsageRecord({ has_fallback: true }))).toBe(true)
|
||||
expect(hasUsageFallback(buildUsageRecord({ has_fallback: false }))).toBe(false)
|
||||
expect(hasUsageFallback(buildUsageRecord({ has_fallback: undefined }))).toBe(false)
|
||||
})
|
||||
|
||||
it('uses status code only as a last fallback for timeline status', () => {
|
||||
expect(resolveTimelineFinalStatus({
|
||||
statusCode: 200,
|
||||
|
||||
@@ -11,6 +11,12 @@ function hasLegacyFailureSignal(
|
||||
(typeof record.error_message === 'string' && record.error_message.trim().length > 0)
|
||||
}
|
||||
|
||||
export function hasUsageFallback(
|
||||
record: Pick<UsageRecord, 'has_fallback'>
|
||||
): boolean {
|
||||
return record.has_fallback === true
|
||||
}
|
||||
|
||||
function hasTerminalSuccessStatusCode(
|
||||
record: Pick<UsageRecord, 'status_code'>
|
||||
): boolean {
|
||||
|
||||
@@ -144,7 +144,7 @@ import {
|
||||
getDateRangeFromPeriod
|
||||
} from '@/features/usage/composables'
|
||||
import { reconcileActiveRequestDiscovery } from '@/features/usage/utils/activeRequestDiscovery'
|
||||
import { isUsageRecordFailed } from '@/features/usage/utils/status'
|
||||
import { hasUsageFallback, isUsageRecordFailed } from '@/features/usage/utils/status'
|
||||
import type { DateRangeParams, FilterStatusValue } from '@/features/usage/types'
|
||||
import type { UserOption } from '@/features/usage/components/UsageRecordsTable.vue'
|
||||
import { log } from '@/utils/logger'
|
||||
@@ -309,6 +309,8 @@ const filteredRecords = computed(() => {
|
||||
records = records.filter(record => isUsageRecordFailed(record))
|
||||
} else if (filterStatus.value === 'cancelled') {
|
||||
records = records.filter(record => record.status === 'cancelled')
|
||||
} else if (filterStatus.value === 'has_fallback') {
|
||||
records = records.filter(record => hasUsageFallback(record))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +401,9 @@ async function pollActiveRequests() {
|
||||
if (update.api_format != null) record.api_format = update.api_format
|
||||
if (update.endpoint_api_format != null) record.endpoint_api_format = update.endpoint_api_format
|
||||
if (update.has_format_conversion != null) record.has_format_conversion = update.has_format_conversion
|
||||
if (typeof update.has_fallback === 'boolean') {
|
||||
record.has_fallback = record.has_fallback === true || update.has_fallback
|
||||
}
|
||||
// 模型映射:streaming 时已可确定
|
||||
if ('target_model' in update && (typeof update.target_model === 'string' || update.target_model === null)) {
|
||||
record.target_model = update.target_model
|
||||
|
||||
Reference in New Issue
Block a user