Merge teacher composer and compact prompt UI into main
This commit is contained in:
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { TeacherChatPanel } from '@/pages/Chat/TeacherChatPanel';
|
||||
import type { TeacherDefinition, TeacherRequest, TeacherSend, TeacherTopic } from '../../shared/coding-teacher';
|
||||
const api = vi.hoisted(() => ({
|
||||
models: vi.fn(),
|
||||
catalog: vi.fn(),
|
||||
config: vi.fn(),
|
||||
preview: vi.fn(),
|
||||
@@ -71,6 +72,7 @@ beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
localStorage.clear();
|
||||
streams = new Map();
|
||||
api.models.mockResolvedValue({ supported: true, models: [{ id: 'qwen' }, { id: 'other-model' }] });
|
||||
api.config.mockResolvedValue({ enabled: true, published_version: 1, revision: 1, definition });
|
||||
api.catalog.mockResolvedValue({ items: [{ teacher_id: 'one', version: 1, definition, is_default: true }] });
|
||||
api.conversation.mockResolvedValue({ topic: null, before: null });
|
||||
@@ -107,8 +109,8 @@ describe('teacher side chat', () => {
|
||||
api.sendConversation.mockRejectedValue(new Error('结果暂未确认'));
|
||||
render(<TeacherChatPanel projectId="p" sourceId="c" selectedAgent={agent} />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '看看进展' })).toBeEnabled());
|
||||
expect([...screen.getByTestId('consultation-composer-actions').querySelectorAll('.consultation-hint')].map(el => el.textContent)).toEqual(shortcuts.map(item => item.label));
|
||||
expect(screen.queryByRole('button', { name: '继续看看👀' })).not.toBeInTheDocument();
|
||||
expect([...screen.getByTestId('consultation-shortcuts').querySelectorAll('.consultation-hint')].map(el => el.textContent)).toEqual(shortcuts.map(item => item.label));
|
||||
expect(screen.queryByRole('button', { name: '帮我整体看🧠' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(shortcuts[0].prompt)).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '看看进展' }));
|
||||
await screen.findByText('结果暂未确认');
|
||||
@@ -145,7 +147,7 @@ describe('teacher side chat', () => {
|
||||
expect(screen.queryByRole('button', { name: '旧入口' })).not.toBeInTheDocument();
|
||||
const cleared = { ...updated, version: 3, definition: { ...updated.definition, shortcuts: [] } };
|
||||
view.rerender(<TeacherChatPanel projectId="p" sourceId="c" selectedAgent={cleared} catalogState={state(cleared)} />);
|
||||
expect(screen.getByTestId('consultation-composer-actions').querySelectorAll('.consultation-hint')).toHaveLength(0);
|
||||
expect(screen.queryByTestId('consultation-shortcuts')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('之前的回答')).toBeVisible();
|
||||
expect(api.sendConversation).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -547,13 +549,14 @@ 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(screen.getByTestId('consultation-shortcuts')).toContainElement(help());
|
||||
expect(screen.getByTestId('consultation-composer-actions')).not.toContainElement(help());
|
||||
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();
|
||||
@@ -568,11 +571,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({
|
||||
@@ -583,7 +586,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('我自己还想问的另一件事');
|
||||
});
|
||||
@@ -618,8 +621,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;
|
||||
@@ -629,10 +632,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('');
|
||||
@@ -643,16 +646,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);
|
||||
});
|
||||
@@ -677,13 +680,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 => {
|
||||
@@ -696,7 +699,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);
|
||||
@@ -708,9 +711,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;
|
||||
@@ -724,10 +727,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);
|
||||
@@ -750,7 +753,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('');
|
||||
@@ -838,7 +841,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));
|
||||
});
|
||||
@@ -962,7 +965,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());
|
||||
@@ -1035,10 +1038,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();
|
||||
});
|
||||
@@ -1059,3 +1062,19 @@ describe('merged cloud teacher classroom', () => {
|
||||
expect(screen.getByRole('button', { name: '智能体新话题' })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('selects a teacher model for the next send and retains it after remount', async () => {
|
||||
const view = render(<TeacherChatPanel projectId="p" sourceId="c" />);
|
||||
await ready();
|
||||
const trigger = screen.getByRole('button', { name: /老师模型/ });
|
||||
fireEvent.keyDown(trigger, { key: 'Enter' });
|
||||
fireEvent.click(await screen.findByRole('menuitemradio', { name: 'other-model' }));
|
||||
fireEvent.change(screen.getByRole('textbox', { name: '向智能体提问' }), { target: { value: '我的想法' } });
|
||||
api.send.mockResolvedValue(first);
|
||||
fireEvent.click(screen.getByRole('button', { name: '提问' }));
|
||||
await waitFor(() => expect(api.send).toHaveBeenCalledWith('p/agent-topics', 'first', expect.objectContaining({ modelId: 'other-model', text: '我的想法' })));
|
||||
view.unmount();
|
||||
render(<TeacherChatPanel projectId="p" sourceId="c" />);
|
||||
await screen.findByRole('button', { name: /老师模型:other-model/ });
|
||||
expect(screen.getByRole('button', { name: '语音输入' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user