Merge grouped teacher introductions into latest main

This commit is contained in:
鲨鱼辣椒
2026-09-29 23:24:59 +08:00
13 changed files with 295 additions and 150 deletions

View File

@@ -65,7 +65,7 @@ for (const width of [375, 768, 1280, 1470]) {
await page.getByTestId('community-invitation').scrollIntoViewIfNeeded();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
await expect(page.getByRole('button', { name: '去作品广场逛逛' })).toBeVisible();
while (await teachers.count()) await teachers.getByRole('button', { name: /^收起.*的介绍$/ }).first().click();
while (await teachers.count()) await teachers.getByRole('button', { name: /^收起全部老师介绍/ }).first().click();
await heading.scrollIntoViewIfNeeded();
const composer = page.getByTestId('coding-message-composer');
await expect(composer).toBeInViewport({ ratio: 1 });
@@ -125,7 +125,7 @@ for (const width of [1280, 1470]) {
}).toBeLessThan(2);
const inputBeforeDismiss = await composer.boundingBox();
const headingBeforeDismiss = await heading.boundingBox();
while (await teachers.count()) await teachers.getByRole('button', { name: /^收起.*的介绍$/ }).first().click();
while (await teachers.count()) await teachers.getByRole('button', { name: /^收起全部老师介绍/ }).first().click();
await expect(composer.getByRole('textbox')).toHaveValue('保留这份草稿');
expect(await composer.boundingBox()).toEqual(inputBeforeDismiss);
expect(await heading.boundingBox()).toEqual(headingBeforeDismiss);

View File

@@ -12,7 +12,7 @@ export function Layout() {
const [bubble, setBubble] = useState<HTMLDivElement | null>(null);
const [draft, setDraft] = useState('');
const [sent, setSent] = useState(false);
const [dismissed, setDismissed] = useState<string[]>([]);
const [dismissed, setDismissed] = useState(false);
const agents = ['架构工程师-陈宗琦', '产品工程师-Alice'].map((name, index) => ({
teacher_id: String(index), version: 1, is_default: index === 0,
definition: { config_id: String(index), name, avatar_id: 'avatar-06',
@@ -22,9 +22,8 @@ export function Layout() {
agents, selectedAgent: agents[0], definition: agents[0].definition, enabled: true,
unreadAgentIds: [], invitation: null, welcome: null, loading: false, error: null, checking: false,
refreshing: false, refreshStatus: 'idle', refresh: async () => {}, reloadCatalog: async () => {}, dismiss: () => {},
welcomes: new URLSearchParams(location.search).has('teachers') ? agents.flatMap((agent, index) => dismissed.includes(agent.teacher_id)
? [] : [{ agentId: agent.teacher_id, definition: agent.definition, text: agent.definition.description, tone: index }]) : [],
dismissWelcome: (id: string) => setDismissed(values => [...values, id]),
welcomes: new URLSearchParams(location.search).has('teachers') && !dismissed ? agents.map((agent, index) => ({ agentId: agent.teacher_id, definition: agent.definition, text: agent.definition.description, tone: index })) : [],
dismissWelcomes: () => setDismissed(true),
} as ComponentProps<typeof TeacherCompanion>['companion'];
return <div className="flex h-screen">
{new URLSearchParams(location.search).has('sidebar') && <aside data-testid="project-sidebar" className="shrink-0 border-r p-4 text-sm" style={{ width: 210, background: '#f1f6fc' }}>我的项目</aside>}

View File

@@ -19,7 +19,7 @@ export function Layout() {
const [refreshStatus, setRefreshStatus] = useState<'idle' | 'success' | 'error'>('idle');
const [selected, setSelected] = useState(0);
const welcomeMode = new URLSearchParams(location.search).has('welcomes');
const [dismissed, setDismissed] = useState<string[]>([]);
const [dismissed, setDismissed] = useState(false);
const agents = Array.from({ length: count }, (_, index) => ({
teacher_id: `agent-${index}`, version: index + 1, is_default: index === 0,
definition: { config_id: `agent-${index}`, name: `智能体${index + 1}`, avatar_id: 'avatar-06',
@@ -31,8 +31,8 @@ export function Layout() {
definition: agents[selected]?.definition ?? { name: '编程老师', avatar_id: 'avatar-06' },
invitation: !welcomeMode && visible ? { response: long ? '你刚才说想做一个宠物游戏。我们一起想想,什么样的小动作,会让你愿意一直陪它玩下去?可以先挑一个最想试的想法。' : '有什么编程问题想一起讨论?' } : null,
welcome: null, enabled: true, loading: false, checking: false, error: null,
welcomes: welcomeMode && !open ? agents.flatMap((agent, index) => dismissed.includes(agent.teacher_id) ? [] : [{ agentId: agent.teacher_id, definition: agent.definition, text: agent.definition.description, tone: index % 3 }]) : [],
dismissWelcome: (id: string) => setDismissed(ids => [...new Set([...ids, id])]),
welcomes: welcomeMode && !open && !dismissed ? agents.map((agent, index) => ({ agentId: agent.teacher_id, definition: agent.definition, text: agent.definition.description, tone: index % 3 })) : [],
dismissWelcomes: () => setDismissed(true),
refreshing, refreshStatus,
refresh: async () => {
setRefreshing(true);

View File

@@ -2324,6 +2324,42 @@ test('teacher introductions collapse after three operation replies and remain av
} finally { await releaseSnapshot(electronApp); }
});
test('teacher introduction group closes all three and stays closed for a new teacher after refresh and reload', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);
const connection = await page.evaluate(async () => ({ token: await window.electron.ipcRenderer.invoke('hostapi:token') as string, baseUrl: await window.electron.ipcRenderer.invoke('hostapi:base-url') as string }));
await installCodingFirstChatHost(electronApp, connection, true);
await settleSnapshot(electronApp);
await disableCodingEventSource(page);
const addTeacher = async (id: string) => electronApp.evaluate((_electron, id) => {
const state = (globalThis as typeof globalThis & { __makelorePiFirstChatE2E: TeacherCatalogFixture }).__makelorePiFirstChatE2E;
const original = state.teacherCatalog.items[0];
state.teacherCatalog.items.push({ ...original, teacher_id: id, is_default: false,
definition: { ...original.definition, config_id: id, name: id } });
}, id);
try {
await addTeacher('第三位老师');
await page.reload(); page = await getStableWindow(electronApp);
await page.getByTestId('ai-module-option-programming').click();
await page.evaluate(() => { window.location.hash = '/chat'; });
const group = page.getByTestId('teacher-welcomes');
await expect(group.locator('.teacher-welcome-card')).toHaveCount(3);
await group.getByRole('button', { name: '收起全部老师介绍(朋友)' }).click();
await expect(group).toHaveCount(0);
await expect(page.getByTestId('teacher-chat-panel')).toHaveCount(0);
await addTeacher('第四位老师');
await page.getByRole('button', { name: '刷新智能体', exact: true }).click();
await expect(page.getByRole('button', { name: '与第四位老师聊天', exact: true })).toBeVisible();
await expect(group).toHaveCount(0);
await page.reload();
await expect(page.getByRole('button', { name: '与第四位老师聊天', exact: true })).toBeVisible();
await expect(group).toHaveCount(0);
await page.getByRole('button', { name: '与代码智能体聊天', exact: true }).hover();
await expect(page.getByTestId('teacher-introduction')).toContainText('一起发现代码里的小奥秘');
expect((await readState(electronApp)).captured.filter(item => /\/(agent-topics|messages|teacher-check-in|prompt)$/.test(item.path) && item.method === 'POST')).toHaveLength(0);
} finally { await releaseSnapshot(electronApp); }
});
test('teacher avatar consumes the welcome bubble through close, reopen and reload without a model request', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);
@@ -2355,7 +2391,7 @@ test('teacher avatar consumes the welcome bubble through close, reopen and reloa
await page.reload();
await expect(teacherButton).toBeVisible();
await expect(invitation).toHaveCount(0);
await expect(page.getByRole('button', { name: '收起朋友的介绍' })).toBeVisible();
await expect(page.getByTestId('teacher-welcomes')).toHaveCount(0);
expect((await readState(electronApp)).captured.filter(item => /\/(agent-topics|messages|teacher-check-in|prompt)$/.test(item.path) && item.method === 'POST')).toHaveLength(0);
} finally { await releaseSnapshot(electronApp); }
});

View File

@@ -79,15 +79,15 @@ for (const width of [375, 768, 1280]) {
return !!bubble && !!viewport && viewport.y >= bubble.y + bubble.height;
}).toBe(true);
await page.screenshot({ path: test.info().outputPath(`teacher-introductions-${width}.png`) });
await group.getByRole('button', { name: '收起智能体2的介绍' }).click();
await expect(group.locator('.teacher-welcome-card')).toHaveCount(2);
await group.getByRole('button', { name: '收起全部老师介绍(智能体2)' }).click();
await expect(group).toHaveCount(0);
await expect(page.locator('#coding-consultation-dock')).toHaveCount(0);
await page.reload();
await expect(group.locator('.teacher-welcome-card')).toHaveCount(3);
await group.getByRole('button', { name: '和智能体3聊聊' }).click({ position: { x: 8, y: 8 } });
await expect(page.getByRole('button', { name: '与智能体3聊天' })).toHaveAttribute('aria-pressed', 'true');
await expect(group).toHaveCount(0);
await page.getByRole('button', { name: '关闭智能体栏' }).click();
await expect(group.locator('.teacher-welcome-card')).toHaveCount(1);
while (await group.count()) await group.getByRole('button', { name: /^收起.*的介绍$/ }).first().click();
await expect(group).toHaveCount(0);
});
}
@@ -108,8 +108,6 @@ test('many teacher introductions remain scrollable and honor reduced motion', as
await page.keyboard.press('Enter');
await expect(page.getByRole('button', { name: '与智能体8聊天' })).toHaveAttribute('aria-pressed', 'true');
await page.getByRole('button', { name: '关闭智能体栏' }).click();
await expect(group.locator('.teacher-welcome-card')).toHaveCount(7);
while (await group.count()) await group.getByRole('button', { name: /^收起.*的介绍$/ }).first().click();
await expect(group).toHaveCount(0);
});