mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(frontend): highlight selected models missing upstream (#528)
* fix(frontend): highlight unavailable selected models * fix(frontend): refine model catalog warnings and refresh control Co-authored-by: Codex <noreply@openai.com> --------- Co-authored-by: Zyy <zyycn@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -195,6 +195,7 @@
|
||||
"searchOrEnterIdAndPressEnter": "Search or enter ID and press Enter to add",
|
||||
"selectModel": "Select model",
|
||||
"notInCurrentModelCatalog": "{id} (not in the current model catalog)",
|
||||
"modelCatalogMissingHint": "The current upstream catalog does not list this model, your selection has been kept",
|
||||
"loadingModel": "Loading model",
|
||||
"noMatchingModels": "No matching models",
|
||||
"noModels": "No models",
|
||||
|
||||
@@ -195,6 +195,7 @@
|
||||
"searchOrEnterIdAndPressEnter": "搜索或输入 ID,回车添加",
|
||||
"selectModel": "选择模型",
|
||||
"notInCurrentModelCatalog": "{id}(当前目录未返回)",
|
||||
"modelCatalogMissingHint": "当前上游目录未返回此模型,已保留你的选择",
|
||||
"loadingModel": "加载模型中",
|
||||
"noMatchingModels": "无匹配模型",
|
||||
"noModels": "暂无模型",
|
||||
|
||||
@@ -195,6 +195,7 @@
|
||||
"searchOrEnterIdAndPressEnter": "搜索或輸入 ID,回車添加",
|
||||
"selectModel": "選擇模型",
|
||||
"notInCurrentModelCatalog": "{id}(當前目錄未返回)",
|
||||
"modelCatalogMissingHint": "當前上游目錄未返回此模型,已保留你的選擇",
|
||||
"loadingModel": "加載模型中",
|
||||
"noMatchingModels": "無匹配模型",
|
||||
"noModels": "暫無模型",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { AccountModelAccess } from '@/api'
|
||||
|
||||
import { ZCheckbox, ZEmpty, ZFormItem, ZIconButton, ZInput, ZScrollbar, ZSegmented } from '@codex-proxy/ui'
|
||||
import { ZCheckbox, ZEmpty, ZFormItem, ZIconButton, ZInput, ZPopover, ZScrollbar, ZSegmented } from '@codex-proxy/ui'
|
||||
|
||||
import { RefreshCw, Search } from '@lucide/vue'
|
||||
import { RefreshCw, Search, TriangleAlert } from '@lucide/vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getAccountModels, refreshAccountModels } from '@/api'
|
||||
@@ -19,7 +19,8 @@ const props = withDefaults(defineProps<{
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const model = defineModel<AccountModelAccess | undefined>({ required: true })
|
||||
const catalog = ref<Array<{ id: string, label: string }>>([])
|
||||
// 未取得目录时不能判断缺失,成功返回空目录则可以
|
||||
const catalog = ref<Array<{ id: string, label: string }>>()
|
||||
const search = ref('')
|
||||
const inputError = ref('')
|
||||
const request = useRequestState()
|
||||
@@ -67,13 +68,14 @@ const mode = computed({
|
||||
const restricted = computed(() => mode.value === 'allowlist' || mode.value === 'denylist')
|
||||
const canAdd = computed(() => Boolean(search.value.trim()) && !model.value?.models.includes(search.value.trim()))
|
||||
const models = computed(() => {
|
||||
const entries = new Map(catalog.value.map(item => [
|
||||
const catalogReady = catalog.value !== undefined && !loading.value && !error.value
|
||||
const entries = new Map((catalog.value ?? []).map(item => [
|
||||
item.id,
|
||||
{ ...item, unavailable: false },
|
||||
]))
|
||||
for (const id of model.value?.models ?? []) {
|
||||
if (!entries.has(id))
|
||||
entries.set(id, { id, label: id, unavailable: true })
|
||||
entries.set(id, { id, label: id, unavailable: catalogReady })
|
||||
}
|
||||
const query = search.value.trim().toLowerCase()
|
||||
return [
|
||||
@@ -137,7 +139,7 @@ watch([
|
||||
restricted,
|
||||
], () => {
|
||||
request.invalidate()
|
||||
catalog.value = []
|
||||
catalog.value = undefined
|
||||
search.value = ''
|
||||
inputError.value = ''
|
||||
error.value = ''
|
||||
@@ -152,15 +154,15 @@ watch(search, () => {
|
||||
<template>
|
||||
<div class="grid gap-3">
|
||||
<ZFormItem :label="t('accounts.modelAccess.modelLimitations')">
|
||||
<template v-if="restricted && accountId" #extra>
|
||||
<ZIconButton :aria-label="t('accounts.modelAccess.refreshModel')" size="small" :loading="loading" :disabled="disabled" @click="load(true)">
|
||||
<div class="flex min-w-0 items-center justify-between gap-2">
|
||||
<ZSegmented v-model="mode" class="w-80 min-w-0 max-w-full" :aria-label="t('accounts.modelAccess.modelRestrictionMode')" :options="modes" :disabled="disabled" />
|
||||
<ZIconButton v-if="restricted && accountId" :aria-label="t('accounts.modelAccess.refreshModel')" variant="plain" :loading="loading" :disabled="disabled" @click="load(true)">
|
||||
<template #loading>
|
||||
<RefreshCw class="size-3.5 animate-spin motion-reduce:animate-none" />
|
||||
</template>
|
||||
<RefreshCw class="size-3.5" />
|
||||
</ZIconButton>
|
||||
</template>
|
||||
<ZSegmented v-model="mode" class="w-80 max-w-full" :aria-label="t('accounts.modelAccess.modelRestrictionMode')" :options="modes" :disabled="disabled" />
|
||||
</div>
|
||||
</ZFormItem>
|
||||
<template v-if="restricted">
|
||||
<ZInput v-model="search" :aria-label="t('accounts.modelAccess.searchOrAddModels')" :placeholder="t('accounts.modelAccess.searchOrEnterIdAndPressEnter')" :disabled="disabled" :aria-invalid="Boolean(inputError)" @keydown.enter.prevent="addModel">
|
||||
@@ -173,21 +175,47 @@ watch(search, () => {
|
||||
</p>
|
||||
<ZScrollbar v-if="models.length" max-height="15rem" class="min-w-0 -m-1">
|
||||
<div class="grid grid-cols-2 gap-2 p-1 sm:grid-cols-3" role="group" :aria-label="t('accounts.modelAccess.selectModel')" :aria-busy="loading || undefined">
|
||||
<ZCheckbox
|
||||
<div
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
class="min-h-11 min-w-0 rounded-cp px-3 py-2.5 transition-colors duration-150 motion-reduce:transition-none"
|
||||
class="flex min-w-0 items-center rounded-cp transition-colors duration-150 motion-reduce:transition-none"
|
||||
:class="model?.models.includes(item.id) ? 'bg-cp-primary-container text-cp-primary-on-container' : 'bg-cp-fill-quaternary text-cp-text-secondary hover:bg-cp-fill-tertiary hover:text-cp-text'"
|
||||
:model-value="model?.models.includes(item.id) ?? false"
|
||||
:label="item.id"
|
||||
:title="item.unavailable && accountId ? t('accounts.modelAccess.notInCurrentModelCatalog', { id: item.id }) : item.id"
|
||||
:disabled="disabled"
|
||||
@update:model-value="select(item.id, $event)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="block truncate">{{ item.id }}</span>
|
||||
</template>
|
||||
</ZCheckbox>
|
||||
<ZCheckbox
|
||||
class="min-h-11 min-w-0 flex-1 px-3 py-2.5"
|
||||
:model-value="model?.models.includes(item.id) ?? false"
|
||||
:label="item.id"
|
||||
:title="item.id"
|
||||
:disabled="disabled"
|
||||
@update:model-value="select(item.id, $event)"
|
||||
>
|
||||
<template #label>
|
||||
<span class="block truncate">{{ item.id }}</span>
|
||||
</template>
|
||||
</ZCheckbox>
|
||||
<ZPopover v-if="item.unavailable" class="mr-1 shrink-0" trigger="hover-click" placement="top" role="tooltip">
|
||||
<template #reference="{ open, id }">
|
||||
<ZIconButton
|
||||
class="bg-transparent! hover:bg-transparent! active:bg-transparent!"
|
||||
size="small"
|
||||
:aria-label="t('accounts.modelAccess.notInCurrentModelCatalog', { id: item.id })"
|
||||
:aria-describedby="open ? id : undefined"
|
||||
:aria-expanded="open"
|
||||
:title="undefined"
|
||||
>
|
||||
<TriangleAlert class="size-3.5 text-cp-warning-text" aria-hidden="true" />
|
||||
</ZIconButton>
|
||||
</template>
|
||||
<div class="max-w-64 space-y-1 px-3 py-2 text-cp-sm wrap-anywhere">
|
||||
<p class="m-0 font-emphasis text-cp-text">
|
||||
{{ item.id }}
|
||||
</p>
|
||||
<p class="m-0 text-cp-text-secondary">
|
||||
{{ t('accounts.modelAccess.modelCatalogMissingHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</ZPopover>
|
||||
</div>
|
||||
</div>
|
||||
</ZScrollbar>
|
||||
<p v-else-if="loading" class="m-0 py-4 text-center text-cp-sm text-cp-text-tertiary" role="status">
|
||||
|
||||
Reference in New Issue
Block a user