feat(会员): 后台 AGC 模型支持标注权限档
- AdminAgcModel 新增 access 字段(缺省 basic,失败开放),GET 回读 PUT 写入 - api-server 保存时校验权限档只允许 basic/full - 后台 AGC 模型页新增「权限档」列,可按模型选择基础/高性能模型 - fake API 与页面用例覆盖权限档选择与保存
This commit is contained in:
@@ -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} 启用`}
|
||||
|
||||
Reference in New Issue
Block a user