mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 21:20:20 +08:00
feat(usage): 展示压缩操作与进行态请求语义
This commit is contained in:
@@ -940,6 +940,7 @@ export interface ProviderModelMapping {
|
||||
priority: number // 优先级(数字越小优先级越高)
|
||||
api_formats?: string[] // 作用域(适用的 API 格式),为空表示对所有格式生效
|
||||
endpoint_ids?: string[] // 作用域(适用的端点 ID),为空表示对所有端点生效
|
||||
operations?: string[] // 作用域(适用的请求操作),为空表示对该格式的全部操作生效
|
||||
}
|
||||
|
||||
// 保留别名以保持向后兼容
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface UsageRecordDetail {
|
||||
id: string
|
||||
provider?: string // 仅管理员可见
|
||||
model: string
|
||||
request_type?: string | null
|
||||
reasoning_effort?: string | null
|
||||
service_tier?: string | null
|
||||
actual_service_tier?: string | null
|
||||
@@ -368,6 +369,7 @@ export const meApi = {
|
||||
has_format_conversion?: boolean | null
|
||||
has_fallback?: boolean | null
|
||||
target_model?: string | null
|
||||
request_type?: string | null
|
||||
reasoning_effort?: string | null
|
||||
service_tier?: string | null
|
||||
actual_service_tier?: string | null
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface UsageRecord {
|
||||
provider_id?: string // UUID
|
||||
provider_name?: string
|
||||
model: string
|
||||
request_type?: string | null
|
||||
reasoning_effort?: string | null
|
||||
service_tier?: string | null
|
||||
actual_service_tier?: string | null
|
||||
@@ -566,6 +567,7 @@ export const usageApi = {
|
||||
has_format_conversion?: boolean | null
|
||||
has_fallback?: boolean | null
|
||||
target_model?: string | null
|
||||
request_type?: string | null
|
||||
reasoning_effort?: string | null
|
||||
service_tier?: string | null
|
||||
actual_service_tier?: string | null
|
||||
|
||||
@@ -58,6 +58,21 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<Label class="text-xs">请求操作</Label>
|
||||
<span class="text-xs text-muted-foreground">{{ operationScopeSummary }}</span>
|
||||
</div>
|
||||
<MultiSelect
|
||||
v-model="selectedOperations"
|
||||
:options="operationOptions"
|
||||
placeholder="全部操作"
|
||||
empty-text="暂无可选操作"
|
||||
no-results-text="未找到操作"
|
||||
trigger-class="h-9 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 映射名称选择面板 -->
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">提供商模型</Label>
|
||||
@@ -302,6 +317,8 @@ export interface AliasGroup {
|
||||
apiFormats: string[]
|
||||
endpointIdsKey: string
|
||||
endpointIds: string[]
|
||||
operationsKey: string
|
||||
operations: string[]
|
||||
aliases: ProviderModelAlias[]
|
||||
}
|
||||
|
||||
@@ -330,6 +347,11 @@ type EndpointOption = {
|
||||
label: string
|
||||
}
|
||||
|
||||
type OperationOption = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
// 状态
|
||||
const submitting = ref(false)
|
||||
const loadingModels = ref(false)
|
||||
@@ -358,6 +380,12 @@ const selectedNames = ref<string[]>([])
|
||||
// 选中的端点 ID;空数组表示全部端点
|
||||
const selectedEndpointIds = ref<string[]>([])
|
||||
|
||||
const selectedOperations = ref<string[]>([])
|
||||
|
||||
const operationOptions: OperationOption[] = [
|
||||
{ value: 'compact', label: '线程压缩' }
|
||||
]
|
||||
|
||||
// 自定义名称列表(手动添加的)
|
||||
const allCustomNames = ref<string[]>([])
|
||||
|
||||
@@ -391,6 +419,19 @@ const endpointScopeSummary = computed(() => {
|
||||
return `${selected.length} 个端点`
|
||||
})
|
||||
|
||||
const normalizedSelectedOperations = computed(() => {
|
||||
const selected = normalizeStringList(selectedOperations.value)
|
||||
return selected.length > 0 ? selected : undefined
|
||||
})
|
||||
|
||||
const operationScopeSummary = computed(() => {
|
||||
const selected = normalizedSelectedOperations.value
|
||||
if (!selected) return '全部操作'
|
||||
return selected.length === 1 && selected[0] === 'compact'
|
||||
? '线程压缩'
|
||||
: `${selected.length} 项操作`
|
||||
})
|
||||
|
||||
// 所有已知名称集合
|
||||
const allKnownNames = computed(() => {
|
||||
const set = new Set<string>()
|
||||
@@ -523,6 +564,7 @@ function findDuplicateNames(
|
||||
names: string[],
|
||||
endpointIds: string[] | undefined,
|
||||
apiFormats: string[] | undefined = undefined,
|
||||
operations: string[] | undefined = undefined,
|
||||
): string[] {
|
||||
const duplicates = new Set<string>()
|
||||
for (const rawName of names) {
|
||||
@@ -532,6 +574,7 @@ function findDuplicateNames(
|
||||
return alias.name === name
|
||||
&& scopesOverlap(alias.endpoint_ids, endpointIds)
|
||||
&& scopesOverlap(alias.api_formats, apiFormats)
|
||||
&& scopesOverlap(alias.operations, operations)
|
||||
})
|
||||
if (duplicate) duplicates.add(name)
|
||||
}
|
||||
@@ -597,6 +640,7 @@ function initForm() {
|
||||
const existingNames = props.editingGroup.aliases.map(a => a.name)
|
||||
selectedNames.value = [...existingNames]
|
||||
selectedEndpointIds.value = normalizeStringList(props.editingGroup.endpointIds)
|
||||
selectedOperations.value = normalizeStringList(props.editingGroup.operations)
|
||||
allCustomNames.value = [...existingNames]
|
||||
} else {
|
||||
formData.value = {
|
||||
@@ -604,6 +648,7 @@ function initForm() {
|
||||
}
|
||||
selectedNames.value = []
|
||||
selectedEndpointIds.value = []
|
||||
selectedOperations.value = []
|
||||
allCustomNames.value = []
|
||||
}
|
||||
searchQuery.value = ''
|
||||
@@ -626,6 +671,10 @@ function getEndpointIdsKey(endpointIds: string[] | undefined): string {
|
||||
return getScopeKey(endpointIds)
|
||||
}
|
||||
|
||||
function getOperationsKey(operations: string[] | undefined): string {
|
||||
return getScopeKey(operations)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function handleSubmit() {
|
||||
if (submitting.value) return
|
||||
@@ -642,6 +691,7 @@ async function handleSubmit() {
|
||||
const currentAliases = targetModel.provider_model_mappings || []
|
||||
let newAliases: ProviderModelAlias[]
|
||||
const nextEndpointIds = normalizedSelectedEndpointIds.value
|
||||
const nextOperations = normalizedSelectedOperations.value
|
||||
|
||||
const buildAliases = (names: string[]): ProviderModelAlias[] => {
|
||||
return names.map((name) => {
|
||||
@@ -652,6 +702,9 @@ async function handleSubmit() {
|
||||
if (nextEndpointIds && nextEndpointIds.length > 0) {
|
||||
alias.endpoint_ids = nextEndpointIds
|
||||
}
|
||||
if (nextOperations && nextOperations.length > 0) {
|
||||
alias.operations = nextOperations
|
||||
}
|
||||
return alias
|
||||
})
|
||||
}
|
||||
@@ -659,15 +712,26 @@ async function handleSubmit() {
|
||||
if (props.editingGroup) {
|
||||
const oldApiFormatsKey = props.editingGroup.apiFormatsKey
|
||||
const oldEndpointIdsKey = props.editingGroup.endpointIdsKey
|
||||
const oldOperationsKey = props.editingGroup.operationsKey
|
||||
const oldAliasNames = new Set(props.editingGroup.aliases.map(a => a.name))
|
||||
|
||||
const filteredAliases = currentAliases.filter((a: ProviderModelAlias) => {
|
||||
const currentKey = getApiFormatsKey(a.api_formats)
|
||||
const currentEndpointIdsKey = getEndpointIdsKey(a.endpoint_ids)
|
||||
return !(currentKey === oldApiFormatsKey && currentEndpointIdsKey === oldEndpointIdsKey && oldAliasNames.has(a.name))
|
||||
const currentOperationsKey = getOperationsKey(a.operations)
|
||||
return !(currentKey === oldApiFormatsKey
|
||||
&& currentEndpointIdsKey === oldEndpointIdsKey
|
||||
&& currentOperationsKey === oldOperationsKey
|
||||
&& oldAliasNames.has(a.name))
|
||||
})
|
||||
|
||||
const duplicates = findDuplicateNames(filteredAliases, selectedNames.value, nextEndpointIds)
|
||||
const duplicates = findDuplicateNames(
|
||||
filteredAliases,
|
||||
selectedNames.value,
|
||||
nextEndpointIds,
|
||||
undefined,
|
||||
nextOperations,
|
||||
)
|
||||
if (duplicates.length > 0) {
|
||||
showError(`以下映射名称已存在:${duplicates.join(', ')}`, '错误')
|
||||
return
|
||||
@@ -678,7 +742,13 @@ async function handleSubmit() {
|
||||
...buildAliases(selectedNames.value)
|
||||
]
|
||||
} else {
|
||||
const duplicates = findDuplicateNames(currentAliases, selectedNames.value, nextEndpointIds)
|
||||
const duplicates = findDuplicateNames(
|
||||
currentAliases,
|
||||
selectedNames.value,
|
||||
nextEndpointIds,
|
||||
undefined,
|
||||
nextOperations,
|
||||
)
|
||||
if (duplicates.length > 0) {
|
||||
showError(`以下映射名称已存在:${duplicates.join(', ')}`, '错误')
|
||||
return
|
||||
|
||||
@@ -75,6 +75,13 @@
|
||||
>
|
||||
{{ getEndpointScopeLabel(group) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="group.operations.length > 0"
|
||||
variant="outline"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ getOperationScopeLabel(group) }}
|
||||
</Badge>
|
||||
</div>
|
||||
<!-- 映射数量 -->
|
||||
<span class="text-xs text-muted-foreground shrink-0">
|
||||
@@ -130,6 +137,7 @@
|
||||
</div>
|
||||
<!-- 测试按钮 -->
|
||||
<Button
|
||||
v-if="group.operations.length === 0"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7 shrink-0"
|
||||
@@ -269,8 +277,12 @@ function getEndpointIdsKey(endpointIds: string[] | undefined): string {
|
||||
return getScopeKey(endpointIds)
|
||||
}
|
||||
|
||||
function getOperationsKey(operations: string[] | undefined): string {
|
||||
return getScopeKey(operations)
|
||||
}
|
||||
|
||||
function getAliasGroupKey(group: AliasGroup): string {
|
||||
return `${group.model.id}-${group.apiFormatsKey}-${group.endpointIdsKey}`
|
||||
return `${group.model.id}-${group.apiFormatsKey}-${group.endpointIdsKey}-${group.operationsKey}`
|
||||
}
|
||||
|
||||
function getEndpointScopeLabel(group: AliasGroup): string {
|
||||
@@ -278,6 +290,11 @@ function getEndpointScopeLabel(group: AliasGroup): string {
|
||||
return `${group.endpointIds.length} 端点`
|
||||
}
|
||||
|
||||
function getOperationScopeLabel(group: AliasGroup): string {
|
||||
if (group.operations.length === 1 && group.operations[0] === 'compact') return '压缩'
|
||||
return `${group.operations.length} 项操作`
|
||||
}
|
||||
|
||||
// 按"模型+作用域"分组的映射列表
|
||||
const aliasGroups = computed<AliasGroup[]>(() => {
|
||||
const groups: AliasGroup[] = []
|
||||
@@ -289,7 +306,8 @@ const aliasGroups = computed<AliasGroup[]>(() => {
|
||||
for (const alias of model.provider_model_mappings) {
|
||||
const apiFormatsKey = getApiFormatsKey(alias.api_formats)
|
||||
const endpointIdsKey = getEndpointIdsKey(alias.endpoint_ids)
|
||||
const groupKey = `${model.id}|${apiFormatsKey}|${endpointIdsKey}`
|
||||
const operationsKey = getOperationsKey(alias.operations)
|
||||
const groupKey = `${model.id}|${apiFormatsKey}|${endpointIdsKey}|${operationsKey}`
|
||||
|
||||
if (!groupMap.has(groupKey)) {
|
||||
const group: AliasGroup = {
|
||||
@@ -298,6 +316,8 @@ const aliasGroups = computed<AliasGroup[]>(() => {
|
||||
apiFormats: alias.api_formats || [],
|
||||
endpointIdsKey,
|
||||
endpointIds: normalizeStringList(alias.endpoint_ids),
|
||||
operationsKey,
|
||||
operations: normalizeStringList(alias.operations),
|
||||
aliases: []
|
||||
}
|
||||
groupMap.set(groupKey, group)
|
||||
@@ -317,6 +337,7 @@ const aliasGroups = computed<AliasGroup[]>(() => {
|
||||
if (nameA !== nameB) return nameA.localeCompare(nameB)
|
||||
return a.apiFormatsKey.localeCompare(b.apiFormatsKey)
|
||||
|| a.endpointIdsKey.localeCompare(b.endpointIdsKey)
|
||||
|| a.operationsKey.localeCompare(b.operationsKey)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -339,8 +360,9 @@ const deleteConfirmDescription = computed(() => {
|
||||
const modelName = model.global_model_display_name || model.provider_model_name
|
||||
const scopeText = apiFormats.length === 0 ? '全部' : apiFormats.map(f => formatApiFormat(f)).join(', ')
|
||||
const endpointScope = getEndpointScopeLabel(deletingGroup.value)
|
||||
const operationScope = getOperationScopeLabel(deletingGroup.value)
|
||||
const aliasNames = aliases.map(a => a.name).join(', ')
|
||||
return `确定要删除模型「${modelName}」在作用域「${scopeText} / ${endpointScope}」下的 ${aliases.length} 个映射吗?\n\n映射名称:${aliasNames}`
|
||||
return `确定要删除模型「${modelName}」在作用域「${scopeText} / ${endpointScope} / ${operationScope}」下的 ${aliases.length} 个映射吗?\n\n映射名称:${aliasNames}`
|
||||
})
|
||||
|
||||
// 切换映射组展开状态
|
||||
@@ -383,7 +405,7 @@ function deleteGroup(group: AliasGroup) {
|
||||
async function confirmDelete() {
|
||||
if (!deletingGroup.value) return
|
||||
|
||||
const { model, aliases, apiFormatsKey, endpointIdsKey } = deletingGroup.value
|
||||
const { model, aliases, apiFormatsKey, endpointIdsKey, operationsKey } = deletingGroup.value
|
||||
|
||||
try {
|
||||
const currentAliases = model.provider_model_mappings || []
|
||||
@@ -391,7 +413,11 @@ async function confirmDelete() {
|
||||
const newAliases = currentAliases.filter((a: ProviderModelAlias) => {
|
||||
const currentKey = getApiFormatsKey(a.api_formats)
|
||||
const currentEndpointIdsKey = getEndpointIdsKey(a.endpoint_ids)
|
||||
return !(currentKey === apiFormatsKey && currentEndpointIdsKey === endpointIdsKey && aliasNamesToRemove.has(a.name))
|
||||
const currentOperationsKey = getOperationsKey(a.operations)
|
||||
return !(currentKey === apiFormatsKey
|
||||
&& currentEndpointIdsKey === endpointIdsKey
|
||||
&& currentOperationsKey === operationsKey
|
||||
&& aliasNamesToRemove.has(a.name))
|
||||
})
|
||||
|
||||
await updateModel(props.provider.id, model.id, {
|
||||
|
||||
@@ -79,6 +79,13 @@
|
||||
>
|
||||
{{ getGroupEndpointScopeLabel(item.group) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="item.group && item.group.operations.length > 0"
|
||||
variant="outline"
|
||||
class="text-xs shrink-0"
|
||||
>
|
||||
{{ getGroupOperationScopeLabel(item.group) }}
|
||||
</Badge>
|
||||
</template>
|
||||
<!-- 正则映射 -->
|
||||
<template v-else>
|
||||
@@ -160,6 +167,7 @@
|
||||
</span>
|
||||
<!-- 测试按钮(直连测试) -->
|
||||
<Button
|
||||
v-if="!item.group || item.group.operations.length === 0"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7 shrink-0"
|
||||
@@ -492,11 +500,20 @@ function getEndpointIdsKey(endpointIds: string[] | undefined): string {
|
||||
return getScopeKey(endpointIds)
|
||||
}
|
||||
|
||||
function getOperationsKey(operations: string[] | undefined): string {
|
||||
return getScopeKey(operations)
|
||||
}
|
||||
|
||||
function getGroupEndpointScopeLabel(group: AliasGroup): string {
|
||||
if (!group.endpointIds || group.endpointIds.length === 0) return '全部端点'
|
||||
return `${group.endpointIds.length} 端点`
|
||||
}
|
||||
|
||||
function getGroupOperationScopeLabel(group: AliasGroup): string {
|
||||
if (group.operations.length === 1 && group.operations[0] === 'compact') return '压缩'
|
||||
return `${group.operations.length} 项操作`
|
||||
}
|
||||
|
||||
// 精确映射分组(来自 provider_model_mappings)
|
||||
const exactMappingGroups = computed<AliasGroup[]>(() => {
|
||||
const groups: AliasGroup[] = []
|
||||
@@ -508,7 +525,8 @@ const exactMappingGroups = computed<AliasGroup[]>(() => {
|
||||
for (const alias of model.provider_model_mappings) {
|
||||
const apiFormatsKey = getApiFormatsKey(alias.api_formats)
|
||||
const endpointIdsKey = getEndpointIdsKey(alias.endpoint_ids)
|
||||
const groupKey = `${model.id}|${apiFormatsKey}|${endpointIdsKey}`
|
||||
const operationsKey = getOperationsKey(alias.operations)
|
||||
const groupKey = `${model.id}|${apiFormatsKey}|${endpointIdsKey}|${operationsKey}`
|
||||
|
||||
if (!groupMap.has(groupKey)) {
|
||||
const group: AliasGroup = {
|
||||
@@ -517,6 +535,8 @@ const exactMappingGroups = computed<AliasGroup[]>(() => {
|
||||
apiFormats: alias.api_formats || [],
|
||||
endpointIdsKey,
|
||||
endpointIds: normalizeStringList(alias.endpoint_ids),
|
||||
operationsKey,
|
||||
operations: normalizeStringList(alias.operations),
|
||||
aliases: []
|
||||
}
|
||||
groupMap.set(groupKey, group)
|
||||
@@ -593,7 +613,7 @@ const combinedMappings = computed<CombinedMapping[]>(() => {
|
||||
// 添加精确映射
|
||||
for (const group of exactMappingGroups.value) {
|
||||
result.push({
|
||||
key: `exact-${group.model.id}-${group.apiFormatsKey}`,
|
||||
key: `exact-${group.model.id}-${group.apiFormatsKey}-${group.endpointIdsKey}-${group.operationsKey}`,
|
||||
type: 'exact',
|
||||
targetModelName: group.model.global_model_display_name || group.model.provider_model_name,
|
||||
targetModelId: group.model.id,
|
||||
@@ -638,7 +658,8 @@ const deleteConfirmDescription = computed(() => {
|
||||
const modelName = model.global_model_display_name || model.provider_model_name
|
||||
const aliasNames = aliases.map(a => a.name).join(', ')
|
||||
const endpointScope = getGroupEndpointScopeLabel(deletingGroup.value)
|
||||
return `确定要删除模型「${modelName}」在「${endpointScope}」下的 ${aliases.length} 个映射吗?\n\n映射名称:${aliasNames}`
|
||||
const operationScope = getGroupOperationScopeLabel(deletingGroup.value)
|
||||
return `确定要删除模型「${modelName}」在「${endpointScope} / ${operationScope}」下的 ${aliases.length} 个映射吗?\n\n映射名称:${aliasNames}`
|
||||
})
|
||||
|
||||
// 切换展开状态
|
||||
@@ -685,7 +706,7 @@ function deleteGroup(group: AliasGroup) {
|
||||
async function confirmDelete() {
|
||||
if (!deletingGroup.value) return
|
||||
|
||||
const { model, aliases, apiFormatsKey, endpointIdsKey } = deletingGroup.value
|
||||
const { model, aliases, apiFormatsKey, endpointIdsKey, operationsKey } = deletingGroup.value
|
||||
|
||||
try {
|
||||
const currentAliases = model.provider_model_mappings || []
|
||||
@@ -693,7 +714,11 @@ async function confirmDelete() {
|
||||
const newAliases = currentAliases.filter((a: ProviderModelAlias) => {
|
||||
const currentKey = getApiFormatsKey(a.api_formats)
|
||||
const currentEndpointIdsKey = getEndpointIdsKey(a.endpoint_ids)
|
||||
return !(currentKey === apiFormatsKey && currentEndpointIdsKey === endpointIdsKey && aliasNamesToRemove.has(a.name))
|
||||
const currentOperationsKey = getOperationsKey(a.operations)
|
||||
return !(currentKey === apiFormatsKey
|
||||
&& currentEndpointIdsKey === endpointIdsKey
|
||||
&& currentOperationsKey === operationsKey
|
||||
&& aliasNamesToRemove.has(a.name))
|
||||
})
|
||||
|
||||
await updateModel(props.provider.id, model.id, {
|
||||
|
||||
@@ -244,6 +244,14 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
<span class="min-w-0 truncate text-[15px] font-semibold leading-5">{{ record.model }}</span>
|
||||
<Badge
|
||||
v-if="getRequestTypeLabel(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full border-sky-500/30 bg-sky-500/5 px-1.5 text-[10px] leading-4 text-sky-700 dark:text-sky-300 flex-shrink-0"
|
||||
title="线程压缩"
|
||||
>
|
||||
{{ getRequestTypeLabel(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getReasoningEffort(record)"
|
||||
variant="outline"
|
||||
@@ -753,6 +761,14 @@
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<Badge
|
||||
v-if="getRequestTypeLabel(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full border-sky-500/30 bg-sky-500/5 px-1.5 text-[10px] leading-4 text-sky-700 dark:text-sky-300 flex-shrink-0"
|
||||
title="线程压缩"
|
||||
>
|
||||
{{ getRequestTypeLabel(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getReasoningEffort(record)"
|
||||
variant="outline"
|
||||
@@ -777,6 +793,14 @@
|
||||
class="flex min-w-0 items-center gap-1"
|
||||
>
|
||||
<span class="truncate">{{ record.model }}</span>
|
||||
<Badge
|
||||
v-if="getRequestTypeLabel(record)"
|
||||
variant="outline"
|
||||
class="h-4 rounded-full border-sky-500/30 bg-sky-500/5 px-1.5 text-[10px] leading-4 text-sky-700 dark:text-sky-300 flex-shrink-0"
|
||||
title="线程压缩"
|
||||
>
|
||||
{{ getRequestTypeLabel(record) }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="getReasoningEffort(record)"
|
||||
variant="outline"
|
||||
@@ -1605,6 +1629,10 @@ function getReasoningEffort(record: UsageRecord): string | null {
|
||||
return effort || null
|
||||
}
|
||||
|
||||
function getRequestTypeLabel(record: UsageRecord): string | null {
|
||||
return record.request_type?.trim().toLowerCase() === 'compact' ? '压缩' : null
|
||||
}
|
||||
|
||||
function getReasoningEffortTitle(record: UsageRecord): string {
|
||||
const effort = getReasoningEffort(record)
|
||||
return effort ? `Reasoning: ${effort}` : ''
|
||||
@@ -1644,8 +1672,9 @@ function getModelTooltip(record: UsageRecord): string {
|
||||
const actualModel = getActualModel(record)
|
||||
const reasoningEffort = getReasoningEffort(record)
|
||||
const serviceTierTitle = getFastBadgeTitle(record)
|
||||
const requestType = getRequestTypeLabel(record)
|
||||
const tierSuffix = serviceTierTitle ? `\n${serviceTierTitle}` : ''
|
||||
const suffix = `${reasoningEffort ? `\nReasoning: ${reasoningEffort}` : ''}${tierSuffix}`
|
||||
const suffix = `${requestType ? `\n操作: ${requestType}` : ''}${reasoningEffort ? `\nReasoning: ${reasoningEffort}` : ''}${tierSuffix}`
|
||||
if (actualModel) {
|
||||
return `${record.model} -> ${actualModel}${suffix}`
|
||||
}
|
||||
|
||||
@@ -297,6 +297,15 @@ describe('UsageRecordsTable', () => {
|
||||
expect(root.textContent).toContain('max')
|
||||
})
|
||||
|
||||
it('marks Responses compaction while the record is pending', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
status: 'pending',
|
||||
request_type: 'compact',
|
||||
})])
|
||||
|
||||
expect(root.textContent).toContain('压缩')
|
||||
})
|
||||
|
||||
it('shows fast badge for priority service tier', () => {
|
||||
const root = mountUsageRecordsTable([buildRecord({ service_tier: 'priority' })])
|
||||
|
||||
|
||||
@@ -216,6 +216,7 @@ describe('useUsageData', () => {
|
||||
has_format_conversion: false,
|
||||
has_retry: true,
|
||||
target_model: 'gpt-5.5',
|
||||
request_type: 'compact',
|
||||
reasoning_effort: 'xhigh',
|
||||
service_tier: 'auto',
|
||||
actual_service_tier: 'priority',
|
||||
@@ -245,6 +246,7 @@ describe('useUsageData', () => {
|
||||
has_format_conversion: undefined,
|
||||
has_retry: false,
|
||||
target_model: null,
|
||||
request_type: null,
|
||||
reasoning_effort: null,
|
||||
service_tier: null,
|
||||
actual_service_tier: null,
|
||||
@@ -279,6 +281,7 @@ describe('useUsageData', () => {
|
||||
has_format_conversion: false,
|
||||
has_retry: true,
|
||||
target_model: 'gpt-5.5',
|
||||
request_type: 'compact',
|
||||
reasoning_effort: 'xhigh',
|
||||
service_tier: 'auto',
|
||||
actual_service_tier: 'priority',
|
||||
|
||||
@@ -630,6 +630,7 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
provider_key_name: record.provider_key_name || existing.provider_key_name,
|
||||
rate_multiplier: record.rate_multiplier ?? existing.rate_multiplier,
|
||||
target_model: record.target_model ?? existing.target_model,
|
||||
request_type: record.request_type ?? existing.request_type,
|
||||
reasoning_effort: record.reasoning_effort ?? existing.reasoning_effort,
|
||||
service_tier: record.service_tier ?? existing.service_tier,
|
||||
actual_service_tier: record.actual_service_tier ?? existing.actual_service_tier
|
||||
|
||||
@@ -96,6 +96,7 @@ export interface UsageRecord {
|
||||
model: string
|
||||
target_model?: string | null // 映射后的目标模型名(若无映射则为空)
|
||||
model_version?: string | null // Provider 返回的实际模型版本(列表轻量字段)
|
||||
request_type?: string | null // 由请求语义识别出的操作类型
|
||||
reasoning_effort?: string | null // 从发送给 Provider 的请求体提取的 reasoning 级别
|
||||
service_tier?: string | null // 从发送给 Provider 的请求体提取的服务层级
|
||||
actual_service_tier?: string | null // Provider 响应确认的实际服务层级
|
||||
|
||||
@@ -558,6 +558,11 @@ async function pollActiveRequests() {
|
||||
if ('target_model' in update && (typeof update.target_model === 'string' || update.target_model === null)) {
|
||||
record.target_model = update.target_model
|
||||
}
|
||||
if ('request_type' in update) {
|
||||
record.request_type = typeof update.request_type === 'string'
|
||||
? update.request_type
|
||||
: null
|
||||
}
|
||||
if ('reasoning_effort' in update) {
|
||||
record.reasoning_effort = typeof update.reasoning_effort === 'string'
|
||||
? update.reasoning_effort
|
||||
|
||||
Reference in New Issue
Block a user