fix(ui): clarify teacher help prompt and fit narrow panels

This commit is contained in:
鲨鱼辣椒
2026-09-29 11:57:29 +08:00
parent 3f5d1a8b65
commit e4b99d5e32
6 changed files with 56 additions and 40 deletions

View File

@@ -1869,7 +1869,7 @@ test('project consultations preserve student drafts and switch between work and
await expect(page.getByRole('button', { name: '与朋友聊天' })).toHaveAttribute('aria-pressed', 'true');
await expect(page.locator('#coding-consultation-dock')).toHaveCSS('width', '508px');
await expect(teacher.locator('form .consultation-hint')).toBeVisible();
await expect(teacher.locator('form .consultation-hint')).toHaveText('帮我看看');
await expect(teacher.locator('form .consultation-hint')).toHaveText('不知道问什么,你先帮我看看👀');
await expect(teacher.getByText('把你的困惑说出来就好')).toHaveCount(0);
await expect(teacher.getByText('当前项目',{exact:true})).toHaveCount(0);
await expect(teacher.getByRole('button',{name:'解释当前代码',exact:true})).toBeVisible();
@@ -1924,7 +1924,7 @@ test('project consultations preserve student drafts and switch between work and
expect(request.body).not.toHaveProperty('discussion');
}
await expect(teacher).toHaveAccessibleName('朋友咨询');
expect(consultationRequests[0].body).toMatchObject({intent:'suggestions',text:'帮我看看'});
expect(consultationRequests[0].body).toMatchObject({intent:'suggestions',text:'不知道问什么,你先帮我看看👀'});
expect(consultationRequests[1].body).toMatchObject({text:'怎样观察别人玩游戏?'});
expect(consultationRequests[1].body?.intent).toBeUndefined();
await page.screenshot({path:test.info().outputPath('project-teacher-side-chat.png')});

View File

@@ -29,7 +29,7 @@ test('retry stickers stay readable and preserve drafts in a 319px consultation',
const panel = page.getByTestId('teacher-chat-panel');
const retry = panel.locator('.consultation-retry');
await expect(retry).toBeEnabled();
await expect(retry).toHaveText('帮我看看');
await expect(retry).toHaveText('不知道问什么,你先帮我看看👀');
expect(await retry.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
const [panelBounds, retryBounds] = await Promise.all([panel.boundingBox(), retry.boundingBox()]);
expect(retryBounds!.x).toBeGreaterThanOrEqual(panelBounds!.x);
@@ -41,7 +41,7 @@ test('retry stickers stay readable and preserve drafts in a 319px consultation',
await expect(page.getByText('我们可以先从一个小地方开始。')).toBeVisible();
await expect(input).toHaveValue('我还有一点自己的想法');
expect(await page.evaluate(() => (window as unknown as { __teacherReplyInputs: unknown[] }).__teacherReplyInputs)).toEqual([
{ requestId: expect.any(String), text: '帮我看看', intent: 'suggestions', references: [], projectId: 'fixture', sourceConversationId: 'source' },
{ requestId: expect.any(String), text: '不知道问什么,你先帮我看看👀', intent: 'suggestions', references: [], projectId: 'fixture', sourceConversationId: 'source' },
]);
}
});
@@ -89,7 +89,8 @@ for (const width of [319, 508]) {
const send = page.getByRole('button', { name: '提问', exact: true });
const sent = () => page.evaluate(() => (window as unknown as { __teacherReplyInputs: unknown[] }).__teacherReplyInputs);
await expect(hint).toBeEnabled();
await expect(hint).toHaveText('帮我看看');
await expect(hint).toHaveText('不知道问什么,你先帮我看看👀');
expect(await hint.evaluate(el => el.scrollWidth <= el.clientWidth && el.scrollHeight <= el.clientHeight)).toBe(true);
await expect(hint).toHaveCSS('background-color', 'rgb(255, 242, 201)');
const initialSend = (await send.boundingBox())!;
const hintBounds = (await hint.boundingBox())!;
@@ -109,9 +110,9 @@ for (const width of [319, 508]) {
expect(await sent()).toEqual([]);
await hint.click();
await expect(page.getByText('我们可以先从一个小地方开始。')).toBeVisible();
expect(await sent()).toEqual([{ requestId: expect.any(String), text: '帮我看看', intent: 'suggestions', references: [], projectId: 'fixture', sourceConversationId: 'source' }]);
expect(await sent()).toEqual([{ requestId: expect.any(String), text: '不知道问什么,你先帮我看看👀', intent: 'suggestions', references: [], projectId: 'fixture', sourceConversationId: 'source' }]);
await expect(page.getByRole('button', { name: '我也说不清,你带我看看', exact: true })).toHaveCount(0);
await expect(page.locator('.consultation-messages').getByRole('button', { name: '帮我看看', exact: true })).toHaveCount(0);
await expect(page.locator('.consultation-messages').getByRole('button', { name: '不知道问什么,你先帮我看看👀', exact: true })).toHaveCount(0);
const card = page.getByRole('button', { name: '可以从哪个小地方开始?' });
await expect(card).toBeVisible();
await input.fill('留着我自己的草稿');

View File

@@ -496,13 +496,13 @@ describe('teacher side chat', () => {
}));
render(<TeacherChatPanel projectId="p" sourceId="c" />);
const input = screen.getByLabelText('向智能体提问');
const help = () => screen.getByText('帮我看看', { selector: 'form .consultation-hint' });
const help = () => screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' });
await waitFor(() => expect(help()).toBeEnabled());
expect(screen.getByTestId('consultation-composer-actions')).toContainElement(help());
expect(help()).toHaveAccessibleName('帮我看看');
expect(help()).toHaveAccessibleName('不知道问什么,你先帮我看看👀');
expect(api.send).not.toHaveBeenCalled();
fireEvent.change(input, { target: { value: '我先自己想想' } });
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(api.send).not.toHaveBeenCalled();
fireEvent.change(input, { target: { value: ' ' } });
expect(help()).toBeEnabled();
@@ -517,11 +517,11 @@ describe('teacher side chat', () => {
expect(screen.queryByRole('button', { name: '尚未确认的问题' })).not.toBeInTheDocument();
expect(api.send).toHaveBeenCalledTimes(1);
expect(api.send).toHaveBeenCalledWith('p/agent-topics', 'first', expect.objectContaining({
intent: 'suggestions', text: '帮我看看', sourceConversationId: 'c',
intent: 'suggestions', text: '不知道问什么,你先帮我看看👀', sourceConversationId: 'c',
}));
expect(help()).toBeDisabled();
fireEvent.change(input, { target: { value: '我自己还想问的另一件事' } });
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
await waitFor(() => expect(streams.has('first')).toBe(true));
const sent = api.send.mock.calls[0][2] as TeacherSend;
act(() => streams.get('first')!.dispatchEvent(new MessageEvent('snapshot', { data: JSON.stringify({
@@ -532,7 +532,7 @@ describe('teacher side chat', () => {
expect(screen.queryByText('我看看你最近做到了哪里…')).not.toBeInTheDocument();
for (const question of suggestedQuestions) expect(screen.getByRole('button', { name: question })).toBeEnabled();
expect(screen.queryByRole('button', { name: '我也说不清,你带我看看', exact: true })).not.toBeInTheDocument();
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(api.send).toHaveBeenCalledTimes(1);
expect(input).toHaveValue('我自己还想问的另一件事');
});
@@ -567,8 +567,8 @@ describe('teacher side chat', () => {
it('keeps an uncertain suggestions request identity through reopening and draft edits', async () => {
api.send.mockRejectedValue(new Error('网络暂不可用'));
const view = render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled());
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled());
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await screen.findByText('网络暂不可用');
fireEvent.change(screen.getByLabelText('向智能体提问'), { target: { value: '我还没写完的问题' } });
const originalRequest = api.send.mock.calls[0][2] as TeacherSend;
@@ -578,10 +578,10 @@ describe('teacher side chat', () => {
render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByLabelText('向智能体提问')).toBeEnabled());
expect(screen.getByLabelText('向智能体提问')).toHaveValue('我还没写完的问题');
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(api.send).toHaveBeenCalledTimes(1);
fireEvent.change(screen.getByLabelText('向智能体提问'), { target: { value: '' } });
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await screen.findByText('网络暂不可用');
expect(api.send.mock.calls[1][2]).toEqual(originalRequest);
expect(screen.getByLabelText('向智能体提问')).toHaveValue('');
@@ -592,16 +592,16 @@ describe('teacher side chat', () => {
localStorage.setItem('makelore-consultation-draft:signed-out:p:teacher', JSON.stringify({ text: '', references: [], pending: original }));
api.send.mockRejectedValue(new Error('网络暂不可用'));
const view = render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled());
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled());
expect(api.send).not.toHaveBeenCalled();
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await screen.findByText('网络暂不可用');
expect(JSON.parse(JSON.stringify(api.send.mock.calls[0][2]))).toEqual(original);
view.unmount();
render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled());
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled());
expect(api.send).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await screen.findByText('网络暂不可用');
expect(JSON.parse(JSON.stringify(api.send.mock.calls[1][2]))).toEqual(original);
});
@@ -626,13 +626,13 @@ describe('teacher side chat', () => {
api.read.mockResolvedValue({ ...first, requests: [suggestionsRequest({ id: original.requestId, text: original.text, status })] });
api.send.mockResolvedValue({ ...first, revision: 2 });
render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled());
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled());
expect(api.send).not.toHaveBeenCalled();
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await waitFor(() => expect(api.send).toHaveBeenCalledTimes(1));
const sent = api.send.mock.calls[0][2] as TeacherSend;
expect(sent.requestId).not.toBe(original.requestId);
expect(sent).toMatchObject({ text: '帮我看看', intent: 'suggestions', references: [], sourceConversationId: 'c' });
expect(sent).toMatchObject({ text: '不知道问什么,你先帮我看看👀', intent: 'suggestions', references: [], sourceConversationId: 'c' });
});
it.each(['failed', 'cancelled', 'interrupted'] as const)('retries a %s suggestion request without leaking raw JSON or reusing its accepted request id', async status => {
@@ -645,7 +645,7 @@ describe('teacher side chat', () => {
await screen.findByText('这次没有看清楚,请再试一次。');
expect(screen.queryByText('{"intro":"未完成')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: suggestedQuestions[0] })).not.toBeInTheDocument();
fireEvent.click(screen.getByText('帮我看看', { selector: '.consultation-retry' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: '.consultation-retry' }));
await screen.findByText('我们可以从最近遇到的这两个地方聊起。');
expect(api.send.mock.calls[0][2]).toMatchObject({ intent: 'suggestions' });
expect(api.send.mock.calls[0][2].requestId).not.toBe(failed.id);
@@ -657,9 +657,9 @@ describe('teacher side chat', () => {
...first, revision: 3, requests: [accepted, suggestionsRequest({ id: input.requestId })],
}));
const view = render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled());
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled());
await waitFor(() => expect(streams.has('first')).toBe(true));
fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' }));
await screen.findByText('响应中途断开');
fireEvent.change(screen.getByLabelText('向智能体提问'), { target: { value: '我的自由提问继续留着' } });
const originalRequest = api.send.mock.calls[0][2] as TeacherSend;
@@ -673,10 +673,10 @@ describe('teacher side chat', () => {
act(() => streams.get('first')!.dispatchEvent(new MessageEvent('snapshot', { data: JSON.stringify(recovered) })));
}
await screen.findByText('智能体已接到,但这次回复失败了');
await waitFor(() => expect(screen.getByText('帮我看看', { selector: '.consultation-retry' })).toBeEnabled());
await waitFor(() => expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: '.consultation-retry' })).toBeEnabled());
expect(screen.getByLabelText('向智能体提问')).toHaveValue('我的自由提问继续留着');
expect(api.send).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByText('帮我看看', { selector: '.consultation-retry' }));
fireEvent.click(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: '.consultation-retry' }));
await screen.findByText('我们可以从最近遇到的这两个地方聊起。');
expect(api.send).toHaveBeenCalledTimes(2);
expect(api.send.mock.calls[1][2].requestId).not.toBe(originalRequest.requestId);
@@ -699,7 +699,7 @@ describe('teacher side chat', () => {
expect(api.send.mock.calls[0][2].intent).toBeUndefined();
expect(screen.getByLabelText('向智能体提问')).toHaveValue('');
expect(screen.queryByRole('button', { name: '移除引用' })).not.toBeInTheDocument();
expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled();
expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeEnabled();
view.unmount();
render(<TeacherChatPanel projectId="p" sourceId="c" />);
expect(screen.getByLabelText('向智能体提问')).toHaveValue('');
@@ -787,7 +787,7 @@ describe('teacher side chat', () => {
fireEvent.click(screen.getByRole('button', { name: '运营配置的预览问题' }));
expect(screen.getByLabelText('向智能体提问')).toHaveValue('运营配置的预览问题');
expect(api.send).not.toHaveBeenCalled();
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '智能体新话题' }));
await waitFor(() => expect(api.create).toHaveBeenCalledWith('preview/sample', 7, '学生的练习代码', undefined));
});
@@ -911,7 +911,7 @@ describe('merged cloud teacher classroom', () => {
api.sendConversation.mockRejectedValue(new Error('结果暂未确认'));
const props = { projectId: 'p', sourceId: 'original-source', selectedAgent: agent };
const view = render(<TeacherChatPanel {...props} />);
const hint = () => screen.getByText('帮我看看', { selector: 'form .consultation-hint' });
const hint = () => screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' });
await waitFor(() => expect(hint()).toBeEnabled());
expect(api.sendConversation).not.toHaveBeenCalled();
fireEvent.click(hint());
@@ -984,10 +984,10 @@ describe('merged cloud teacher classroom', () => {
render(<TeacherChatPanel projectId="p" sourceId="c" />);
await waitFor(() => expect(screen.getByText('智能体暂未开放,历史仍可查看。')).toBeVisible());
const input = screen.getByLabelText('向智能体提问');
expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeDisabled();
expect(screen.getByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).toBeDisabled();
fireEvent.change(input, { target: { value: '继续讨论' } });
expect(screen.getByRole('button', { name: '提问', exact: true })).toBeDisabled();
expect(screen.queryByText('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
expect(screen.queryByText((_text, element) => element?.textContent === '不知道问什么,你先帮我看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument();
fireEvent.keyDown(input, { key: 'Enter' });
expect(api.send).not.toHaveBeenCalled();
});