From 458c37ff2d054f6e975106df569fb7c345709526 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 24 Sep 2026 12:10:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=20AGC=20=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=A1=B5=E5=8E=BB=E6=8E=89=E5=88=AB=E5=90=8D=E5=88=97=EF=BC=8C?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=BB=B4=E6=8A=A4=E4=B8=8A=E6=B8=B8=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E6=A8=A1=E5=9E=8B=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adminApiTypes:AdminAgcModel 改为 {model, enabled},目录改为 {revision, defaultModel, models} - AdminAgcModelsPage:列为模型名/启用/默认/操作,行按下标定位,重命名默认行时同步 defaultModel,默认行不可停用或删除 - 页面文案改为说明目录初始值由服务端启动时从上游同步 --- apps/admin-web/src/api/adminApiTypes.ts | 8 +- .../src/pages/AdminAgcModelsPage.test.tsx | 56 ++++- .../src/pages/AdminAgcModelsPage.tsx | 192 +++++++++--------- apps/admin-web/src/styles/admin.css | 2 +- 4 files changed, 148 insertions(+), 110 deletions(-) diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts index 01e99ed10..70faa20f9 100644 --- a/apps/admin-web/src/api/adminApiTypes.ts +++ b/apps/admin-web/src/api/adminApiTypes.ts @@ -1075,16 +1075,16 @@ export interface AdminRechargeRefundActionResponse { export interface AdminWalletRestrictionResponse { wallet: AdminProfileWalletPayload; } +/** AGC 平台模型目录项:`model` 就是上游原始模型名。 */ export interface AdminAgcModel { - id: string; - alias: string; - modelId: string; + model: string; enabled: boolean; } +/** AGC 平台模型目录:开放给客户端的模型、启用状态与默认项。 */ export interface AdminAgcModelCatalog { revision: number; - defaultModelId: string; + defaultModel: string; models: AdminAgcModel[]; } diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx index bb88b9de6..94c718046 100644 --- a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx +++ b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx @@ -25,13 +25,11 @@ vi.mock('../components/useAdminWriteConfirm', () => ({ })); afterEach(cleanup); -test('edits alias and upstream model without changing the stable identifier or revision', async () => { +test('renames a model name while keeping revision and row order', async () => { const catalog = { revision: 3, - defaultModelId: 'quality', - models: [ - { id: 'quality', alias: '高质量', modelId: 'gpt-6-astra', enabled: true }, - ], + defaultModel: 'gpt-5.6-sol', + models: [{ model: 'gpt-5.6-sol', enabled: true }], }; vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog); vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({ @@ -39,18 +37,56 @@ test('edits alias and upstream model without changing the stable identifier or r revision: 4, })); render(); - await screen.findByDisplayValue('gpt-6-astra'); - fireEvent.change(screen.getByLabelText('模型 1 别名'), { - target: { value: '精细创作' }, + await screen.findByDisplayValue('gpt-5.6-sol'); + fireEvent.change(screen.getByLabelText('模型 1 模型名'), { + target: { value: 'gpt-5.6-terra' }, }); fireEvent.click(screen.getByRole('button', { name: '保存' })); await waitFor(() => expect(saveAgcModelCatalog).toHaveBeenCalledWith('test', { - ...catalog, - models: [{ ...catalog.models[0], alias: '精细创作' }], + revision: 3, + defaultModel: 'gpt-5.6-terra', + models: [{ model: 'gpt-5.6-terra', enabled: true }], }), ); await waitFor(() => { expect(screen.getAllByText('已保存').length).toBeGreaterThanOrEqual(2); }); }); + +test('keeps the default model on the renamed row and appends an enabled blank row', async () => { + const catalog = { + revision: 3, + defaultModel: 'gpt-5.6-sol', + models: [ + { model: 'gpt-5.6-sol', enabled: true }, + { model: 'gpt-5.6-luna', enabled: true }, + ], + }; + vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog); + vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => input); + render(); + await screen.findByDisplayValue('gpt-5.6-luna'); + + fireEvent.click(screen.getByLabelText('模型 2 默认')); + fireEvent.change(screen.getByLabelText('模型 2 模型名'), { + target: { value: 'gpt-5.6-verda' }, + }); + fireEvent.click(screen.getByRole('button', { name: '添加模型' })); + fireEvent.change(screen.getByLabelText('模型 3 模型名'), { + target: { value: 'gpt-5.6-nova' }, + }); + fireEvent.click(screen.getByRole('button', { name: '保存' })); + + await waitFor(() => + expect(saveAgcModelCatalog).toHaveBeenCalledWith('test', { + revision: 3, + defaultModel: 'gpt-5.6-verda', + models: [ + { model: 'gpt-5.6-sol', enabled: true }, + { model: 'gpt-5.6-verda', enabled: true }, + { model: 'gpt-5.6-nova', enabled: true }, + ], + }), + ); +}); diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx index 2a3618fc1..150fedb1b 100644 --- a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx +++ b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx @@ -2,7 +2,7 @@ import { CircleHelp, Plus, RefreshCcw, Save, Trash2 } from 'lucide-react'; import { useEffect, useState } from 'react'; import { getAgcModelCatalog, saveAgcModelCatalog } from '../api/adminApiClient'; -import type { AdminAgcModel, AdminAgcModelCatalog } from '../api/adminApiTypes'; +import type { AdminAgcModelCatalog } from '../api/adminApiTypes'; import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm'; import { handlePageError } from './pageUtils'; @@ -38,19 +38,38 @@ export function AdminAgcModelsPage({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); - function update(id: string, patch: Partial) { + /** 目录项没有稳定标识,一律按下标定位。 */ + function setEnabled(index: number, enabled: boolean) { setSaved(false); setCatalog( (current) => current && { ...current, - models: current.models.map((model) => - model.id === id ? { ...model, ...patch } : model, + models: current.models.map((model, candidate) => + candidate === index ? { ...model, enabled } : model, ), }, ); } + /** 重命名默认项时必须同步默认模型名,否则服务端会因默认项不在目录中而拒绝保存。 */ + function renameModel(index: number, model: string) { + setSaved(false); + setCatalog( + (current) => + current && { + ...current, + models: current.models.map((entry, candidate) => + candidate === index ? { ...entry, model } : entry, + ), + defaultModel: + current.models[index]?.model === current.defaultModel + ? model + : current.defaultModel, + }, + ); + } + async function save() { if (!catalog || busy) return; if ( @@ -78,7 +97,7 @@ export function AdminAgcModelsPage({

AGC 模型

-

管理客户端可用模型与用户看到的名称

+

维护平台开放给客户端的模型、启用状态与默认项

版本 v{catalog?.revision ?? '-'} @@ -93,13 +112,7 @@ export function AdminAgcModelsPage({
默认模型 - - {catalog - ? (catalog.models.find( - (model) => model.id === catalog.defaultModelId, - )?.alias ?? '未设置') - : '未设置'} - + {catalog?.defaultModel || '未设置'}
发布状态 @@ -110,7 +123,9 @@ export function AdminAgcModelsPage({

模型目录

- 客户端仅显示别名,实际模型名仅在这里维护 + + 目录初始值由服务端启动时从上游同步,这里维护平台开放给客户端的模型、启用状态与默认项 +
@@ -135,15 +150,7 @@ export function AdminAgcModelsPage({ (current) => current && { ...current, - models: [ - ...current.models, - { - id: crypto.randomUUID(), - alias: '', - modelId: '', - enabled: true, - }, - ], + models: [...current.models, { model: '', enabled: true }], }, ); }} @@ -166,85 +173,80 @@ export function AdminAgcModelsPage({ - - + - {catalog?.models.map((model, index) => ( - - - - - - - - ))} + {catalog?.models.map((model, index) => { + const isDefault = catalog.defaultModel === model.model; + return ( + + + + + + + ); + })}
别名实际模型名模型名 启用 默认 操作
- - update(model.id, { alias: e.target.value }) - } - /> - - - update(model.id, { modelId: e.target.value }) - } - /> - - - update(model.id, { enabled: e.target.checked }) - } - /> - - { - setSaved(false); - setCatalog({ ...catalog, defaultModelId: model.id }); - }} - /> - - -
+ renameModel(index, e.target.value)} + /> + + setEnabled(index, e.target.checked)} + /> + + { + setSaved(false); + setCatalog( + (current) => + current && { + ...current, + defaultModel: model.model, + }, + ); + }} + /> + + +
diff --git a/apps/admin-web/src/styles/admin.css b/apps/admin-web/src/styles/admin.css index 2198adbdf..5026f941a 100644 --- a/apps/admin-web/src/styles/admin.css +++ b/apps/admin-web/src/styles/admin.css @@ -3816,7 +3816,7 @@ button:disabled { color: #fff; } .admin-agc-models-table-grid { - min-width: 720px; + min-width: 540px; } .admin-agc-models-table-grid th { padding-top: 12px;