refactor: 重构异步任务系统和计费服务架构

- 重构任务系统:新增 lifecycle (TaskStatus/BillingStatus)、context、application 模块
- 将 video tasks 泛化为 async tasks,支持更通用的异步任务管理
- 新增 Gemini Files 管理模块和管理界面
- 重构 billing 服务:拆分 schema.py 和 service.py
- 新增 candidate 服务模块用于请求候选管理
- 数据库迁移:添加 billing_status、request_id、gemini_file_mappings 表和索引
- 移除废弃的 video_telemetry、task orchestrator 等模块
This commit is contained in:
fawney19
2026-02-02 03:16:52 +08:00
parent feb7484fda
commit 9e31efe26c
75 changed files with 7511 additions and 2068 deletions

View File

@@ -242,25 +242,41 @@
</form>
<template #footer>
<Button
variant="outline"
@click="$emit('update:open', false)"
>
取消
</Button>
<Button
:disabled="isSaving || !canSave"
@click="handleSave"
>
{{ isSaving ? '保存中...' : '保存' }}
</Button>
<Button
variant="outline"
:disabled="isVerifying || !canVerify"
@click="handleVerify"
>
{{ isVerifying ? '验证中...' : '验证' }}
</Button>
<div class="flex w-full items-center justify-between">
<!-- 左侧清除按钮仅在已有配置时显示 -->
<div>
<Button
v-if="hasExistingConfig"
variant="destructive"
:disabled="isClearing"
@click="handleClear"
>
{{ isClearing ? '清除中...' : '清除' }}
</Button>
</div>
<!-- 右侧验证保存取消按钮 -->
<div class="flex gap-2">
<Button
variant="outline"
:disabled="isVerifying || !canVerify"
@click="handleVerify"
>
{{ isVerifying ? '验证中...' : '验证' }}
</Button>
<Button
:disabled="isSaving || !canSave"
@click="handleSave"
>
{{ isSaving ? '保存中...' : '保存' }}
</Button>
<Button
variant="outline"
@click="$emit('update:open', false)"
>
取消
</Button>
</div>
</div>
</template>
</Dialog>
</template>
@@ -281,8 +297,9 @@ import {
SelectValue,
Switch,
} from '@/components/ui'
import { saveProviderOpsConfig, verifyProviderAuth, getProviderOpsConfig } from '@/api/providerOps'
import { saveProviderOpsConfig, verifyProviderAuth, getProviderOpsConfig, deleteProviderOpsConfig } from '@/api/providerOps'
import { useToast } from '@/composables/useToast'
import { useConfirm } from '@/composables/useConfirm'
import {
authTemplateRegistry,
type AuthTemplate,
@@ -305,11 +322,13 @@ const emit = defineEmits<{
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'session_cookie', 'token_cookie', 'auth_cookie', 'cookie_string', 'cookie', 'proxy_password'] as const
const { success: showSuccess, error: showError } = useToast()
const { confirmDanger } = useConfirm()
// State
const isSaving = ref(false)
const isVerifying = ref(false)
const isLoadingConfig = ref(false)
const isClearing = ref(false)
const verifyStatus = ref<'success' | 'error' | null>(null)
const formChanged = ref(false)
@@ -543,6 +562,40 @@ async function handleSave() {
}
}
async function handleClear() {
if (!props.providerId) return
const confirmed = await confirmDanger(
'确定要清除该提供商的认证配置吗?清除后将无法进行余额查询、签到等操作。',
'清除认证',
'清除'
)
if (!confirmed) return
isClearing.value = true
try {
const result = await deleteProviderOpsConfig(props.providerId)
if (result.success) {
showSuccess(result.message || '认证信息已清除', '清除成功')
// 重置状态
hasExistingConfig.value = false
sensitivePlaceholders.value = {}
verifyStatus.value = null
formChanged.value = false
selectedTemplateId.value = 'new_api'
resetFormData()
emit('saved')
emit('update:open', false)
} else {
showError(result.message || '清除失败')
}
} catch (error: any) {
showError(error.response?.data?.detail || error.message, '清除失败')
} finally {
isClearing.value = false
}
}
function loadFromConfig(config: any) {
if (!config?.connector) return

View File

@@ -55,6 +55,15 @@
</div>
</div>
<div class="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="icon"
:title="provider.enable_format_conversion ? '已启用格式转换(点击关闭)' : '启用格式转换'"
:class="provider.enable_format_conversion ? 'text-primary' : ''"
@click="toggleFormatConversion"
>
<Shuffle class="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
@@ -502,7 +511,8 @@ import {
Power,
GripVertical,
Copy,
Shield
Shield,
Shuffle
} from 'lucide-vue-next'
import { useEscapeKey } from '@/composables/useEscapeKey'
import Button from '@/components/ui/button.vue'
@@ -511,7 +521,7 @@ import Card from '@/components/ui/card.vue'
import { useToast } from '@/composables/useToast'
import { useClipboard } from '@/composables/useClipboard'
import { useCountdownTimer, formatCountdown } from '@/composables/useCountdownTimer'
import { getProvider, getProviderEndpoints } from '@/api/endpoints'
import { getProvider, getProviderEndpoints, updateProvider } from '@/api/endpoints'
import {
KeyFormDialog,
KeyAllowedModelsEditDialog,
@@ -702,6 +712,20 @@ function handleClose() {
}
}
// 切换格式转换开关
async function toggleFormatConversion() {
if (!provider.value) return
const newValue = !provider.value.enable_format_conversion
try {
await updateProvider(provider.value.id, { enable_format_conversion: newValue })
provider.value.enable_format_conversion = newValue
showSuccess(newValue ? '已启用格式转换' : '已禁用格式转换')
emit('refresh')
} catch {
showError('切换格式转换失败')
}
}
// 显示端点管理对话框
function showAddEndpointDialog() {
endpointDialogOpen.value = true