fix(groups): react immediately to Codex manifest edits and unify dialog toggles

This commit is contained in:
Zero Clover
2026-09-05 19:46:25 +08:00
parent ab99d56e96
commit f3bbb95310
3 changed files with 127 additions and 471 deletions
@@ -9,25 +9,12 @@
{{ t("admin.groups.codexModelsManifest.hint") }}
</p>
</div>
<button
type="button"
<Toggle
data-testid="codex-manifest-toggle"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full transition-colors',
config.enabled
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
:aria-label="t('admin.groups.codexModelsManifest.enable')"
@click="emitUpdate({ enabled: !config.enabled })"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
config.enabled ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
:model-value="config.enabled"
@update:model-value="emitUpdate({ enabled: $event })"
/>
</div>
<div v-if="config.enabled">
@@ -115,31 +102,12 @@
{{ t("admin.groups.codexModelsManifest.fallbackHint") }}
</p>
</div>
<button
type="button"
<Toggle
data-testid="codex-manifest-fallback-toggle"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full transition-colors',
config.fallback_to_scheduler
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
:aria-label="t('admin.groups.codexModelsManifest.fallback')"
@click="
emitUpdate({
fallback_to_scheduler: !config.fallback_to_scheduler,
})
"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
config.fallback_to_scheduler
? 'translate-x-6'
: 'translate-x-1',
]"
/>
</button>
:model-value="config.fallback_to_scheduler"
@update:model-value="emitUpdate({ fallback_to_scheduler: $event })"
/>
</div>
<p
@@ -161,6 +129,7 @@
import { computed, onMounted, onUnmounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import Icon from "@/components/icons/Icon.vue";
import Toggle from "@/components/common/Toggle.vue";
import { useKeyedDebouncedSearch } from "@/composables/useKeyedDebouncedSearch";
import { adminAPI } from "@/api/admin";
import type { CodexModelsManifestConfig } from "@/types";
+51 -431
View File
@@ -682,23 +682,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="createForm.is_exclusive = !createForm.is_exclusive"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.is_exclusive
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="createForm.is_exclusive" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
createForm.is_exclusive
@@ -781,23 +765,7 @@
{{ t("admin.groups.modelsList.hint", { endpoint: modelsListEndpoint(createForm.platform) }) }}
</p>
</div>
<button
type="button"
@click="createModelsListState.enabled = !createModelsListState.enabled"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full transition-colors',
createModelsListState.enabled
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createModelsListState.enabled ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="createModelsListState.enabled" />
</div>
<div
v-if="createModelsListState.enabled"
@@ -1362,23 +1330,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="createForm.mcp_xml_inject = !createForm.mcp_xml_inject"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.mcp_xml_inject
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.mcp_xml_inject ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="createForm.mcp_xml_inject" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
createForm.mcp_xml_inject
@@ -1420,27 +1372,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="
createForm.claude_code_only = !createForm.claude_code_only
"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.claude_code_only
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.claude_code_only
? 'translate-x-6'
: 'translate-x-1',
]"
/>
</button>
<Toggle v-model="createForm.claude_code_only" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
createForm.claude_code_only
@@ -1594,27 +1526,11 @@
<label class="text-sm text-gray-600 dark:text-gray-400">
{{ t("admin.groups.openaiFast.force") }}
</label>
<button
type="button"
role="switch"
:aria-checked="createForm.force_openai_fast"
:aria-label="t('admin.groups.openaiFast.force')"
<Toggle
data-testid="create-force-openai-fast"
@click="createForm.force_openai_fast = !createForm.force_openai_fast"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.force_openai_fast
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.force_openai_fast ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
:aria-label="t('admin.groups.openaiFast.force')"
v-model="createForm.force_openai_fast"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiFast.hint") }}
@@ -1623,27 +1539,11 @@
<label class="text-sm text-gray-600 dark:text-gray-400">
{{ t("admin.groups.openaiFast.free") }}
</label>
<button
type="button"
role="switch"
:aria-checked="createForm.free_openai_fast"
:aria-label="t('admin.groups.openaiFast.free')"
<Toggle
data-testid="create-free-openai-fast"
@click="createForm.free_openai_fast = !createForm.free_openai_fast"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.free_openai_fast
? 'bg-emerald-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.free_openai_fast ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
:aria-label="t('admin.groups.openaiFast.free')"
v-model="createForm.free_openai_fast"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiFast.freeHint") }}
@@ -1662,21 +1562,10 @@
<label class="text-sm text-gray-600 dark:text-gray-400">{{
t("admin.groups.openaiLive.allow")
}}</label>
<button
type="button"
@click="toggleLive('create')"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.allow_live
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="createForm.allow_live ? 'translate-x-6' : 'translate-x-1'"
/>
</button>
<Toggle
:model-value="createForm.allow_live"
@update:model-value="toggleLive('create')"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiLive.hint") }}
@@ -1697,28 +1586,7 @@
<label class="text-sm text-gray-600 dark:text-gray-400">{{
t("admin.groups.openaiMessages.allowDispatch")
}}</label>
<button
type="button"
@click="
createForm.allow_messages_dispatch =
!createForm.allow_messages_dispatch
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.allow_messages_dispatch
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.allow_messages_dispatch
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="createForm.allow_messages_dispatch" />
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiMessages.allowDispatchHint") }}
@@ -1943,27 +1811,7 @@
}}
</p>
</div>
<button
type="button"
@click="
createForm.require_oauth_only = !createForm.require_oauth_only
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.require_oauth_only
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.require_oauth_only
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="createForm.require_oauth_only" />
</div>
<!-- require_privacy_set toggle -->
@@ -1980,27 +1828,7 @@
}}
</p>
</div>
<button
type="button"
@click="
createForm.require_privacy_set = !createForm.require_privacy_set
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.require_privacy_set
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.require_privacy_set
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="createForm.require_privacy_set" />
</div>
</div>
@@ -2057,28 +1885,7 @@
</div>
<!-- 启用开关 -->
<div class="flex items-center gap-3 mb-3">
<button
type="button"
@click="
createForm.model_routing_enabled =
!createForm.model_routing_enabled
"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.model_routing_enabled
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.model_routing_enabled
? 'translate-x-6'
: 'translate-x-1',
]"
/>
</button>
<Toggle v-model="createForm.model_routing_enabled" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
createForm.model_routing_enabled
@@ -2478,23 +2285,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="editForm.is_exclusive = !editForm.is_exclusive"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.is_exclusive
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="editForm.is_exclusive" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
editForm.is_exclusive
@@ -2582,23 +2373,7 @@
{{ t("admin.groups.modelsList.hint", { endpoint: modelsListEndpoint(editForm.platform) }) }}
</p>
</div>
<button
type="button"
@click="editModelsListState.enabled = !editModelsListState.enabled"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 items-center rounded-full transition-colors',
editModelsListState.enabled
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editModelsListState.enabled ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="editModelsListState.enabled" />
</div>
<div
v-if="editModelsListState.enabled"
@@ -3163,23 +2938,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="editForm.mcp_xml_inject = !editForm.mcp_xml_inject"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.mcp_xml_inject
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.mcp_xml_inject ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="editForm.mcp_xml_inject" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
editForm.mcp_xml_inject
@@ -3221,23 +2980,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<button
type="button"
@click="editForm.claude_code_only = !editForm.claude_code_only"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.claude_code_only
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.claude_code_only ? 'translate-x-6' : 'translate-x-1',
]"
/>
</button>
<Toggle v-model="editForm.claude_code_only" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
editForm.claude_code_only
@@ -3400,27 +3143,11 @@
<label class="text-sm text-gray-600 dark:text-gray-400">
{{ t("admin.groups.openaiFast.force") }}
</label>
<button
type="button"
role="switch"
:aria-checked="editForm.force_openai_fast"
:aria-label="t('admin.groups.openaiFast.force')"
<Toggle
data-testid="edit-force-openai-fast"
@click="editForm.force_openai_fast = !editForm.force_openai_fast"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.force_openai_fast
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.force_openai_fast ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
:aria-label="t('admin.groups.openaiFast.force')"
v-model="editForm.force_openai_fast"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiFast.hint") }}
@@ -3429,27 +3156,11 @@
<label class="text-sm text-gray-600 dark:text-gray-400">
{{ t("admin.groups.openaiFast.free") }}
</label>
<button
type="button"
role="switch"
:aria-checked="editForm.free_openai_fast"
:aria-label="t('admin.groups.openaiFast.free')"
<Toggle
data-testid="edit-free-openai-fast"
@click="editForm.free_openai_fast = !editForm.free_openai_fast"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.free_openai_fast
? 'bg-emerald-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.free_openai_fast ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
:aria-label="t('admin.groups.openaiFast.free')"
v-model="editForm.free_openai_fast"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiFast.freeHint") }}
@@ -3468,21 +3179,10 @@
<label class="text-sm text-gray-600 dark:text-gray-400">{{
t("admin.groups.openaiLive.allow")
}}</label>
<button
type="button"
@click="toggleLive('edit')"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.allow_live
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="editForm.allow_live ? 'translate-x-6' : 'translate-x-1'"
/>
</button>
<Toggle
:model-value="editForm.allow_live"
@update:model-value="toggleLive('edit')"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiLive.hint") }}
@@ -3503,28 +3203,7 @@
<label class="text-sm text-gray-600 dark:text-gray-400">{{
t("admin.groups.openaiMessages.allowDispatch")
}}</label>
<button
type="button"
@click="
editForm.allow_messages_dispatch =
!editForm.allow_messages_dispatch
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.allow_messages_dispatch
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.allow_messages_dispatch
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="editForm.allow_messages_dispatch" />
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
{{ t("admin.groups.openaiMessages.allowDispatchHint") }}
@@ -3748,27 +3427,7 @@
}}
</p>
</div>
<button
type="button"
@click="
editForm.require_oauth_only = !editForm.require_oauth_only
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.require_oauth_only
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.require_oauth_only
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="editForm.require_oauth_only" />
</div>
<!-- require_privacy_set toggle -->
@@ -3785,27 +3444,7 @@
}}
</p>
</div>
<button
type="button"
@click="
editForm.require_privacy_set = !editForm.require_privacy_set
"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.require_privacy_set
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.require_privacy_set
? 'translate-x-6'
: 'translate-x-1'
"
/>
</button>
<Toggle v-model="editForm.require_privacy_set" />
</div>
</div>
@@ -3862,27 +3501,7 @@
</div>
<!-- 启用开关 -->
<div class="flex items-center gap-3 mb-3">
<button
type="button"
@click="
editForm.model_routing_enabled = !editForm.model_routing_enabled
"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.model_routing_enabled
? 'bg-primary-500'
: 'bg-gray-300 dark:bg-dark-600',
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.model_routing_enabled
? 'translate-x-6'
: 'translate-x-1',
]"
/>
</button>
<Toggle v-model="editForm.model_routing_enabled" />
<span class="text-sm text-gray-500 dark:text-gray-400">
{{
editForm.model_routing_enabled
@@ -4590,6 +4209,7 @@ import AppLayout from "@/components/layout/AppLayout.vue";
import TablePageLayout from "@/components/layout/TablePageLayout.vue";
import DataTable from "@/components/common/DataTable.vue";
import Pagination from "@/components/common/Pagination.vue";
import Toggle from "@/components/common/Toggle.vue";
import BaseDialog from "@/components/common/BaseDialog.vue";
import ConfirmDialog from "@/components/common/ConfirmDialog.vue";
import EmptyState from "@/components/common/EmptyState.vue";
@@ -5200,7 +4820,7 @@ const createCodexManifestDefaults = (): CodexModelsManifestConfig => ({
account_ids: [],
fallback_to_scheduler: false,
});
const editCodexManifestConfig = reactive<CodexModelsManifestConfig>(createCodexManifestDefaults());
const editCodexManifestConfig = ref<CodexModelsManifestConfig>(createCodexManifestDefaults());
const editCodexManifestAccountNames = ref<Record<number, string>>({});
const modelsListCandidatesTracker = createModelsListCandidatesTracker();
const createModelsListSelectedCount = computed(
@@ -6383,13 +6003,13 @@ const handleEdit = async (group: AdminGroup) => {
// 固定账号 manifest 配置:回显配置并异步解析已存账号名称(失败显示 #<id>)
const savedCodexManifestConfig =
group.codex_models_manifest_config ?? createCodexManifestDefaults();
Object.assign(editCodexManifestConfig, {
editCodexManifestConfig.value = {
enabled: savedCodexManifestConfig.enabled ?? false,
account_ids: [...(savedCodexManifestConfig.account_ids ?? [])],
fallback_to_scheduler: savedCodexManifestConfig.fallback_to_scheduler ?? false,
});
};
editCodexManifestAccountNames.value = {};
for (const id of editCodexManifestConfig.account_ids) {
for (const id of editCodexManifestConfig.value.account_ids) {
adminAPI.accounts
.getById(id)
.then((account) => {
@@ -6448,7 +6068,7 @@ const closeEditModal = () => {
resetMessagesDispatchFormState(editForm);
editForm.allow_live = false;
resetModelsListState(editModelsListState);
Object.assign(editCodexManifestConfig, createCodexManifestDefaults());
editCodexManifestConfig.value = createCodexManifestDefaults();
editCodexManifestAccountNames.value = {};
editCodexManifestRef.value?.resetValidation?.();
};
@@ -6472,8 +6092,8 @@ const handleUpdateGroup = async () => {
// 固定账号 manifest:开启后至少一个账号,前端阻止提交并提示。
if (
editForm.platform === "openai" &&
editCodexManifestConfig.enabled &&
editCodexManifestConfig.account_ids.length === 0
editCodexManifestConfig.value.enabled &&
editCodexManifestConfig.value.account_ids.length === 0
) {
appStore.showError(t("admin.groups.codexModelsManifest.selectAtLeastOne"));
editCodexManifestRef.value?.validate();
@@ -6523,9 +6143,9 @@ const handleUpdateGroup = async () => {
codex_models_manifest_config:
editForm.platform === "openai"
? {
enabled: editCodexManifestConfig.enabled,
account_ids: [...editCodexManifestConfig.account_ids],
fallback_to_scheduler: editCodexManifestConfig.fallback_to_scheduler,
enabled: editCodexManifestConfig.value.enabled,
account_ids: [...editCodexManifestConfig.value.account_ids],
fallback_to_scheduler: editCodexManifestConfig.value.fallback_to_scheduler,
}
: createCodexManifestDefaults(),
supported_model_scopes: normalizeSupportedModelScopesForPlatform(
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AdminGroup } from '@/types'
import GroupsView from '@/views/admin/GroupsView.vue'
import { adminAPI } from '@/api/admin'
const {
listGroups,
@@ -302,4 +303,70 @@ describe('GroupsView duplicate action', () => {
expect(showError).toHaveBeenCalledWith('group name already exists')
wrapper.unmount()
})
it('updates manifest controls immediately and submits the displayed selection', async () => {
vi.useFakeTimers()
vi.mocked(adminAPI.accounts.list).mockResolvedValue({
items: [{ id: 5, name: 'Manifest account' }]
} as never)
updateGroup.mockResolvedValue(sourceGroup)
const wrapper = mountView()
try {
await flushPromises()
const editButton = wrapper.findAll('button').find((button) => button.text() === 'common.edit')!
await editButton.trigger('click')
await flushPromises()
const toggle = wrapper.get('[data-testid="codex-manifest-toggle"]')
await toggle.trigger('click')
expect(toggle.attributes('aria-checked')).toBe('true')
const search = wrapper.get('[data-testid="codex-manifest-search"]')
await search.trigger('focus')
await vi.advanceTimersByTimeAsync(300)
await flushPromises()
expect(adminAPI.accounts.list).toHaveBeenCalledWith(
1, 20, { search: '', platform: 'openai', group: '42' }, expect.anything()
)
await wrapper.get('[data-testid="codex-manifest-dropdown"] button').trigger('click')
expect(wrapper.get('[data-testid="codex-manifest-selected-tags"]').text()).toContain('Manifest account')
await wrapper.get('[aria-label="remove account 5"]').trigger('click')
expect(wrapper.find('[data-testid="codex-manifest-selected-tags"]').exists()).toBe(false)
await wrapper.get('#edit-group-form').trigger('submit')
expect(updateGroup).not.toHaveBeenCalled()
expect(wrapper.find('[data-testid="codex-manifest-validation-error"]').exists()).toBe(true)
await search.trigger('focus')
await wrapper.get('[data-testid="codex-manifest-dropdown"] button').trigger('click')
expect(wrapper.get('[data-testid="codex-manifest-selected-tags"]').text()).toContain('Manifest account')
const fallback = wrapper.get('[data-testid="codex-manifest-fallback-toggle"]')
await fallback.trigger('click')
expect(fallback.attributes('aria-checked')).toBe('true')
await fallback.trigger('click')
expect(fallback.attributes('aria-checked')).toBe('false')
await fallback.trigger('click')
await toggle.trigger('click')
expect(wrapper.find('[data-testid="codex-manifest-search"]').exists()).toBe(false)
await toggle.trigger('click')
expect(wrapper.get('[data-testid="codex-manifest-selected-tags"]').text()).toContain('Manifest account')
await wrapper.get('#edit-group-form').trigger('submit')
await flushPromises()
expect(updateGroup).toHaveBeenCalledWith(42, expect.objectContaining({
codex_models_manifest_config: {
enabled: true, account_ids: [5], fallback_to_scheduler: true
}
}))
// Reopening reads the saved group afresh, without retaining the prior draft.
await editButton.trigger('click')
await flushPromises()
expect(wrapper.get('[data-testid="codex-manifest-toggle"]').attributes('aria-checked')).toBe('false')
expect(wrapper.find('[data-testid="codex-manifest-search"]').exists()).toBe(false)
} finally {
wrapper.unmount()
vi.useRealTimers()
}
})
})