mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
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:
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user