fix(ui): remove redundant failure messages and concurrency controls

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-10-18 23:31:12 +08:00
co-authored by Codex
parent d1aec7311c
commit a166580aed
16 changed files with 64 additions and 115 deletions
@@ -4,7 +4,7 @@ import type {
ClientProfilePreview,
ClientProfileSelection,
} from '@/api/modules/settings/profiles'
import { ZButton, ZSegmented } from '@codex-proxy/ui'
import { ZButton, ZEmpty, ZSegmented } from '@codex-proxy/ui'
import { computed, onMounted, shallowRef, watch } from 'vue'
import { getClientProfileOptions, previewClientProfile } from '@/api/modules/settings/profiles'
import { errorMessage } from '@/utils/operation'
@@ -107,14 +107,15 @@ onMounted(() => load())
/>
<slot name="source-extra" />
</div>
<div v-if="loadError" role="alert" class="flex flex-wrap items-center justify-between gap-3 text-cp text-cp-error">
<span>预设加载失败:{{ loadError }}</span>
<ZButton size="small" :disabled="disabled || loading" @click="load()">
重试
</ZButton>
</div>
<ZEmpty v-if="loadError" title="预设加载失败" :description="loadError" size="small" surface="none" role="alert">
<template #action>
<ZButton size="small" :disabled="disabled || loading" @click="load()">
重试
</ZButton>
</template>
</ZEmpty>
<ClientProfilePresetFields
v-if="!inherited"
v-if="!loadError && !inherited"
:model-value="effective ?? null"
:presets="options?.presets ?? []"
:preview="preview"
@@ -126,7 +127,7 @@ onMounted(() => load())
@update:model-value="model = $event"
/>
<ClientProfilePreviewPanel
v-if="inherited || !custom"
v-if="!loadError && (inherited || !custom)"
:preview="preview"
:previewing="previewing"
:needs-version-input="!!needsInput"
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ClientProfilePreview } from '@/api/modules/settings/profiles'
import { ZSkeleton } from '@codex-proxy/ui'
import { ZEmpty, ZSkeleton } from '@codex-proxy/ui'
defineProps<{
preview?: Pick<ClientProfilePreview, 'userAgent' | 'versionSource' | 'checkedAtDisplay' | 'error'> & { versionLag?: number | null }
@@ -14,9 +14,7 @@ defineProps<{
<p v-if="needsVersionInput" role="status" class="m-0 text-cp-sm text-cp-text-tertiary">
填写版本后预览
</p>
<p v-else-if="error && !previewing" role="alert" class="m-0 text-cp-sm text-cp-error">
{{ error }}
</p>
<ZEmpty v-else-if="error && !previewing" title="预览暂不可用" :description="error" size="small" surface="none" role="alert" />
<div
v-else-if="previewing || preview"
class="grid min-h-20 min-w-0 content-center gap-2 rounded-cp bg-cp-fill-quaternary p-4"
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { XaiClientProfilePreview, XaiClientProfileSelection } from '@/api/modules/settings/profiles'
import { ZButton, ZFormItem, ZInput, ZSegmented, ZSelect } from '@codex-proxy/ui'
import { ZButton, ZEmpty, ZFormItem, ZInput, ZSegmented, ZSelect } from '@codex-proxy/ui'
import { computed, onMounted, shallowRef, watch } from 'vue'
import { getXaiClientProfileOptions, previewXaiClientProfile } from '@/api/modules/settings/profiles'
import { errorMessage } from '@/utils/operation'
@@ -107,12 +107,13 @@ onMounted(() => props.allowInherit && void load())
/>
<slot name="source-extra" />
</div>
<div v-if="loadError" role="alert" class="flex items-center justify-between gap-3 text-cp text-cp-error">
<span>全局配置加载失败:{{ loadError }}</span>
<ZButton size="small" @click="load">
重试
</ZButton>
</div>
<ZEmpty v-if="loadError" title="全局配置加载失败" :description="loadError" size="small" surface="none" role="alert">
<template #action>
<ZButton size="small" @click="load">
重试
</ZButton>
</template>
</ZEmpty>
<template v-if="model">
<div class="grid gap-4 sm:grid-cols-2">
<ZFormItem label="客户端类型">
@@ -131,6 +132,6 @@ onMounted(() => props.allowInherit && void load())
</ZFormItem>
</div>
</template>
<ClientProfilePreviewPanel :preview="preview" :previewing="previewing" :needs-version-input="needsVersionInput" :error="previewError" />
<ClientProfilePreviewPanel v-if="!loadError" :preview="preview" :previewing="previewing" :needs-version-input="needsVersionInput" :error="previewError" />
</div>
</template>
@@ -1,38 +1,21 @@
<script setup lang="ts">
import type { Account } from '@/api'
import { ZDialog, ZIconButton } from '@codex-proxy/ui'
import { RefreshCw } from '@lucide/vue'
import { ZDialog } from '@codex-proxy/ui'
import { toRef } from 'vue'
import { useAccountConcurrency } from '../composables/useAccountConcurrency'
import AccountConcurrencyTable from './AccountConcurrencyTable.vue'
const props = defineProps<{ account: Account | null }>()
const open = defineModel<boolean>({ required: true })
const { data, entries, loading, error, refresh } = useAccountConcurrency(toRef(props, 'account'), open)
const { data, entries, loading, error } = useAccountConcurrency(toRef(props, 'account'), open)
</script>
<template>
<ZDialog v-model="open" title="并发明细" description="当前正在处理的请求" width="72rem">
<div class="grid min-w-0 gap-2">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center text-cp-sm" role="group" aria-label="当前占用 / 并发上限">
<span class="whitespace-nowrap font-mono tabular-nums">
<strong class="font-emphasis text-cp-text">{{ data?.total ?? account?.capacity.usedSlots ?? '—' }}</strong>
<span class="text-cp-text-tertiary"> / {{ account?.capacity.totalSlots ?? '不限' }}</span>
</span>
</div>
<ZIconButton
aria-label="刷新并发明细" title="每 5 秒自动刷新" variant="plain" size="small"
:disabled="loading" :aria-busy="loading" @click="refresh"
>
<RefreshCw class="size-4" :class="{ 'animate-spin motion-reduce:animate-none': loading }" />
</ZIconButton>
</div>
<AccountConcurrencyTable
:entries="entries"
:loading="loading && !data" :aria-busy="loading"
:empty-text="error ? '暂时无法读取并发明细' : '当前没有并发占用'"
/>
</div>
<AccountConcurrencyTable
:entries="entries"
:loading="loading && !data" :aria-busy="loading"
:empty-text="error ? '暂时无法读取并发明细' : '当前没有并发占用'"
/>
</ZDialog>
</template>
@@ -3,7 +3,7 @@ import type { ApiKeyAccountForm } from '../utils/upstreamApiKey'
import type { Account, AccountGroupRef, AccountModelAccess } from '@/api'
import { ZButton, ZDialog, ZFormItem, ZSegmented, ZTextarea } from '@codex-proxy/ui'
import { ZButton, ZDialog, ZEmpty, ZFormItem, ZSegmented, ZTextarea } from '@codex-proxy/ui'
import AccountPlanBadge from '@/components/account/AccountPlanBadge.vue'
import ProviderIconGroup from '@/components/ProviderIconGroup.vue'
import { isOpenAiApiKeyAccount, isOpenAiOAuthAccount } from '../utils/upstreamApiKey'
@@ -70,9 +70,7 @@ const selectedGroupIds = defineModel<string[]>('selectedGroupIds', { required: t
<p v-if="configurationLoading" role="status" class="m-0 text-cp-sm text-cp-text-secondary">
正在读取上游设置…
</p>
<p v-else-if="!configurationReady" role="alert" class="m-0 text-cp-sm text-cp-error">
传输方式读取失败,其他设置仍可保存
</p>
<ZEmpty v-else-if="!configurationReady" title="传输方式读取失败" description="其他设置仍可保存" size="small" surface="none" role="alert" />
<ZSegmented v-else v-model="oauthTransport" aria-label="上游传输方式" :options="transportOptions" :disabled="saving" />
</ZFormItem>
@@ -83,9 +81,7 @@ const selectedGroupIds = defineModel<string[]>('selectedGroupIds', { required: t
<p v-if="configurationLoading" role="status" class="m-0 text-cp-sm text-cp-text-secondary">
正在读取上游设置…
</p>
<p v-else-if="!configurationReady" role="alert" class="m-0 text-cp-sm text-cp-error">
上游设置读取失败,请关闭后重试
</p>
<ZEmpty v-else-if="!configurationReady" title="上游设置读取失败" description="请关闭后重试" size="small" surface="none" role="alert" />
<AccountApiKeyFields v-else v-model="apiKey" editing :disabled="saving" />
</section>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { AccountResetCreditAutomation } from '@/api'
import { ZButton, ZIconButton, ZPopover, ZSwitch } from '@codex-proxy/ui'
import { ZButton, ZEmpty, ZIconButton, ZPopover, ZSwitch } from '@codex-proxy/ui'
import { CircleHelp, Clock3 } from '@lucide/vue'
import { computed, shallowRef, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -107,12 +107,13 @@ watch(() => props.accountId, () => {
<p v-if="resultText" class="text-cp-xs text-cp-warning" role="status">
{{ resultText }}
</p>
<div v-if="loadError" class="flex items-center justify-between gap-2 text-cp-xs text-cp-error" role="alert">
<span>{{ loadError }}</span>
<ZButton size="small" variant="plain" :loading="loadAction.loading.value" @click="load">
重试
</ZButton>
</div>
<ZEmpty v-if="loadError" title="自动使用设置加载失败" :description="loadError" size="small" surface="none" role="alert">
<template #action>
<ZButton size="small" variant="plain" :loading="loadAction.loading.value" @click="load">
重试
</ZButton>
</template>
</ZEmpty>
</section>
</template>
@@ -192,13 +192,7 @@ function handleRequestConsume(creditId: string) {
</div>
<div :aria-busy="loading || consuming">
<p
v-if="loadError"
class="m-0 rounded-cp bg-cp-error-container px-4 py-3 text-cp-xs leading-normal font-emphasis text-cp-error-on-container"
role="status"
>
{{ loadError }},请刷新重试
</p>
<ZEmpty v-if="loadError" title="重置卡加载失败" description="请刷新重试" size="small" surface="none" role="alert" />
<p
v-else-if="loading && !hasSnapshot"
class="m-0 px-4 py-4 text-cp-xs font-emphasis text-cp-text-secondary"
@@ -6,7 +6,7 @@ import { getAccountConcurrency } from '@/api'
import { useRequestState } from '@/composables/useRequestState'
export function useAccountConcurrency(account: Ref<Account | null>, open: Ref<boolean>) {
const data = shallowRef<Pick<AccountConcurrencyPage, 'entries' | 'total'> | null>(null)
const data = shallowRef<Pick<AccountConcurrencyPage, 'entries'> | null>(null)
const receivedAt = shallowRef(0)
const now = shallowRef(performance.now())
const visibility = useDocumentVisibility()
@@ -60,7 +60,6 @@ export function useAccountConcurrency(account: Ref<Account | null>, open: Ref<bo
}
const snapshotAt = performance.now()
data.value = {
total: result.total,
entries: [...collected.values()].map(({ entry, receivedAt }) => ({
...entry,
occupiedMs: entry.occupiedMs === null ? null : entry.occupiedMs + snapshotAt - receivedAt,
@@ -95,5 +94,5 @@ export function useAccountConcurrency(account: Ref<Account | null>, open: Ref<bo
}, { immediate: true })
onScopeDispose(stop)
return { data, entries, loading: request.loading, error: request.error, refresh }
return { data, entries, loading: request.loading, error: request.error }
}
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { PluginManagementPage, PluginManagementView } from '@/api'
import { ZButton } from '@codex-proxy/ui'
import { ZButton, ZEmpty } from '@codex-proxy/ui'
import { RotateCw } from '@lucide/vue'
import { useEventListener, useResizeObserver } from '@vueuse/core'
import { computed, onMounted, shallowRef, useTemplateRef, watch } from 'vue'
@@ -60,22 +60,16 @@ onMounted(pushLayout)
<div v-if="!loadError && (loading || !frameReady)" class="absolute inset-0 z-10 flex bg-cp-bg-layout">
<PluginPageLoading />
</div>
<div v-if="loadError" class="grid min-h-0 flex-1 place-items-center p-6 text-center">
<div class="grid max-w-md justify-items-center gap-3">
<p class="m-0 text-cp font-heavy text-cp-text">
插件页面无法加载
</p>
<p class="m-0 text-xs leading-relaxed text-cp-text-secondary">
{{ loadError }}
</p>
<ZEmpty v-if="loadError" title="插件页面无法加载" :description="loadError" class="min-h-0 flex-1 content-center" surface="none" role="alert">
<template #action>
<ZButton size="small" @click="loadPage">
<template #icon>
<RotateCw class="size-3.5" />
</template>
重试
</ZButton>
</div>
</div>
</template>
</ZEmpty>
<iframe
v-else-if="srcdoc"
ref="iframe"
@@ -55,8 +55,6 @@ const manualLocationWarning = computed(() => {
const location = props.testResult?.location
if (location?.status === 'conflict')
return 'IPv4 与 IPv6 出口时区不一致,请手动填写'
if (location?.status === 'failed')
return location.message
return ''
})
const busy = computed(() => props.saving || props.testingConnection || props.detectingLocation || batchBusy.value)
@@ -103,10 +101,7 @@ function clearForm() {
<ZSwitch v-model="customLocation" label="自定义时区位置" :disabled="busy" />
<RequestLocationDetectButton :loading="detectingLocation" :disabled="busy" @detect="emit('detectLocation')" />
</div>
<p v-if="testResult?.success === false" class="m-0 text-cp-sm text-cp-error-text" role="alert">
连接测试失败:{{ testResult.message }}
</p>
<p v-else-if="manualLocationWarning" class="m-0 text-cp-sm text-cp-warning-text" role="status">
<p v-if="manualLocationWarning" class="m-0 text-cp-sm text-cp-warning-text" role="status">
{{ manualLocationWarning }}
</p>
<RequestLocationFields v-if="customLocation" v-model="location" :disabled="busy" />
@@ -6,10 +6,8 @@ import { Plus, Trash2 } from '@lucide/vue'
withDefaults(defineProps<{
mappings: Array<{ requestedModel: string, upstreamModel: string }>
loading?: boolean
error?: string
}>(), {
loading: false,
error: '',
})
const emit = defineEmits<{
@@ -32,7 +30,6 @@ const emit = defineEmits<{
</template>
添加映射
</ZButton>
<span v-if="error" class="text-xs font-emphasis text-cp-error-text">{{ error }}</span>
</div>
<div
@@ -101,14 +101,6 @@ function formatFileSize(value: number | null): string {
{{ downloads.warning }}
</p>
<p
v-if="error && downloads"
class="m-0 rounded-cp bg-cp-error-container px-3 py-2.5 text-cp-sm leading-[1.45] font-bold text-cp-error-on-container"
role="alert"
>
{{ error }},当前仍显示上次成功结果
</p>
<div v-if="loading && !downloads" class="grid gap-3 sm:grid-cols-2" aria-label="正在加载离线安装包">
<div v-for="index in 2" :key="index" class="grid gap-3 rounded-cp bg-cp-bg-container p-4 shadow-cp-tertiary">
<ZSkeleton class="h-4 w-28" />
@@ -10,7 +10,7 @@ const open = defineModel<boolean>({ required: true })
const sample = shallowRef('')
const localError = shallowRef('')
const preview = usePrivacyPreview()
const { loading, error, result } = preview
const { loading, result } = preview
const output = computed(() => result.value ? JSON.stringify({ body: result.value.body, headers: result.value.headers, turnMetadata: result.value.turnMetadata }, null, 2) : '')
watch(open, (value) => {
preview.invalidate()
@@ -52,8 +52,8 @@ async function test() {
</p>
</li>
</ol>
<p v-if="localError || error" role="alert" class="m-0 text-cp-sm text-cp-error-text">
{{ localError || error }}
<p v-if="localError" role="alert" class="m-0 text-cp-sm text-cp-error-text">
{{ localError }}
</p>
</div>
<template #footer>
@@ -25,7 +25,7 @@ const fixedValue = computed({ get: () => fixedValues.value[selectedIndex.value]
} })
const localError = shallowRef('')
const preview = usePrivacyPreview()
const { loading, error, result } = preview
const { loading, result } = preview
const processed = computed(() => result.value ? JSON.stringify({ body: result.value.body, headers: result.value.headers, turnMetadata: result.value.turnMetadata }, null, 2) : '')
const outcome = computed(() => result.value?.outcomes[0])
@@ -147,8 +147,8 @@ async function apply() {
<pre class="cp-scrollbar m-0 min-h-0 overflow-auto rounded-cp bg-cp-fill-quaternary p-3 font-mono text-cp-xs break-all whitespace-pre-wrap" data-preview-output>{{ processed || '点击测试查看结果' }}</pre>
</div>
</div>
<p v-if="localError || error || outcome?.reason" role="alert" class="m-0 shrink-0 text-cp-sm text-cp-error-text">
{{ localError || error || outcome?.reason }}
<p v-if="localError || outcome?.reason" role="alert" class="m-0 shrink-0 text-cp-sm text-cp-error-text">
{{ localError || outcome?.reason }}
</p>
</section>
</div>
@@ -9,7 +9,6 @@ export function usePrivacyPreview() {
function invalidate() {
state.invalidate()
result.value = undefined
state.error.value = ''
}
async function run(policy: CodexPrivacyPolicy, sample: PrivacySample) {
const id = state.start()
@@ -21,8 +20,8 @@ export function usePrivacyPreview() {
result.value = response
return response
}
catch (cause) { state.fail(id, cause) }
catch {}
finally { state.finish(id) }
}
return { loading: state.loading, error: state.error, result, run, invalidate }
return { loading: state.loading, result, run, invalidate }
}
+8 -9
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ZButton, ZIconButton, ZPageHeader, ZSegmented, ZSelect } from '@codex-proxy/ui'
import { ZButton, ZEmpty, ZIconButton, ZPageHeader, ZSegmented, ZSelect } from '@codex-proxy/ui'
import { Save, Undo2 } from '@lucide/vue'
import { computed, nextTick, reactive, shallowRef, watch } from 'vue'
@@ -138,14 +138,13 @@ watch(section, (value) => {
</div>
<div v-if="settingsVisited" v-show="isBasicSection" class="mt-5 grid w-full gap-5">
<div v-if="error" role="alert" class="flex flex-wrap items-center justify-between gap-3 rounded-cp-card bg-cp-error-container p-5 text-cp-error-on-container">
<p class="m-0 text-cp">
设置加载失败:{{ error }}
</p>
<ZButton :loading="loading" @click="loadSettings()">
重新加载
</ZButton>
</div>
<ZEmpty v-if="error" title="设置加载失败" role="alert">
<template #action>
<ZButton :loading="loading" @click="loadSettings()">
重新加载
</ZButton>
</template>
</ZEmpty>
<SettingsAccessSection
v-if="visited.has('access')"