feat(会员): 后台 AGC 模型支持标注权限档

- AdminAgcModel 新增 access 字段(缺省 basic,失败开放),GET 回读 PUT 写入
- api-server 保存时校验权限档只允许 basic/full
- 后台 AGC 模型页新增「权限档」列,可按模型选择基础/高性能模型
- fake API 与页面用例覆盖权限档选择与保存
This commit is contained in:
2026-10-03 16:29:03 +08:00
parent 103280025c
commit 2630b0f690
6 changed files with 75 additions and 2 deletions
+3
View File
@@ -1174,6 +1174,7 @@ export interface AdminWalletRestrictionResponse {
wallet: AdminProfileWalletPayload;
}
export type AdminAgcAgentMode = 'codex' | 'cc';
export type AdminAgcModelAccess = 'basic' | 'full';
export type AdminAgcModelProtocol =
| 'openai_responses'
| 'openai_chat'
@@ -1187,6 +1188,8 @@ export interface AdminAgcModel {
agentMode: AdminAgcAgentMode;
/** 上游线协议,与执行器正交。 */
protocol?: AdminAgcModelProtocol;
/** 该模型要求的会员档位,缺省 `basic`(失败开放)。 */
access?: AdminAgcModelAccess;
}
export interface AdminAgcModelCatalog {
@@ -101,10 +101,46 @@ test('new models default to Codex and save the selected Agent mode', async () =>
modelId: '',
enabled: true,
agentMode: 'cc',
access: 'basic',
});
});
});
test('权限档可按模型单独选择并随目录保存', async () => {
const catalog = {
revision: 3,
defaultModelId: 'quality',
models: [
{
id: 'quality',
alias: '高质量',
modelId: 'gpt-6-astra',
enabled: true,
agentMode: 'codex' as const,
access: 'basic' as const,
},
],
};
vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog);
vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({
...input,
revision: 4,
}));
render(<AdminAgcModelsPage token="test" onUnauthorized={vi.fn()} />);
await screen.findByDisplayValue('gpt-6-astra');
const access = screen.getByLabelText('模型 1 权限档') as HTMLSelectElement;
expect(access.value).toBe('basic');
fireEvent.change(access, { target: { value: 'full' } });
fireEvent.click(screen.getByRole('button', { name: '保存' }));
await waitFor(() => {
const calls = vi.mocked(saveAgcModelCatalog).mock.calls;
const payload = calls[calls.length - 1]?.[1];
expect(payload?.models[0]).toMatchObject({ access: 'full' });
});
});
test('协议与 Agent 模式是独立字段,可按模型单独选择', async () => {
const catalog = {
revision: 3,
@@ -118,6 +118,7 @@ export function AdminAgcModelsPage({
{ key: 'modelId', label: '实际模型名' },
{ key: 'agentMode', label: 'Agent 模式' },
{ key: 'protocol', label: '协议' },
{ key: 'access', label: '权限档' },
{ key: 'enabled', label: '启用' },
{ key: 'default', label: '默认' },
{ key: 'actions', label: '操作' },
@@ -159,6 +160,7 @@ export function AdminAgcModelsPage({
enabled: true,
agentMode: 'codex',
protocol: 'openai_responses',
access: 'basic',
},
],
},
@@ -246,6 +248,21 @@ export function AdminAgcModelsPage({
<option value="anthropic">Anthropic Messages</option>
</select>
</td>
<td>
<select
aria-label={`模型 ${index + 1} 权限档`}
value={model.access ?? 'basic'}
disabled={busy}
onChange={(e) =>
update(model.id, {
access: e.target.value as AdminAgcModel['access'],
})
}
>
<option value="basic">基础模型</option>
<option value="full">高性能模型</option>
</select>
</td>
<td>
<input
aria-label={`模型 ${index + 1} 启用`}