refactor(frontend): phase system-update state and clear remaining any

system-update now uses a discriminated phase union so mutually exclusive
operation states cannot be expressed together; connection-test event shapes
and cost chart points are typed instead of any.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-08-02 01:25:53 +08:00
co-authored by Codex
parent e79d57eaf9
commit 365d389a14
4 changed files with 79 additions and 28 deletions
+11 -2
View File
@@ -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<AccountListResponse>({
@@ -217,7 +226,7 @@ export function getAccounts(data: AccountListParams) {
})
}
export function exportAccounts(data: AccountListParams) {
export function exportAccounts(data: AccountExportParam) {
return request<unknown>({
url: '/api/admin/accounts/export',
method: 'GET',
+47 -18
View File
@@ -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<SystemUpdatePhase['kind']>([
'loading',
'checking',
'updating',
'restarting',
])
export const useSystemUpdateStore = defineStore('system-update', () => {
const version = shallowRef<Awaited<ReturnType<typeof getSystemVersion>> | null>(null)
const updateInfo = shallowRef<Awaited<ReturnType<typeof getSystemUpdateDetail>> | null>(null)
const loading = shallowRef(false)
const checking = shallowRef(false)
const updating = shallowRef(false)
const restarting = shallowRef(false)
const phase = shallowRef<SystemUpdatePhase>({ 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<typeof getSystemVersion> | undefined
let loadSystemPromise: Promise<void> | 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,
@@ -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<any>(null)
const testingAccount = shallowRef<Account | null>(null)
const connectionTestStatus = shallowRef<ConnectionTestStatus>('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 ?? []) {
@@ -41,7 +41,7 @@ const props = withDefaults(
const activeView = shallowRef('cost')
const { palette } = useUsageChartPalette()
const points = computed<any[]>(() => 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,
)
})