Refine personal Agent cards and configuration

This commit is contained in:
鲨鱼辣椒 committed 2026-10-12 17:45:02 +08:00
1 parent c14d996c05
commit 8b40463a9f
17 files changed
+555 -412

No files matched your search

+74
View File
@@ -27,6 +27,80 @@ it('shows a default avatar for absent or broken images and recovers when the ima
expect(screen.getByRole('img', { name: '写作搭档头像' })).toHaveAttribute('src', 'https://images.test/new.png');
});
it('keeps a new assistant portrait while its name is typed or cleared', () => {
const view = render(<CloudAvatar name="" />);
const portrait = screen.getByRole('img').getAttribute('src');
for (const name of ['学', '学习', '学习 Friend', '学习 Friend 🚀', '']) {
view.rerender(<CloudAvatar name={name} />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
expect(screen.getByRole('img')).toHaveAttribute('alt', `${name}默认头像`);
}
});
it('keeps an assistant portrait across renaming, reopening and a failed custom image', () => {
const view = render(<CloudAvatar identity="english-practice" name="英语陪练" />);
const portrait = screen.getByRole('img').getAttribute('src');
for (const name of ['英', '英语', '英语 Friend', '英语 Friend 🌟']) {
view.rerender(<CloudAvatar identity="english-practice" name={name} />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
}
view.unmount();
const reopened = render(<CloudAvatar identity="english-practice" name="英语 Friend 🌟" src="https://images.test/saved.png" />);
expect(screen.getByRole('img')).toHaveAttribute('src', 'https://images.test/saved.png');
fireEvent.error(screen.getByRole('img'));
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
reopened.rerender(<CloudAvatar identity="english-practice" name="新名字" src="https://images.test/replaced.png" />);
expect(screen.getByRole('img')).toHaveAttribute('src', 'https://images.test/replaced.png');
});
it('uses one default portrait across creation, persisted identities and reopening', () => {
const creation = render(<CloudAvatar name="" />);
const portrait = screen.getByRole('img').getAttribute('src');
creation.rerender(<CloudAvatar name="我的英语陪练" />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
creation.unmount();
const view = render(<CloudAvatar identity="assistant-a" name="同名助手" />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
view.rerender(<CloudAvatar identity="assistant-b" name="同名助手" />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
view.rerender(<CloudAvatar identity="assistant-a" name="改名后的助手" />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
view.unmount();
render(<CloudAvatar identity="assistant-a" name="改名后的助手" />);
expect(screen.getByRole('img')).toHaveAttribute('src', portrait);
});
it('keeps the default selection and chosen portrait stable while the name is edited', async () => {
api.useAvatarPreset.mockResolvedValue({ icon: '/chosen.png', icon_url: 'https://images.test/chosen.png' });
function RenameEditor() {
const [name, setName] = useState('英语陪练');
const [value, setValue] = useState<CloudAgentAvatar>({});
const [uploading, setUploading] = useState(false);
return <><input aria-label="助手名称" value={name} onChange={event => setName(event.target.value)} />
<CloudAvatarEditor identity="english-practice" name={name} value={value} disabled={false} uploading={uploading} onUploading={setUploading} onChange={setValue} /></>;
}
render(<RenameEditor />);
const portraits = () => screen.getAllByRole('button', { name: /^像素伙伴 / });
const initialSelection = portraits().find(button => button.getAttribute('aria-pressed') === 'true')!;
const portrait = screen.getByRole('img', { name: '英语陪练默认头像' }).getAttribute('src');
expect(initialSelection.querySelector('img')).toHaveAttribute('src', portrait);
for (const name of ['英', '英语', '英语 Friend']) {
fireEvent.change(screen.getByRole('textbox', { name: '助手名称' }), { target: { value: name } });
expect(screen.getByRole('img', { name: `${name}默认头像` })).toHaveAttribute('src', portrait);
expect(initialSelection).toHaveAttribute('aria-pressed', 'true');
expect(portraits().filter(button => button.getAttribute('aria-pressed') === 'true')).toHaveLength(1);
}
fireEvent.click(screen.getByRole('button', { name: '像素伙伴 7' }));
await waitFor(() => expect(screen.getByRole('img', { name: '英语 Friend头像' })).toHaveAttribute('src', 'https://images.test/chosen.png'));
fireEvent.change(screen.getByRole('textbox', { name: '助手名称' }), { target: { value: '新的伙伴' } });
expect(screen.getByRole('img', { name: '新的伙伴头像' })).toHaveAttribute('src', 'https://images.test/chosen.png');
expect(screen.getByRole('button', { name: '像素伙伴 7' })).toHaveAttribute('aria-pressed', 'true');
fireEvent.click(screen.getByRole('button', { name: '移除头像' }));
expect(screen.getByRole('img', { name: '新的伙伴默认头像' })).toHaveAttribute('src', portrait);
expect(initialSelection).toHaveAttribute('aria-pressed', 'true');
expect(api.useAvatarPreset).toHaveBeenCalledExactlyOnceWith('avatar-07');
});
it('offers sixteen platform portraits and uses the chosen portrait without a file picker', async () => {
api.useAvatarPreset.mockResolvedValue({ icon: '/chosen.png', icon_url: 'https://images.test/chosen.png' });
render(<Editor />);
+20
View File
@@ -44,6 +44,26 @@ function moduleFor(fetchImpl: typeof fetch) {
beforeEach(() => { vi.clearAllMocks(); journalRecords.clear(); clearWorksSquareSession(); login(); });
afterEach(() => { instances.splice(0).forEach((module) => module.dispose()); clearWorksSquareSession(); vi.useRealTimers(); });
it.each(['', ' '])('preserves an empty introduction through create, save and list (%j)', async purpose => {
const emptyDraft = { ...draft, purpose: '' };
const transport = vi.fn().mockResolvedValueOnce(session()).mockResolvedValueOnce(json(emptyDraft))
.mockResolvedValueOnce(json({ ...emptyDraft, draft_revision: 2 })).mockResolvedValueOnce(json({ agents: [emptyDraft], next_cursor: null }));
const module = moduleFor(transport);
expect(await module.create({ ...input, purpose })).toMatchObject({ purpose: '' });
expect(await module.save(draft.slug, { expected_revision: 1, name: draft.name, purpose, system_prompt: draft.system_prompt })).toMatchObject({ purpose: '' });
expect((await module.list()).agents[0].purpose).toBe('');
expect(JSON.parse(transport.mock.calls[1][1].body).purpose).toBe('');
expect(JSON.parse(transport.mock.calls[2][1].body).purpose).toBe('');
});
it.each([null, 42, 'x'.repeat(2001)])('rejects malformed or overlong introductions before network access', async purpose => {
const transport = vi.fn();
const module = moduleFor(transport);
await expect(module.create({ ...input, purpose })).rejects.toMatchObject({ code: 'invalid_input' });
await expect(module.save(draft.slug, { expected_revision: 1, name: draft.name, purpose, system_prompt: draft.system_prompt })).rejects.toMatchObject({ code: 'invalid_input' });
expect(transport).not.toHaveBeenCalled();
});
describe('Main cloud Agents boundary', () => {
it.each(Array.from({ length: 16 }, (_, index) => `avatar-${String(index + 1).padStart(2, '0')}`))('uploads the bundled platform portrait %s through scoped Main transport without a dialog', async id => {
const icon = '/api/storage/public/images/creator/preset.png';
+129 -32
View File
@@ -36,14 +36,15 @@ beforeEach(() => {
});
afterEach(cleanup);
it('does not label disabled or archived assistants as ready to use', async () => {
it('does not label disabled assistants as ready to use or show archived assistants', async () => {
api.list.mockResolvedValue({ agents: [
{ ...draft, published_version: 1, enabled: false },
{ ...draft, slug: 'ml-' + 'b'.repeat(32), name: '归档助手', published_version: 1, archived: true },
], next_cursor: null });
open();
expect(await screen.findByText('已停用')).toBeVisible();
expect(screen.getByText('已归档')).toBeVisible();
expect(screen.queryByText('已归档')).not.toBeInTheDocument();
expect(screen.queryByRole('article', { name: '归档助手' })).not.toBeInTheDocument();
expect(screen.queryByText('可以使用')).not.toBeInTheDocument();
});
@@ -83,20 +84,25 @@ it('shows saved but unapplied changes and applies directly from the editor heade
['publish', expect.objectContaining({ slug: draft.slug, expected_revision: 4 })],
]);
expect(screen.queryByText('修改已保存,尚未应用')).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: '配置', exact: true })).toHaveAttribute('aria-current', 'page');
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
});
it('requires saving before applying and preserves formal chat input across configuration and application', async () => {
it('keeps unsent input accessible before adjusting and applying configuration', async () => {
publishedAssistant(4);
api.recovery.mockResolvedValue({ recent: null, pending: [] });
open();
fireEvent.click(await screen.findByRole('button', { name: '和它聊聊', exact: true }));
await screen.findByTestId('cloud-agent-editor');
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
const message = await screen.findByLabelText('消息');
await waitFor(() => expect(message).toBeEnabled());
fireEvent.change(message, { target: { value: '保留正式聊天的问题' } });
fireEvent.click(screen.getByRole('button', { name: '配置', exact: true }));
fireEvent.click(screen.getByRole('button', { name: '调整智能体', exact: true }));
expect(message).toBeVisible();
expect(message).toHaveValue('保留正式聊天的问题');
expect(screen.getByText('对话中还有未完成的内容,请先发送或处理后再调整智能体。')).toBeVisible();
fireEvent.change(message, { target: { value: '' } });
fireEvent.click(screen.getByRole('button', { name: '调整智能体', exact: true }));
expect(message).not.toBeVisible();
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '新的要求' } });
expect(screen.getByText('修改尚未保存和应用')).toBeVisible();
@@ -106,15 +112,16 @@ it('requires saving before applying and preserves formal chat input across confi
expect(api.call.mock.calls.some(([operation]) => operation === 'publish')).toBe(false);
fireEvent.click(screen.getByRole('button', { name: '应用最新修改', exact: true }));
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
expect(screen.getByRole('button', { name: '配置', exact: true })).toHaveAttribute('aria-current', 'page');
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByLabelText('消息')).toBe(message);
expect(message).toHaveValue('保留正式聊天的问题');
expect(message).not.toBeVisible();
expect(message).toHaveValue('');
expect(screen.queryByRole('button', { name: '聊天', exact: true })).not.toBeInTheDocument();
expect(api.call.mock.calls.filter(([operation]) => operation === 'publish')[0][1]).toMatchObject({ expected_revision: 5 });
expect(api.call.mock.calls.some(([operation]) => operation === 'preview' || operation === 'submit')).toBe(false);
});
it('shares the original failed publication between the header and access page', async () => {
it('retries a failed application from the header using the original publication intent', async () => {
publishedAssistant();
const base = api.call.getMockImplementation()!;
let attempts = 0;
@@ -128,8 +135,7 @@ it('shares the original failed publication between the header and access page',
fireEvent.click(screen.getByRole('button', { name: '应用最新修改', exact: true }));
await screen.findByText(/连接中断/);
expect(screen.getByRole('button', { name: '重试本次应用' })).toBeEnabled();
fireEvent.click(screen.getByRole('button', { name: '使用与接入' }));
fireEvent.click(await screen.findByRole('button', { name: '重试本次发布' }));
fireEvent.click(screen.getByRole('button', { name: '重试本次应用' }));
await waitFor(() => expect(screen.getByRole('button', { name: '已应用', exact: true })).toBeDisabled());
const calls = api.call.mock.calls.filter(([operation]) => operation === 'publish');
expect(calls).toHaveLength(2);
@@ -192,7 +198,7 @@ it('protects an uploaded avatar before a name has been entered', async () => {
expect(api.create).not.toHaveBeenCalled();
});
it('creates from name and avatar, then configures model and raw prompt without a preview chat', async () => {
it('creates from name and avatar, then configures an optional introduction, advanced model and raw prompt', async () => {
const avatar = { icon: '/api/storage/public/images/avatar.png', icon_url: 'https://images.test/avatar.png' };
const base = api.call.getMockImplementation()!;
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
@@ -222,9 +228,14 @@ it('creates from name and avatar, then configures model and raw prompt without a
await screen.findByTestId('cloud-agent-editor');
expect(screen.getByLabelText('名称')).toHaveValue('我的写作助手');
expect(screen.getByRole('img', { name: '我的写作助手头像' })).toHaveAttribute('src', avatar.icon_url);
expect(api.create).toHaveBeenCalledWith({ operation_id: expect.any(String), name: '我的写作助手', purpose: '按照我的提示词提供帮助。', icon: avatar.icon });
expect(api.create).toHaveBeenCalledWith({ operation_id: expect.any(String), name: '我的写作助手', purpose: '', icon: avatar.icon });
expect(api.create.mock.calls[0][0]).not.toHaveProperty('icon_url');
await waitFor(() => expect(screen.getByLabelText('模型')).toHaveValue('model-first'));
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
expect(screen.getByLabelText('一句话介绍')).toHaveValue('');
expect(screen.getByLabelText('一句话介绍')).toBeVisible();
expect(screen.getByLabelText('模型')).toHaveValue('');
expect(screen.getByLabelText('模型')).not.toBeVisible();
expect(screen.getByRole('tab', { name: '介绍与提示词' })).toHaveAttribute('aria-selected', 'true');
expect(screen.getByLabelText('提示词')).toHaveValue('');
expect(screen.queryByLabelText('用途')).not.toBeInTheDocument();
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
@@ -232,17 +243,24 @@ it('creates from name and avatar, then configures model and raw prompt without a
expect(screen.queryByText(/这是帮助它思考和回答的 AI|新助手从目录中的第一个可用模型开始|选择模型后,保存即可试用/)).not.toBeInTheDocument();
expect(screen.queryByText(/每 .*个词元约 .*词元点数/)).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /简单易懂|一步一步来|先给我提示/ })).not.toBeInTheDocument();
fireEvent.change(screen.getByLabelText('模型'), { target: { value: '' } });
for (const name of ['聊天', '自动任务', '使用与接入']) {
expect(screen.queryByRole('button', { name, exact: true })).not.toBeInTheDocument();
}
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '我的文章搭档' } });
expect(screen.getByLabelText('模型')).toHaveValue('');
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
expect(screen.getByRole('button', { name: '模型', exact: true })).toHaveAttribute('aria-current', 'page');
expect(screen.getByLabelText('模型')).toBeVisible();
expect(screen.getByLabelText('模型')).toBeRequired();
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'model-second' } });
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
const prompt = '一次只解释一个新词。\n\n 保留缩进。\n使用简单易懂的语言,遇到新词先解释。\n';
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: prompt } });
expect(api.save).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({
name: '我的文章搭档', system_prompt: prompt, icon: avatar.icon,
name: '我的文章搭档', purpose: '', system_prompt: prompt, icon: avatar.icon,
configuration: expect.objectContaining({ model: 'model-second' }),
}));
expect(api.save.mock.calls[0][1]).not.toHaveProperty('icon_url');
@@ -341,22 +359,101 @@ it('treats an avatar change as a manual draft edit and blocks saving during its
it('saves renamed existing assistants without rewriting arbitrary instructions or selecting a model', async () => {
const original = '自定义标题\n\n 保留缩进。\n使用简单易懂的语言,遇到新词先解释。\n把任务拆成小步骤,一次引导我完成一步。\n先给我提示和思考问题,让我尝试后再补充答案。\n';
const existing = { ...draft, system_prompt: original };
api.list.mockResolvedValue({ agents: [existing], next_cursor: null });
let existing = { ...draft, system_prompt: original };
api.list.mockImplementation(async () => ({ agents: [existing], next_cursor: null }));
const base = api.call.getMockImplementation()!;
api.call.mockImplementation(async (operation, input) => operation === 'catalog' ? {
models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null,
} : base(operation, input));
api.save.mockImplementation(async (_slug, input) => ({ ...existing, ...input, draft_revision: 2 }));
api.save.mockImplementation(async (_slug, input) => (existing = { ...existing, ...input, draft_revision: 2 }));
open();
const cardAvatar = await screen.findByRole('img', { name: '写作搭档默认头像' });
const portrait = cardAvatar.getAttribute('src');
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
expect(screen.getByLabelText('模型')).toHaveValue('');
expect(screen.getByLabelText('模型')).not.toBeVisible();
expect(screen.getByLabelText('提示词')).toHaveValue(original);
fireEvent.click(screen.getByRole('button', { name: '修改名称' }));
expect(screen.getByLabelText('名称')).toHaveFocus();
for (const name of ['新', '新名', '新名字']) {
fireEvent.change(screen.getByLabelText('名称'), { target: { value: name } });
expect(screen.getByRole('img', { name: `${name}默认头像` })).toHaveAttribute('src', portrait);
}
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ system_prompt: original, configuration: expect.objectContaining({ model: '' }) }));
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
expect(await screen.findByRole('article', { name: '新名字' })).toBeVisible();
expect(screen.getByRole('img', { name: '新名字默认头像' })).toHaveAttribute('src', portrait);
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
expect(screen.getByRole('img', { name: '新名字默认头像' })).toHaveAttribute('src', portrait);
expect(screen.getByLabelText('提示词')).toHaveValue(original);
});
it('uses the saved introduction on the assistant card and permits clearing it on reopening', async () => {
let current = { ...draft };
api.list.mockImplementation(async () => ({ agents: [current], next_cursor: null }));
api.save.mockImplementation(async (_slug, input) => (current = { ...current, ...input, draft_revision: current.draft_revision + 1 }));
open();
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
const introduction = '和我一起把小想法变成故事';
fireEvent.change(screen.getByLabelText('一句话介绍'), { target: { value: introduction } });
expect(api.save).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ purpose: introduction, system_prompt: '原始指令' }));
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
expect(await screen.findByText(introduction)).toBeVisible();
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
expect(screen.getByLabelText('一句话介绍')).toHaveValue(introduction);
fireEvent.change(screen.getByLabelText('一句话介绍'), { target: { value: '' } });
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
expect(api.save.mock.calls[1][1]).toMatchObject({ purpose: '', system_prompt: '原始指令' });
fireEvent.click(screen.getByRole('button', { name: '我的智能体', exact: true }));
const card = await screen.findByRole('article', { name: '写作搭档' });
expect(within(card).queryByText(introduction)).not.toBeInTheDocument();
expect(card.querySelector('.agent-home-purpose')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '继续制作', exact: true }));
expect(screen.getByLabelText('一句话介绍')).toHaveValue('');
});
it('requires an explicit advanced model choice before starting use but permits an unfinished draft save', async () => {
let current = { ...draft };
const base = api.call.getMockImplementation()!;
api.call.mockImplementation(async (operation, input) => {
if (operation === 'catalog') return { models: [{ id: 'model-a', name: '模型甲' }], resources: {}, pricing: null };
if (operation === 'access') return { published_version: null, enabled: true, versions: [], grants: [], applications: [] };
return base(operation, input);
});
api.save.mockImplementation(async (_slug, input) => (current = { ...current, ...input, draft_revision: current.draft_revision + 1 }));
open();
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
expect(screen.getByLabelText('模型')).toHaveValue('');
expect(screen.getByLabelText('提示词')).toHaveValue(original);
fireEvent.change(screen.getByLabelText('名称'), { target: { value: '新名字' } });
expect(screen.getByLabelText('模型')).not.toBeVisible();
expect(api.save).not.toHaveBeenCalled();
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '还没配置模型的想法' } });
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ system_prompt: original, configuration: expect.objectContaining({ model: '' }) }));
expect(api.save).toHaveBeenCalledWith(draft.slug, expect.objectContaining({ configuration: expect.objectContaining({ model: '' }) }));
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
expect(screen.getByRole('tab', { name: '高级设置' })).toHaveAttribute('aria-selected', 'true');
const categories = within(screen.getByRole('navigation', { name: '配置分类' })).getAllByRole('button');
expect(categories[0]).toHaveAccessibleName('模型');
expect(categories[0]).toHaveAttribute('aria-current', 'page');
expect(screen.getByLabelText('模型')).toBeVisible();
expect(screen.getByLabelText('模型')).toBeRequired();
expect(screen.getByRole('alert')).toHaveTextContent('请先在高级设置中选择模型。');
expect(screen.queryByRole('button', { name: '确认开始使用' })).not.toBeInTheDocument();
expect(api.call.mock.calls.some(([operation]) => operation === 'publish')).toBe(false);
fireEvent.change(screen.getByLabelText('模型'), { target: { value: 'model-a' } });
fireEvent.click(screen.getByRole('button', { name: '保存草稿' }));
await screen.findByRole('button', { name: '已保存' });
fireEvent.click(screen.getByRole('button', { name: '开始使用', exact: true }));
expect(await screen.findByRole('button', { name: '确认开始使用' })).toBeEnabled();
expect(api.call.mock.calls.some(([operation]) => ['publish', 'preview', 'submit'].includes(operation))).toBe(false);
});
it('retains requirements when model loading fails and offers a usable retry', async () => {
@@ -373,6 +470,8 @@ it('retains requirements when model loading fails and offers a usable retry', as
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
await screen.findByText('目录暂不可用');
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '保留我的想法' } });
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
expect(screen.getByText('目录暂不可用')).toBeVisible();
fail = false;
fireEvent.click(screen.getByRole('button', { name: '重新读取模型' }));
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
@@ -467,7 +566,7 @@ it('freezes the uploaded avatar and creation identity when retrying a lost respo
await screen.findByTestId('cloud-agent-editor');
expect(api.create).toHaveBeenCalledTimes(2);
expect(api.create.mock.calls[1][0]).toEqual(api.create.mock.calls[0][0]);
expect(api.create.mock.calls[0][0]).toEqual({ operation_id: expect.any(String), name: '写作搭档', purpose: '按照我的提示词提供帮助。', icon: avatar.icon });
expect(api.create.mock.calls[0][0]).toEqual({ operation_id: expect.any(String), name: '写作搭档', purpose: '', icon: avatar.icon });
expect(api.uploadAvatar).toHaveBeenCalledTimes(1);
expect(screen.getByRole('img', { name: '写作搭档头像' })).toHaveAttribute('src', avatar.icon_url);
});
@@ -556,11 +655,11 @@ it('switches peer editor tabs while retaining the advanced category and unfinish
open();
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
await waitFor(() => expect(screen.getByLabelText('模型')).toBeEnabled());
const requirements = screen.getByRole('tab', { name: '模型与提示词', exact: true });
const requirements = screen.getByRole('tab', { name: '介绍与提示词', exact: true });
const advanced = screen.getByRole('tab', { name: '高级设置', exact: true });
expect(requirements).toHaveAttribute('aria-selected', 'true');
expect(advanced).toHaveAttribute('aria-selected', 'false');
expect(screen.getByRole('tabpanel', { name: '模型与提示词' })).toBeVisible();
expect(screen.getByRole('tabpanel', { name: '介绍与提示词' })).toBeVisible();
expect(screen.queryByRole('navigation', { name: '配置分类' })).not.toBeInTheDocument();
const instructions = screen.getByLabelText('提示词');
fireEvent.change(instructions, { target: { value: '没有保存的要求' } });
@@ -570,7 +669,7 @@ it('switches peer editor tabs while retaining the advanced category and unfinish
expect(requirements).toHaveAttribute('aria-selected', 'false');
expect(screen.getByRole('tabpanel', { name: '高级设置' })).toBeVisible();
expect(instructions).not.toBeVisible();
expect(screen.getByRole('button', { name: '能力', exact: true })).toHaveAttribute('aria-current', 'page');
expect(screen.getByRole('button', { name: '模型', exact: true })).toHaveAttribute('aria-current', 'page');
fireEvent.click(screen.getByRole('button', { name: '知识', exact: true }));
fireEvent.click(screen.getByText('管理我的知识文档'));
fireEvent.click(screen.getByRole('button', { name: '新建知识库' }));
@@ -609,9 +708,7 @@ it('retains unsaved instructions while saving a budget independently of the draf
open();
fireEvent.click(await screen.findByRole('button', { name: '继续制作', exact: true }));
fireEvent.change(screen.getByLabelText('提示词'), { target: { value: '未保存的指令' } });
fireEvent.click(screen.getByRole('button', { name: '聊天', exact: true }));
expect(screen.queryByLabelText('消息')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '配置', exact: true }));
expect(screen.getByLabelText('提示词')).toHaveValue('未保存的指令');
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
fireEvent.click(screen.getByRole('button', { name: '限制', exact: true }));
@@ -621,7 +718,7 @@ it('retains unsaved instructions while saving a budget independently of the draf
fireEvent.click(screen.getByRole('button', { name: '保存费用上限' }));
await waitFor(() => expect(api.call).toHaveBeenCalledWith('saveBudget', expect.objectContaining({ daily_limit_points: '12' })));
expect(api.save).not.toHaveBeenCalled();
fireEvent.mouseDown(screen.getByRole('tab', { name: '模型与提示词', exact: true }), { button: 0, ctrlKey: false });
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词', exact: true }), { button: 0, ctrlKey: false });
expect(screen.getByLabelText('提示词')).toHaveValue('未保存的指令');
expect(screen.getByRole('button', { name: '保存草稿' })).toBeEnabled();
expect(api.call.mock.calls.some(([operation]) => ['threads', 'preview', 'submit'].includes(operation))).toBe(false);
@@ -662,7 +759,7 @@ it('searches capabilities while retaining existing selections outside the result
expect(await screen.findByRole('checkbox', { name: /missing-tool/ })).toBeChecked();
fireEvent.click(screen.getByRole('button', { name: '选择工具', exact: true }));
fireEvent.change(screen.getByLabelText('搜索工具'), { target: { value: '资料' } });
fireEvent.mouseDown(screen.getByRole('tab', { name: '模型与提示词' }), { button: 0, ctrlKey: false });
fireEvent.mouseDown(screen.getByRole('tab', { name: '介绍与提示词' }), { button: 0, ctrlKey: false });
fireEvent.mouseDown(screen.getByRole('tab', { name: '高级设置' }), { button: 0, ctrlKey: false });
expect(screen.getByLabelText('搜索工具')).toHaveValue('资料');
expect(screen.queryByRole('checkbox', { name: '当前时间' })).not.toBeInTheDocument();