diff --git a/frontend/src/api/modules/accounts.ts b/frontend/src/api/modules/accounts.ts index 93032278d..60bad13b4 100644 --- a/frontend/src/api/modules/accounts.ts +++ b/frontend/src/api/modules/accounts.ts @@ -207,7 +207,16 @@ interface AccountOAuthStartParam { accountId?: string } -interface AccountOAuthCompleteParam { flowId: string, callbackUrl: string } +interface AccountOAuthCompleteParam { + provider: string + flowId: string + callbackUrl: string +} + +interface AccountExportParam { + accountIds: string + confirm: string +} export function getAccounts(data: AccountListParams) { return request({ @@ -217,7 +226,7 @@ export function getAccounts(data: AccountListParams) { }) } -export function exportAccounts(data: AccountListParams) { +export function exportAccounts(data: AccountExportParam) { return request({ url: '/api/admin/accounts/export', method: 'GET', diff --git a/frontend/src/stores/modules/system-update.ts b/frontend/src/stores/modules/system-update.ts index 4484cd37a..70902e9d5 100644 --- a/frontend/src/stores/modules/system-update.ts +++ b/frontend/src/stores/modules/system-update.ts @@ -24,16 +24,32 @@ interface SystemUpdateEvent { level: string message: string at: string + step?: string terminal?: boolean } +// 单一更新阶段状态机:互斥的操作态无法被类型同时表达。 +type SystemUpdatePhase + = | { kind: 'idle' } + | { kind: 'loading' } + | { kind: 'checking' } + | { kind: 'ready' } + | { kind: 'updating' } + | { kind: 'restart_required' } + | { kind: 'restarting' } + | { kind: 'failed' } + +const UPDATE_BUSY_PHASES = new Set([ + 'loading', + 'checking', + 'updating', + 'restarting', +]) + export const useSystemUpdateStore = defineStore('system-update', () => { const version = shallowRef> | null>(null) const updateInfo = shallowRef> | null>(null) - const loading = shallowRef(false) - const checking = shallowRef(false) - const updating = shallowRef(false) - const restarting = shallowRef(false) + const phase = shallowRef({ kind: 'idle' }) const updateError = shallowRef('') const updateSuccess = shallowRef(false) const needRestart = shallowRef(false) @@ -61,6 +77,12 @@ export const useSystemUpdateStore = defineStore('system-update', () => { let loadVersionPromise: ReturnType | undefined let loadSystemPromise: Promise | undefined + const phaseKind = computed(() => phase.value.kind) + const loading = computed(() => phaseKind.value === 'loading') + const checking = computed(() => phaseKind.value === 'checking') + const updating = computed(() => phaseKind.value === 'updating') + const restarting = computed(() => phaseKind.value === 'restarting') + const hasUpdate = computed(() => Boolean(updateInfo.value?.hasUpdate ?? version.value?.hasUpdate)) const isReleaseBuild = computed(() => updateInfo.value?.buildType === 'release') const canUpdate = computed( @@ -68,11 +90,13 @@ export const useSystemUpdateStore = defineStore('system-update', () => { hasUpdate.value && isReleaseBuild.value && Boolean(updateInfo.value?.updateSupported) - && !updating.value - && !checking.value - && !restarting.value, + && !UPDATE_BUSY_PHASES.has(phaseKind.value), ) + function setPhase(next: SystemUpdatePhase) { + phase.value = next + } + function resetUpdateResult() { updateError.value = '' updateSuccess.value = false @@ -149,7 +173,9 @@ export const useSystemUpdateStore = defineStore('system-update', () => { if (loadSystemPromise) return loadSystemPromise - loading.value = true + if (!UPDATE_BUSY_PHASES.has(phaseKind.value)) { + setPhase({ kind: 'loading' }) + } loadSystemPromise = (async () => { updateInfo.value = await getSystemUpdateDetail({ refresh }) if (!version.value) @@ -161,7 +187,8 @@ export const useSystemUpdateStore = defineStore('system-update', () => { await loadSystemPromise } finally { - loading.value = false + if (phaseKind.value === 'loading') + setPhase({ kind: 'ready' }) loadSystemPromise = undefined } } @@ -185,7 +212,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { if (checking.value) return updateInfo.value - checking.value = true + setPhase({ kind: 'checking' }) resetUpdateResult() try { updateInfo.value = await getSystemUpdateDetail({ refresh }) @@ -195,7 +222,8 @@ export const useSystemUpdateStore = defineStore('system-update', () => { return updateInfo.value } finally { - checking.value = false + if (phaseKind.value === 'checking') + setPhase({ kind: 'ready' }) } } @@ -207,7 +235,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { clearUpdateLogs() await connectUpdateEvents(true) - updating.value = true + setPhase({ kind: 'updating' }) updateError.value = '' updateSuccess.value = false try { @@ -220,6 +248,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { latestVersion: result.targetVersion, hasUpdate: false, } + setPhase(result.needRestart ? { kind: 'restart_required' } : { kind: 'ready' }) return result } catch (error: unknown) { @@ -230,11 +259,9 @@ export const useSystemUpdateStore = defineStore('system-update', () => { message: updateError.value, at: new Date().toISOString(), }) + setPhase({ kind: 'failed' }) throw error } - finally { - updating.value = false - } } async function waitForServiceAndReload() { @@ -260,7 +287,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { } updateError.value = `服务未在预期时间内启动 v${expectedVersion}` - restarting.value = false + setPhase({ kind: 'failed' }) } async function restartNow() { @@ -270,10 +297,11 @@ export const useSystemUpdateStore = defineStore('system-update', () => { if (!restartTargetVersion.value) { const error = new Error('缺少待生效的目标版本') updateError.value = error.message + setPhase({ kind: 'failed' }) throw error } - restarting.value = true + setPhase({ kind: 'restarting' }) updateError.value = '' disconnectUpdateEvents() @@ -282,7 +310,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { } catch (error: unknown) { if (error instanceof ApiError && error.status > 0) { - restarting.value = false + setPhase({ kind: 'failed' }) updateError.value = errorMessage(error, '重启失败') throw error } @@ -294,6 +322,7 @@ export const useSystemUpdateStore = defineStore('system-update', () => { return { version, updateInfo, + phase, loading, checking, updating, diff --git a/frontend/src/views/accounts/composables/useAccountConnectionTest.ts b/frontend/src/views/accounts/composables/useAccountConnectionTest.ts index 83c7ffda6..b898e7109 100644 --- a/frontend/src/views/accounts/composables/useAccountConnectionTest.ts +++ b/frontend/src/views/accounts/composables/useAccountConnectionTest.ts @@ -1,6 +1,7 @@ +import type { Account, AccountModelsResponse } from '@/api' import { CheckCircle2, Clock3, Wifi, XCircle } from '@lucide/vue' -import { useEventSource } from '@vueuse/core' +import { useEventSource } from '@vueuse/core' import { clamp } from 'es-toolkit' import { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue' import { getAccountModels, refreshAccountModels } from '@/api' @@ -35,11 +36,23 @@ interface ConnectionTestRequestPayload { input?: Array<{ content?: Array<{ type?: string, text?: string }> }> } +interface ConnectionTestEvent { + type: string + text?: string + model?: string + payload?: ConnectionTestRequestPayload + success?: boolean + accountStatus?: string + upstreamBody?: string + upstreamStatus?: number + error?: string +} + interface AccountConnectionTestOptions { onAccountStatus: (accountId: string, status: string) => void } -function connectionTestErrorText(event: any) { +function connectionTestErrorText(event: ConnectionTestEvent) { if (typeof event.upstreamBody === 'string' && event.upstreamBody.length > 0) return event.upstreamBody if (Number.isInteger(event.upstreamStatus)) @@ -49,7 +62,7 @@ function connectionTestErrorText(event: any) { export function useAccountConnectionTest(options: AccountConnectionTestOptions) { const showConnectionTestModal = shallowRef(false) - const testingAccount = shallowRef(null) + const testingAccount = shallowRef(null) const connectionTestStatus = shallowRef('idle') const connectionTestModel = shallowRef('') const connectionTestContent = shallowRef('') @@ -119,7 +132,7 @@ export function useAccountConnectionTest(options: AccountConnectionTestOptions) } }) - function openConnectionTest(account: any) { + function openConnectionTest(account: Account) { abortConnectionTest() testingAccount.value = account connectionTestSelectedModel.value = '' @@ -241,7 +254,7 @@ export function useAccountConnectionTest(options: AccountConnectionTestOptions) clearConnectionTestRun() } - function handleConnectionTestEvent(event: any) { + function handleConnectionTestEvent(event: ConnectionTestEvent) { if (event.type === 'test_start') { connectionTestModel.value = event.model || connectionTestModel.value appendConnectionTestLog(`开始测试 ${connectionTestModel.value || '未选择模型'}`, 'info') @@ -312,7 +325,7 @@ export function useAccountConnectionTest(options: AccountConnectionTestOptions) } } - function applyConnectionTestModels(result: any, preserveSelection = false) { + function applyConnectionTestModels(result: AccountModelsResponse, preserveSelection = false) { const previousSelection = preserveSelection ? connectionTestSelectedModel.value : '' connectionTestModelOptions.value = [] for (const model of result.models ?? []) { diff --git a/frontend/src/views/usage/components/UsageCostCard.vue b/frontend/src/views/usage/components/UsageCostCard.vue index 035d848a9..a531c7aea 100644 --- a/frontend/src/views/usage/components/UsageCostCard.vue +++ b/frontend/src/views/usage/components/UsageCostCard.vue @@ -41,7 +41,7 @@ const props = withDefaults( const activeView = shallowRef('cost') const { palette } = useUsageChartPalette() -const points = computed(() => props.cost.points) +const points = computed(() => props.cost.points) const hasStandardCost = computed(() => points.value.some(point => point.standardCost != null), ) @@ -62,7 +62,7 @@ const hasData = computed(() => { return points.value.some(point => point.totalTokens > 0 || point.cachedTokens > 0) } return points.value.some( - point => point.inputTokens > 0 || point.cachedTokenRate > 0 || point.cacheHitRequestRate > 0, + point => point.inputTokens > 0 || point.cachedTokenRate > 0 || (point.cacheHitRequestRate ?? 0) > 0, ) })