refactor: 前端全面替换 any 为 unknown 并统一错误处理,后端用量记录补写请求头/体

- 前端 API 层、stores、conversation 解析器、组件全面替换 any 为 unknown/具体类型
- 错误处理统一使用 parseApiError/getErrorStatus 替代 err.response?.data?.detail 模式
- 后端 handler/TaskService/UsageLifecycle/StreamTracker 链路传递 request_headers/request_body
- streaming/pending 状态更新时可补写客户端和提供商的请求头及请求体
- 新增 TaskService 和 UsageService 相关测试
This commit is contained in:
fawney19
2026-02-22 00:43:41 +08:00
parent 98e60e8f74
commit cf2eee222e
121 changed files with 1652 additions and 1179 deletions

View File

@@ -527,10 +527,12 @@
<span>{{ formatFullDate(viewingAnnouncement.created_at) }}</span>
</div>
<!-- eslint-disable vue/no-v-html -->
<div
class="prose prose-sm dark:prose-invert max-w-none"
v-html="renderMarkdown(viewingAnnouncement.content)"
/>
<!-- eslint-enable vue/no-v-html -->
</div>
<template #footer>

View File

@@ -545,6 +545,7 @@ import {
} from 'lucide-vue-next'
import { useToast } from '@/composables/useToast'
import { log } from '@/utils/logger'
import { parseApiError } from '@/utils/errorParser'
const { success, error: showError } = useToast()
@@ -640,13 +641,9 @@ async function loadTokens() {
if (tokens.value.length === 0 && currentPage.value > 1) {
currentPage.value = 1
}
} catch (err: any) {
} catch (err: unknown) {
log.error('加载 Management Tokens 失败:', err)
if (!err.response) {
showError('无法连接到服务器')
} else {
showError(`加载失败:${err.response?.data?.detail || err.message}`)
}
showError(parseApiError(err, '加载失败'))
} finally {
loading.value = false
}
@@ -717,12 +714,9 @@ async function saveToken() {
closeDialog()
await loadTokens()
} catch (err: any) {
} catch (err: unknown) {
log.error('保存 Token 失败:', err)
const message = err.response?.data?.error?.message
|| err.response?.data?.detail
|| '保存失败'
showError(message)
showError(parseApiError(err, '保存失败'))
} finally {
saving.value = false
}
@@ -738,7 +732,7 @@ async function toggleToken(token: ManagementToken) {
tokens.value[index] = result.data
}
success(result.data.is_active ? '令牌已启用' : '令牌已禁用')
} catch (err: any) {
} catch (err: unknown) {
log.error('切换状态失败:', err)
showError('操作失败')
}
@@ -760,7 +754,7 @@ async function deleteToken() {
showDeleteDialog.value = false
success('令牌已删除')
await loadTokens()
} catch (err: any) {
} catch (err: unknown) {
log.error('删除 Token 失败:', err)
showError('删除失败')
} finally {
@@ -787,7 +781,7 @@ async function regenerateToken() {
showTokenDialog.value = true
await loadTokens()
success('令牌已重新生成')
} catch (err: any) {
} catch (err: unknown) {
log.error('重新生成失败:', err)
showError('重新生成失败')
} finally {

View File

@@ -292,6 +292,7 @@ import {
import UserModelDetailDrawer from './components/UserModelDetailDrawer.vue'
import { useRowClick } from '@/composables/useRowClick'
import { log } from '@/utils/logger'
import { parseApiError } from '@/utils/errorParser'
const { error: showError } = useToast()
const { copyToClipboard } = useClipboard()
@@ -440,9 +441,9 @@ async function loadModels() {
// 使用用户认证端点,只获取用户有权限使用的模型
const response = await meApi.getAvailableModels({ limit: 1000 })
models.value = (response.models || []) as PublicGlobalModel[]
} catch (err: any) {
} catch (err: unknown) {
log.error('加载模型失败:', err)
showError(err.response?.data?.detail || err.message, '加载模型失败')
showError(parseApiError(err, ''), '加载模型失败')
} finally {
loading.value = false
}

View File

@@ -498,6 +498,8 @@ import RefreshButton from '@/components/ui/refresh-button.vue'
import { Plus, Key, Copy, Trash2, Loader2, Activity, CheckCircle, Power, Check } from 'lucide-vue-next'
import { useToast } from '@/composables/useToast'
import { log } from '@/utils/logger'
import { parseApiError } from '@/utils/errorParser'
import { getErrorStatus } from '@/types/api-error'
import { computed } from 'vue'
const { success, error: showError } = useToast()
@@ -548,14 +550,15 @@ async function loadApiKeys() {
loading.value = true
try {
apiKeys.value = await meApi.getApiKeys()
} catch (error: any) {
} catch (error: unknown) {
log.error('加载 API 密钥失败:', error)
if (!error.response) {
const status = getErrorStatus(error)
if (status === undefined) {
showError('无法连接到服务器,请检查后端服务是否运行')
} else if (error.response.status === 401) {
} else if (status === 401) {
showError('认证失败,请重新登录')
} else {
showError(`加载 API 密钥失败:${ error.response?.data?.detail || error.message}`)
showError(parseApiError(error, '加载 API 密钥失败'))
}
} finally {
loading.value = false
@@ -596,7 +599,7 @@ async function deleteApiKey() {
deleting.value = true
try {
await meApi.deleteApiKey(keyToDelete.value.id)
apiKeys.value = apiKeys.value.filter(k => k.id !== keyToDelete.value!.id)
apiKeys.value = apiKeys.value.filter(k => k.id !== keyToDelete.value?.id)
showDeleteDialog.value = false
success('API 密钥已删除')
} catch (error) {

View File

@@ -174,10 +174,12 @@
class="flex items-center justify-between gap-3 rounded-lg border border-border bg-muted/30 p-4"
>
<div class="flex items-center gap-3 min-w-0 flex-1">
<!-- eslint-disable vue/no-v-html -->
<div
class="oauth-icon shrink-0"
v-html="getOAuthIcon(link.provider_type)"
/>
<!-- eslint-enable vue/no-v-html -->
<div class="min-w-0">
<div class="text-sm font-medium truncate">
{{ link.display_name }}
@@ -204,10 +206,12 @@
class="flex items-center justify-between gap-3 rounded-lg border border-dashed border-border p-4 hover:border-primary/50 transition-colors"
>
<div class="flex items-center gap-3 min-w-0 flex-1">
<!-- eslint-disable vue/no-v-html -->
<div
class="oauth-icon shrink-0"
v-html="getOAuthIcon(p.provider_type)"
/>
<!-- eslint-enable vue/no-v-html -->
<div class="min-w-0">
<div class="text-sm font-medium truncate">
{{ p.display_name }}
@@ -462,7 +466,7 @@ import { useToast } from '@/composables/useToast'
import { formatCurrency } from '@/utils/format'
import { getApiUrl } from '@/utils/url'
import { log } from '@/utils/logger'
import { getErrorMessage } from '@/types/api-error'
import { getErrorMessage, getErrorStatus } from '@/types/api-error'
const authStore = useAuthStore()
const route = useRoute()
@@ -601,8 +605,8 @@ async function loadOAuthBindings() {
])
oauthLinks.value = links
bindableProviders.value = providers
} catch (err: any) {
if (err?.response?.status === 503) {
} catch (err: unknown) {
if (getErrorStatus(err) === 503) {
oauthUnavailable.value = true
return
}

View File

@@ -388,7 +388,7 @@ interface Props {
function getModelUserConfigurableCapabilities(): CapabilityDefinition[] {
if (!props.model?.supported_capabilities || !props.userConfigurableCapabilities) return []
return props.userConfigurableCapabilities.filter(cap =>
props.model!.supported_capabilities!.includes(cap.name)
props.model?.supported_capabilities?.includes(cap.name)
)
}