mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
fix(ui): remove redundant failure messages and concurrency controls
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
+8
-7
@@ -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
|
||||
|
||||
-8
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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')"
|
||||
|
||||
Reference in New Issue
Block a user