fix: render consultation replies and typed tool activity
This commit is contained in:
@@ -11,6 +11,14 @@ const samples: Record<string, TeacherDiscussionContent> = {
|
||||
const scenario = new URLSearchParams(location.search).get('kind') ?? 'ideas';
|
||||
let current: TeacherTopic = { schemaVersion: 1, revision: 1, id: 'discussion-fixture', accountId: 'fixture', projectId: 'fixture', sourceConversationId: 'project', definition, version: 1, createdAt: 'now', updatedAt: 'now', requests: Array.from({ length: 8 }, (_, i) => ({ id: `r${i}`, text: i === 7 ? '我还没想好,我们边聊边想。' : '我希望这个游戏更有意思。', references: [], createdAt: 'now', sourceCursor: { workerGeneration: 0, seq: i }, sourceCapturedAt: 'now', includedSourceMessageIds: [], omittedMessages: 0, status: 'completed', response: i === 7 ? '可以,我们先把已经想到的放在一起。哪里还不确定,就留着继续聊。' : '先从你最在意的一个小地方开始想。', ...(scenario === 'ordinary' && i === 7 ? { suggestedQuestions: ['有什么好玩的想法?'] } : {}) })), ...(samples[scenario] ? { discussion: { id: 'tool', revision: 1, status: 'active', content: structuredClone(samples[scenario]), ...(scenario === 'structure' ? { previousIdeas: structuredClone(samples.ideas) as Extract<TeacherDiscussionContent, { kind: 'ideas' }> } : {}) } } : {}) };
|
||||
const stream = Object.assign(new EventTarget(), { close() {}, onerror: null, onopen: null });
|
||||
if (scenario === 'rich-text') {
|
||||
current.requests = [{
|
||||
...current.requests[0], text: '帮我看看项目和最近的讨论',
|
||||
response: '## 项目建议\n\n**先核对已有设计**,再决定下一步。\n\n- 检查入口文件\n- 保留当前玩法\n\n| 文件 | 作用 | 需要确认 |\n| --- | --- | --- |\n| src/main.ts | 初始化项目 | 游戏循环与计时 |\n| public/assets | 图片资源 | 构建后路径是否可用 |\n\n```js\nconst message = "保留字符串里的\\n,不把它变成实际换行";\nconst resourcePath = "/assets/" + "very-long-file-name-".repeat(15);\n```\n\n运动距离:$s = vt$。\n\n\n\n[项目说明](https://example.com/readme)\n\n> 先验证观察到的现象。',
|
||||
discussionError: '这次整理没有完成,先保留原来的内容。',
|
||||
unparsedResponse: '{"reply":"正文已保留","tool":{"kind":"structure",',
|
||||
}];
|
||||
}
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const snapshot = () => { current.revision++; stream.dispatchEvent(new MessageEvent('snapshot', { data: JSON.stringify(current) })); };
|
||||
export const teacherApi = {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createRoot } from 'react-dom/client';
|
||||
import { TeacherChatPanel } from '../../../src/pages/Chat/TeacherChatPanel';
|
||||
import '../../../src/styles/globals.css';
|
||||
import '../../../src/pages/Chat/classroom-workspace.css';
|
||||
import 'katex/dist/katex.min.css';
|
||||
const width = Number(new URLSearchParams(location.search).get('width')) || 508;
|
||||
createRoot(document.getElementById('root')!).render(<div className="classroom-workspace flex h-screen flex-col bg-white">
|
||||
<header className="flex h-10 shrink-0 items-center border-b px-5 text-xs">智能体讨论交互测试 · 真实页面组件 / 模拟回复</header>
|
||||
|
||||
@@ -20,6 +20,34 @@ test.beforeAll(async () => {
|
||||
await server.listen(); url = server.resolvedUrls!.local[0];
|
||||
});
|
||||
test.afterAll(async () => { await server?.close(); });
|
||||
for (const width of [319, 508]) {
|
||||
test(`rich replies keep code, tables and images inside the panel at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1180, height: 800 });
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
await page.route('https://example.com/teacher-preview.png', route => route.fulfill({
|
||||
contentType: 'image/svg+xml',
|
||||
body: '<svg xmlns="http://www.w3.org/2000/svg" width="900" height="250"><rect width="900" height="250" fill="#ebeedf"/><text x="30" y="130" font-size="32">Flybot project preview</text></svg>',
|
||||
}));
|
||||
await page.goto(`${url}?kind=rich-text&width=${width}`);
|
||||
const body = page.getByTestId('teacher-reply');
|
||||
await expect(body.getByRole('heading', { name: '项目建议' })).toHaveCount(1);
|
||||
await expect(body.getByRole('table')).toHaveCount(1);
|
||||
await expect(body.locator('.katex')).toHaveCount(1);
|
||||
await expect(body.getByRole('img', { name: '项目截图' })).toBeVisible();
|
||||
await expect.poll(() => body.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
|
||||
await expect.poll(() => page.locator('.consultation-messages').evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
|
||||
const raw = page.getByText('查看收到的原始内容');
|
||||
await expect(raw.locator('..')).not.toHaveAttribute('open');
|
||||
await raw.click();
|
||||
await expect(raw.locator('..').locator('pre')).toContainText('"tool"');
|
||||
const input = page.getByRole('textbox', { name: '向智能体提问' });
|
||||
await expect(input).toBeVisible();
|
||||
await page.locator('.consultation-messages').evaluate(el => { el.scrollTop = 0; });
|
||||
await page.screenshot({ path: test.info().outputPath(`rich-reply-${width}.png`) });
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
}
|
||||
for (const width of [319, 508]) for (const kind of ['ordinary', 'ideas', 'structure', 'flow', 'comparison']) {
|
||||
test(`${kind} pins the current tool and composer at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1180, height: 800 });
|
||||
|
||||
Reference in New Issue
Block a user