fix: toggle the teacher dock from the conversation header
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
- Warm welcome heading, existing composer and a compact inspiration invitation share the empty-chat layout. Both the invitation and persistent sidebar button open only https://square.nianxx.cn/#explore in the system browser on explicit click.
|
- Warm welcome heading, existing composer and a compact inspiration invitation share the empty-chat layout. Both the invitation and persistent sidebar button open only https://square.nianxx.cn/#explore in the system browser on explicit click.
|
||||||
- No public project feed, preview cards, community store or iframe remains in this feature. Existing unrelated Main routes are unchanged. The composer remains mounted across the first send; teacher introductions keep their prior three-successful-operation-round behavior and receive responsive clearance.
|
- No public project feed, preview cards, community store or iframe remains in this feature. Existing unrelated Main routes are unchanged. The composer remains mounted across the first send; teacher introductions keep their prior three-successful-operation-round behavior and receive responsive clearance.
|
||||||
- Preserved the concurrent live help-label overlay (task 20260929-teacher-help-label-83a2f67c): the two buttons and suggestions text remain 帮我整体看🧠. Ported only its three source edits and synchronized existing expectations; peer checkout untouched.
|
- Preserved the concurrent live help-label overlay (task 20260929-teacher-help-label-83a2f67c): the two buttons and suggestions text remain 帮我整体看🧠. Ported only its three source edits and synchronized existing expectations; peer checkout untouched.
|
||||||
- Temporary Renderer now loads this checkout's index-CwqpErpO.js. Existing Main PID 62668 remains running; formal installation, cloud configuration, user history and remote branches unchanged. Kept this worktree for the live trial.
|
- Temporary Renderer now loads this checkout's index-Cgp0q7I7.js. Existing Main PID 62668 remains running; formal installation, cloud configuration, user history and remote branches unchanged. Kept this worktree for the live trial.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
@@ -87,3 +87,15 @@ All three steps completed. Earlier public-feed investigation was feasibility wor
|
|||||||
- Completed: teacher introductions remain fixed-width overlays at the upper right, with no reserved horizontal rail. The welcome is centered horizontally in the whole content page and vertically in the available area above the bottom invitation/composer. Invitation and input sizing and spacing remain unchanged. Desktop teacher dismissal no longer shifts either element; compact content can scroll below the overlay.
|
- Completed: teacher introductions remain fixed-width overlays at the upper right, with no reserved horizontal rail. The welcome is centered horizontally in the whole content page and vertically in the available area above the bottom invitation/composer. Invitation and input sizing and spacing remain unchanged. Desktop teacher dismissal no longer shifts either element; compact content can scroll below the overlay.
|
||||||
- Validation: typecheck, 77 focused unit tests, 8 headless layout/interaction cases, lint:check (0 errors, 8 existing warnings), scoped E2E ESLint, build:vite, native E2E --list (20 cases), ownership and diff checks passed. Screenshots reviewed with both teachers floating; tests assert horizontal/vertical centering, stable bounds on dismissal, collapsed sidebar alignment, bottom input and preserved drafts. Native headed tests were not run to avoid focus changes.
|
- Validation: typecheck, 77 focused unit tests, 8 headless layout/interaction cases, lint:check (0 errors, 8 existing warnings), scoped E2E ESLint, build:vite, native E2E --list (20 cases), ownership and diff checks passed. Screenshots reviewed with both teachers floating; tests assert horizontal/vertical centering, stable bounds on dismissal, collapsed sidebar alignment, bottom input and preserved drafts. Native headed tests were not run to avoid focus changes.
|
||||||
- Trial updated to index-CwqpErpO.js in the background. Actual live horizontal and vertical center errors are 0px, invitation gap 12px and input bottom gap 16px. No alerts/cards/iframe; auth identity, route and all 13 saved drafts unchanged. Existing teacher dismissal state was preserved. Background native capture was unavailable (UnknownVizError), so visual review used the headless layout screenshots; live DOM geometry was independently verified. Evidence: trial floating-teachers-centered-welcome-20260929/. Keep this checkout for the live trial; no merge or push.
|
- Trial updated to index-CwqpErpO.js in the background. Actual live horizontal and vertical center errors are 0px, invitation gap 12px and input bottom gap 16px. No alerts/cards/iframe; auth identity, route and all 13 saved drafts unchanged. Existing teacher dismissal state was preserved. Background native capture was unavailable (UnknownVizError), so visual review used the headless layout screenshots; live DOM geometry was independently verified. Evidence: trial floating-teachers-centered-welcome-20260929/. Keep this checkout for the live trial; no merge or push.
|
||||||
|
|
||||||
|
## Follow-up: Integrate Teacher Dock Toggle Handoff
|
||||||
|
|
||||||
|
- User-authorized handoff from 前端-页面修改2 supplies /tmp/makelore-help-label-83a2f67c/teacher-dock-toggle.patch. Continue the owned trial/frontend feature at 5de8ea8; ownership start/status passed, and existing Project Context Loaded still applies. Read the delivering task and new empty-work-preview task records.
|
||||||
|
- Cross-check: launch metadata still points to this checkout/5de8ea8. 前端-页面修改3 finalized 0167d36 but explicitly has not updated the running app, so there is no new runtime overlay to preserve. Keep that separately completed Main/preview feature out of this bounded button update. No semantic conflict; Planning Gate: Passed.
|
||||||
|
- Apply the narrow two-file button patch and regression, adapt affected native scenarios, validate against the latest layout/help text, and refresh only the trial Renderer. Right header toggle controls consultationOpen with matching accessible labels/state; opening/closing preserves selected teacher, work/chat tab, history and drafts and sends no prompt. No prompt-configuration work, formal installation, cloud change or remote push.
|
||||||
|
|
||||||
|
- Integrated the supplied two production-file changes and nonempty-history/draft/no-submit regression without overwriting the latest welcome layout or help label. Updated two affected native scenarios to use the teacher toggle and existing Work/Chat tabs, preserving browser coverage. README records the new header control.
|
||||||
|
- Checks: typecheck, 91 relevant unit tests in four files, all 8 headless welcome-layout/interaction cases, lint:check (0 errors, 8 existing warnings), scoped changed-test ESLint, build:vite, native E2E --list (20 cases), documentation ownership and whitespace checks passed. Full headed Electron fixture not run to avoid desktop focus changes.
|
||||||
|
- Live Renderer index-Cgp0q7I7.js on unchanged Main PID 62668: background click sequence verified expanded/collapsed labels and aria state, dock removal/reopening, preserved current tab/teacher and both current draft values, unchanged operation history, all 13 stored drafts and 帮我整体看🧠. Current live teacher has no messages; nonempty teacher-history preservation is covered by the integration unit regression. Restored the initial closed dock. Opening the dock only reads existing data; no test prompt was submitted.
|
||||||
|
- Native background timers initially delayed the click-verification script. Temporarily disabled webContents background throttling to finish the pending verification, then restored the original throttling setting. No window activation; focus monitor and live checks remained unfocused. Temporary inspector is closed after finalization. Evidence: trial teacher-dock-toggle-integrated-20260929/.
|
||||||
|
- Welcome centering remains 0px error on both axes, invitation gap 12px and composer bottom gap 16px; auth identity and route preserved, no page alerts. Empty-work commit 0167d36 remains separate and was not live before this update. Keep this trial worktree; no main merge/push or cloud/prompt edits.
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p
|
|||||||
- 本地读取使用连续分页:目录按层展开,默认四层、最多六层,跳过 `.gradle` 等生成目录;条目随页面按需产出,深层目录可单独查询;文件和会话默认每页 120 行、最多 400 行,返回连续原文、起始行列与精确 next 游标,列按 Unicode 字符计数、换行统一为 LF。单个云端结果最多 8 KiB,长行也能继续读取;不受文件 UI 的 256 KiB 预览限制。会话索引保留消息 ID 和摘要,按 ID 读取完整公开原文。每题最多十二批、累计成功结果 64 KiB(包含结果元数据),不足 512 字节时收尾;不上传 Pi 原始日志或思考。云端保留智能体对话及收到的片段,完整工程仍在本机。本地旧版模型仍按自身上下文和六批额度缩小结果。
|
- 本地读取使用连续分页:目录按层展开,默认四层、最多六层,跳过 `.gradle` 等生成目录;条目随页面按需产出,深层目录可单独查询;文件和会话默认每页 120 行、最多 400 行,返回连续原文、起始行列与精确 next 游标,列按 Unicode 字符计数、换行统一为 LF。单个云端结果最多 8 KiB,长行也能继续读取;不受文件 UI 的 256 KiB 预览限制。会话索引保留消息 ID 和摘要,按 ID 读取完整公开原文。每题最多十二批、累计成功结果 64 KiB(包含结果元数据),不足 512 字节时收尾;不上传 Pi 原始日志或思考。云端保留智能体对话及收到的片段,完整工程仍在本机。本地旧版模型仍按自身上下文和六批额度缩小结果。
|
||||||
|
|
||||||
- Code 课堂左侧项目栏默认宽 171px,可拖动右边缘在 128–256px 间调整,双击恢复默认并在本机记住宽度;标题栏同步对齐。项目名与会话名超长省略并可悬停查看;右侧咨询栏默认宽 508px,可拖动至 640px,紧凑窗口仍使用覆盖式咨询。
|
- Code 课堂左侧项目栏默认宽 171px,可拖动右边缘在 128–256px 间调整,双击恢复默认并在本机记住宽度;标题栏同步对齐。项目名与会话名超长省略并可悬停查看;右侧咨询栏默认宽 508px,可拖动至 640px,紧凑窗口仍使用覆盖式咨询。
|
||||||
- 客户端没有内置老师/朋友角色。已下发智能体以头像和名称并排显示在标题栏,点击后打开对应智能体的讨论;数量较多时横向滚动,咨询面板不再提供智能体下拉框。名称、头像、简介、欢迎语和推荐问题均由服务端提供。首次使用默认选择运营指定项,各智能体的未发送草稿分别保存,同一账号与同一智能体只有一个持续聊天,跨项目保留消息和文字草稿;不再显示新话题按钮或话题选择。每轮接受时固定项目、操作对话与当前发布版本;旧的未分配项目草稿可展开查看,不自动转给其他智能体。打开或切换只读取配置和历史,不触发模型;咨询栏支持调整宽度和重新打开。
|
- 客户端没有内置老师/朋友角色。已下发智能体以头像和名称并排显示在标题栏,点击后打开对应智能体的讨论;数量较多时横向滚动,咨询面板不再提供智能体下拉框。名称、头像、简介、欢迎语和推荐问题均由服务端提供。首次使用默认选择运营指定项,各智能体的未发送草稿分别保存,同一账号与同一智能体只有一个持续聊天,跨项目保留消息和文字草稿;不再显示新话题按钮或话题选择。每轮接受时固定项目、操作对话与当前发布版本;旧的未分配项目草稿可展开查看,不自动转给其他智能体。打开或切换只读取配置和历史,不触发模型;咨询栏支持调整宽度和重新打开。顶栏最右侧面板按钮用于收起/展开老师对话栏,不切换“操作对话/作品”、不改变所选老师,保留历史及两侧输入草稿,也不发送消息。
|
||||||
- 鼠标悬停或键盘聚焦顶部智能体入口时,以奶油色圆角卡片显示该智能体的发布介绍;离开、Escape 或点击入口后收起,没有介绍则不显示空卡片。咨询栏不再显示重复的头像、名称标题条,也不显示介绍及新话题、展开、关闭三个图标;老师身份保留在顶部入口及消息署名中。智能体生成的追加问题以淡蓝、奶油黄、淡紫卡片依次承载,正文 16px、行高 1.8,保留整卡点击发送与忙碌时禁用。回复下方不再显示重复的输入引导或省略早期消息条数;输入框提示为“和老师聊聊”。再次点击当前入口或在咨询栏按 Escape 可收起,切换其他入口仍直接打开对应讨论;拖动边缘调宽保留,已下发智能体仅保留只读旧记录入口。悬浮介绍不会创建话题、调用模型或确认未读消息,作品原生预览同时避让介绍卡和真实主动气泡。
|
- 鼠标悬停或键盘聚焦顶部智能体入口时,以奶油色圆角卡片显示该智能体的发布介绍;离开、Escape 或点击入口后收起,没有介绍则不显示空卡片。咨询栏不再显示重复的头像、名称标题条,也不显示介绍及新话题、展开、关闭三个图标;老师身份保留在顶部入口及消息署名中。智能体生成的追加问题以淡蓝、奶油黄、淡紫卡片依次承载,正文 16px、行高 1.8,保留整卡点击发送与忙碌时禁用。回复下方不再显示重复的输入引导或省略早期消息条数;输入框提示为“和老师聊聊”。再次点击当前入口或在咨询栏按 Escape 可收起,切换其他入口仍直接打开对应讨论;拖动边缘调宽保留,已下发智能体仅保留只读旧记录入口。悬浮介绍不会创建话题、调用模型或确认未读消息,作品原生预览同时避让介绍卡和真实主动气泡。
|
||||||
- 主动发言采用紧贴小头像的短气泡;只有存在真实主动消息或运营欢迎语时才显示这组浮层,收起消息时头像和气泡一起消失,顶部入口仍保留。长消息在气泡中最多显示三行,点击接回原智能体对话查看全文。不提供“智能体偶尔来看看”开关或本地模拟巡看控制,既有自动跟进与真实消息接收逻辑保留。作品原生预览对整组气泡和头像测量避让,避免图片被原生页面遮住。
|
- 主动发言采用紧贴小头像的短气泡;只有存在真实主动消息或运营欢迎语时才显示这组浮层,收起消息时头像和气泡一起消失,顶部入口仍保留。长消息在气泡中最多显示三行,点击接回原智能体对话查看全文。不提供“智能体偶尔来看看”开关或本地模拟巡看控制,既有自动跟进与真实消息接收逻辑保留。作品原生预览对整组气泡和头像测量避让,避免图片被原生页面遮住。
|
||||||
- 新的单会话入口停止旧版前端定时跟进派发;项目主动观察由独立功能衔接,不能重新创建可见话题。未下发目录的旧版入口仍保留原跟进合同:前端每 5 分钟请求一次智能体跟进;窗口隐藏、来源归档、主对话正在执行、智能体正在回复或学生正在智能体栏写草稿时延后。Main 再检查在线启停、来源归属、项目级冷却和已完成文本指纹,未变上下文按下述十五分钟冷却处理。跟进沿用运营模型、已发布 Skills 与当前智能体话题,生成符合所选配置的具体建议或引导;以主动智能体发言持久化,不伪造学生消息。模型调用沿用智能体计费规则。
|
- 新的单会话入口停止旧版前端定时跟进派发;项目主动观察由独立功能衔接,不能重新创建可见话题。未下发目录的旧版入口仍保留原跟进合同:前端每 5 分钟请求一次智能体跟进;窗口隐藏、来源归档、主对话正在执行、智能体正在回复或学生正在智能体栏写草稿时延后。Main 再检查在线启停、来源归属、项目级冷却和已完成文本指纹,未变上下文按下述十五分钟冷却处理。跟进沿用运营模型、已发布 Skills 与当前智能体话题,生成符合所选配置的具体建议或引导;以主动智能体发言持久化,不伪造学生消息。模型调用沿用智能体计费规则。
|
||||||
|
|||||||
@@ -775,9 +775,8 @@ export function CodingChatPanel({
|
|||||||
onRecover={async () => {
|
onRecover={async () => {
|
||||||
if (targetConversationId) await handleRecovery(codingRecovery(runtimeError?.code).action, false);
|
if (targetConversationId) await handleRecovery(codingRecovery(runtimeError?.code).action, false);
|
||||||
}}
|
}}
|
||||||
browserOpen={agentBrowserOpen}
|
consultationAvailable={Boolean(activeProject)}
|
||||||
browserAvailable={Boolean(activeProject)}
|
onToggleConsultation={() => setConsultationOpen((current) => !current)}
|
||||||
onToggleBrowser={() => setAgentBrowserOpen((current) => !current)}
|
|
||||||
projectName={activeProject?.name}
|
projectName={activeProject?.name}
|
||||||
consultationOpen={consultationOpen}
|
consultationOpen={consultationOpen}
|
||||||
teacherEntry={activeProject ? <TeacherCompanion companion={teacherCompanion} open={consultationOpen} onOpen={openTeacher} onToggle={toggleTeacher} bubbleRef={setTeacherBubble} introductionRef={setTeacherIntroduction} /> : undefined}
|
teacherEntry={activeProject ? <TeacherCompanion companion={teacherCompanion} open={consultationOpen} onOpen={openTeacher} onToggle={toggleTeacher} bubbleRef={setTeacherBubble} introductionRef={setTeacherIntroduction} /> : undefined}
|
||||||
|
|||||||
@@ -50,12 +50,11 @@ export function CodingConversationHeader({
|
|||||||
onRename,
|
onRename,
|
||||||
onAbort,
|
onAbort,
|
||||||
onRecover,
|
onRecover,
|
||||||
browserOpen = false,
|
consultationAvailable = false,
|
||||||
browserAvailable = false,
|
onToggleConsultation,
|
||||||
onToggleBrowser,
|
|
||||||
onAskTeacher,
|
onAskTeacher,
|
||||||
teacherEntry,
|
teacherEntry,
|
||||||
consultationOpen,
|
consultationOpen = false,
|
||||||
projectName,
|
projectName,
|
||||||
}: {
|
}: {
|
||||||
conversation: CodingConversationMetadata | null;
|
conversation: CodingConversationMetadata | null;
|
||||||
@@ -63,9 +62,8 @@ export function CodingConversationHeader({
|
|||||||
onRename(title: string): Promise<void>;
|
onRename(title: string): Promise<void>;
|
||||||
onAbort(): Promise<void>;
|
onAbort(): Promise<void>;
|
||||||
onRecover(): Promise<void>;
|
onRecover(): Promise<void>;
|
||||||
browserOpen?: boolean;
|
consultationAvailable?: boolean;
|
||||||
browserAvailable?: boolean;
|
onToggleConsultation?(): void;
|
||||||
onToggleBrowser?(): void;
|
|
||||||
onAskTeacher?(): void;
|
onAskTeacher?(): void;
|
||||||
teacherEntry?: ReactNode;
|
teacherEntry?: ReactNode;
|
||||||
consultationOpen?: boolean;
|
consultationOpen?: boolean;
|
||||||
@@ -134,18 +132,19 @@ export function CodingConversationHeader({
|
|||||||
{teacherEntry}
|
{teacherEntry}
|
||||||
{!teacherEntry && onAskTeacher && <button type="button" aria-label="智能体" aria-expanded={consultationOpen} aria-controls="coding-consultation-dock"
|
{!teacherEntry && onAskTeacher && <button type="button" aria-label="智能体" aria-expanded={consultationOpen} aria-controls="coding-consultation-dock"
|
||||||
className="no-drag shrink-0 rounded-lg px-2 py-1 text-xs" onClick={onAskTeacher}>智能体</button>}
|
className="no-drag shrink-0 rounded-lg px-2 py-1 text-xs" onClick={onAskTeacher}>智能体</button>}
|
||||||
{onToggleBrowser ? (
|
{onToggleConsultation ? (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={iconButtonClass}
|
className={iconButtonClass}
|
||||||
disabled={!browserAvailable}
|
disabled={!consultationAvailable}
|
||||||
aria-label={browserOpen ? '关闭开发浏览器' : '打开开发浏览器'}
|
aria-label={consultationOpen ? '收起老师对话栏' : '展开老师对话栏'}
|
||||||
aria-pressed={browserOpen}
|
aria-expanded={consultationOpen}
|
||||||
title={browserOpen ? '关闭开发浏览器' : '打开开发浏览器'}
|
aria-controls="coding-consultation-dock"
|
||||||
onClick={onToggleBrowser}
|
title={consultationOpen ? '收起老师对话栏' : '展开老师对话栏'}
|
||||||
|
onClick={onToggleConsultation}
|
||||||
>
|
>
|
||||||
{browserOpen
|
{consultationOpen
|
||||||
? <PanelRightClose className="h-4 w-4" aria-hidden="true" />
|
? <PanelRightClose className="h-4 w-4" aria-hidden="true" />
|
||||||
: <PanelRightOpen className="h-4 w-4" aria-hidden="true" />}
|
: <PanelRightOpen className="h-4 w-4" aria-hidden="true" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1470,11 +1470,12 @@ test('Windows Code titlebar keeps browser actions clear of the logo and window c
|
|||||||
if (width === 1280 && zoom === 1) {
|
if (width === 1280 && zoom === 1) {
|
||||||
await page.screenshot({ path: test.info().outputPath('windows-code-titlebar.png') });
|
await page.screenshot({ path: test.info().outputPath('windows-code-titlebar.png') });
|
||||||
}
|
}
|
||||||
await header.getByRole('button', { name: '打开开发浏览器' }).click();
|
await header.getByRole('button', { name: '展开老师对话栏' }).click();
|
||||||
await expect(page.getByTestId('agent-browser-panel')).toBeVisible();
|
await expect(page.locator('#coding-consultation-dock')).toBeVisible();
|
||||||
|
await expect(page.getByRole('tab', { name: '操作对话' })).toHaveAttribute('aria-selected', 'true');
|
||||||
await assertChromeClear();
|
await assertChromeClear();
|
||||||
await header.getByRole('button', { name: '关闭开发浏览器' }).click();
|
await header.getByRole('button', { name: '收起老师对话栏' }).click();
|
||||||
await expect(page.getByTestId('agent-browser-panel')).toHaveCount(0);
|
await expect(page.locator('#coding-consultation-dock')).toHaveCount(0);
|
||||||
}
|
}
|
||||||
await page.getByTestId('titlebar-sidebar-toggle').click();
|
await page.getByTestId('titlebar-sidebar-toggle').click();
|
||||||
await assertChromeClear();
|
await assertChromeClear();
|
||||||
@@ -1537,14 +1538,19 @@ test('PI feature UI isolates Conversations and exposes queue, interaction, model
|
|||||||
await expect(conversationHeader.getByRole('button', { name: '归档' })).toHaveCount(0);
|
await expect(conversationHeader.getByRole('button', { name: '归档' })).toHaveCount(0);
|
||||||
await expect(conversationHeader.getByRole('button', { name: '智能体设置' })).toHaveCount(0);
|
await expect(conversationHeader.getByRole('button', { name: '智能体设置' })).toHaveCount(0);
|
||||||
await expect(conversationHeader.getByRole('button', { name: '打开编程工具' })).toHaveCount(0);
|
await expect(conversationHeader.getByRole('button', { name: '打开编程工具' })).toHaveCount(0);
|
||||||
const browserToggle = conversationHeader.getByRole('button', { name: '打开开发浏览器' });
|
const teacherToggle = conversationHeader.getByRole('button', { name: '展开老师对话栏' });
|
||||||
await expect(browserToggle).toBeVisible();
|
await expect(teacherToggle).toBeVisible();
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const browserBounds = await browserToggle.boundingBox();
|
const browserBounds = await teacherToggle.boundingBox();
|
||||||
expect(browserBounds).not.toBeNull();
|
expect(browserBounds).not.toBeNull();
|
||||||
expect(browserBounds!.x + browserBounds!.width).toBeLessThanOrEqual(logoBounds!.x);
|
expect(browserBounds!.x + browserBounds!.width).toBeLessThanOrEqual(logoBounds!.x);
|
||||||
}
|
}
|
||||||
await browserToggle.click();
|
await teacherToggle.click();
|
||||||
|
await expect(page.locator('#coding-consultation-dock')).toBeVisible();
|
||||||
|
await expect(page.getByRole('tab', { name: '操作对话' })).toHaveAttribute('aria-selected', 'true');
|
||||||
|
await conversationHeader.getByRole('button', { name: '收起老师对话栏' }).click();
|
||||||
|
await expect(page.locator('#coding-consultation-dock')).toHaveCount(0);
|
||||||
|
await page.getByRole('tab', { name: '作品', exact: true }).click();
|
||||||
await expect(page.getByTestId('agent-browser-panel')).toBeVisible();
|
await expect(page.getByTestId('agent-browser-panel')).toBeVisible();
|
||||||
await expect(page.getByTestId('agent-browser-diagnostics')).toHaveCount(0);
|
await expect(page.getByTestId('agent-browser-diagnostics')).toHaveCount(0);
|
||||||
await expect.poll(async () => {
|
await expect.poll(async () => {
|
||||||
@@ -1554,7 +1560,7 @@ test('PI feature UI isolates Conversations and exposes queue, interaction, model
|
|||||||
expect((await readState(electronApp)).captured.some(
|
expect((await readState(electronApp)).captured.some(
|
||||||
(request) => request.path === '/api/agent-browser/diagnostics',
|
(request) => request.path === '/api/agent-browser/diagnostics',
|
||||||
)).toBe(false);
|
)).toBe(false);
|
||||||
await page.getByRole('button', { name: '关闭开发浏览器' }).last().click();
|
await page.getByRole('tab', { name: '操作对话' }).click();
|
||||||
await expect(page.getByTestId('agent-browser-panel')).toHaveCount(0);
|
await expect(page.getByTestId('agent-browser-panel')).toHaveCount(0);
|
||||||
await expect(page.getByRole('button', { name: '项目设置' })).toHaveCount(1);
|
await expect(page.getByRole('button', { name: '项目设置' })).toHaveCount(1);
|
||||||
await expect(page.getByTestId('project-conversations')).toBeVisible();
|
await expect(page.getByTestId('project-conversations')).toBeVisible();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { AppError } from '@/lib/error-model';
|
import { AppError } from '@/lib/error-model';
|
||||||
import type { ConversationSnapshot } from '@/types/coding-conversation';
|
import type { ConversationSnapshot } from '@/types/coding-conversation';
|
||||||
|
import type { TeacherDefinition, TeacherTopic } from '../../shared/coding-teacher';
|
||||||
import type {
|
import type {
|
||||||
CodingConversationMetadata,
|
CodingConversationMetadata,
|
||||||
CodingProjectAgent,
|
CodingProjectAgent,
|
||||||
@@ -320,6 +321,93 @@ describe('CodingChatPanel first Conversation', () => {
|
|||||||
expect(conversationApi.submit).not.toHaveBeenCalled();
|
expect(conversationApi.submit).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('toggles the teacher dock from the header without changing tabs, drafts, history, or sending prompts', async () => {
|
||||||
|
localStorage.clear();
|
||||||
|
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||||
|
projectApi.config.mockResolvedValue({ project, config });
|
||||||
|
projectApi.conversations.mockResolvedValue([conversation]);
|
||||||
|
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||||
|
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||||
|
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||||
|
const definition: TeacherDefinition = {
|
||||||
|
schema_version: 1, config_id: 'teacher-collapse', teacher_id: 'teacher', name: '设计老师',
|
||||||
|
description: '', avatar_id: 'avatar-01', welcome_message: '聊聊吧', suggested_questions: [],
|
||||||
|
system_prompt: '', skills: [], model: { model_id: 'model', reasoning_choice: { mode: 'default' } },
|
||||||
|
limits: { max_input_tokens: 8000, max_output_tokens: 1000 },
|
||||||
|
};
|
||||||
|
const topic: TeacherTopic = {
|
||||||
|
schemaVersion: 1, revision: 1, id: 'teacher-history', accountId: 'signed-out',
|
||||||
|
projectId: project.id, sourceConversationId: conversation.id, definition, version: 1,
|
||||||
|
createdAt: '2026-09-29T00:00:00.000Z', updatedAt: '2026-09-29T00:00:00.000Z',
|
||||||
|
requests: [{
|
||||||
|
id: 'teacher-reply-1', text: '怎样让规则更清楚?', references: [],
|
||||||
|
createdAt: '2026-09-29T00:00:00.000Z', sourceCursor: { seq: 0, workerGeneration: 0 },
|
||||||
|
sourceCapturedAt: '2026-09-29T00:00:00.000Z', includedSourceMessageIds: [], omittedMessages: 0,
|
||||||
|
status: 'completed', response: '先用一句话说明获胜条件。',
|
||||||
|
projectId: project.id, sourceConversationId: conversation.id, teacherVersion: 1,
|
||||||
|
}],
|
||||||
|
conversation: { agentId: 'teacher-collapse', segmentTurns: 1, discussions: {}, seenThrough: 'teacher-reply-1' },
|
||||||
|
};
|
||||||
|
const { teacherApi } = await import('@/lib/coding-teacher');
|
||||||
|
vi.spyOn(teacherApi, 'config').mockResolvedValue({ enabled: true, published_version: 1, revision: 1, definition });
|
||||||
|
vi.spyOn(teacherApi, 'catalog').mockResolvedValue({ items: [{ teacher_id: 'teacher-collapse', version: 1, definition, is_default: true }] });
|
||||||
|
vi.spyOn(teacherApi, 'conversation').mockResolvedValue({ topic, before: null });
|
||||||
|
vi.spyOn(teacherApi, 'events').mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||||
|
const consultationSend = vi.spyOn(teacherApi, 'sendConversation');
|
||||||
|
const legacySend = vi.spyOn(teacherApi, 'send');
|
||||||
|
const consultationCreate = vi.spyOn(teacherApi, 'create');
|
||||||
|
const browserApi = await import('@/lib/agent-browser');
|
||||||
|
const closedBrowser = {
|
||||||
|
browserId: null, projectId: project.id, projectPath: null, state: 'closed' as const,
|
||||||
|
generation: 0, url: '', title: '', visible: false, bounds: null,
|
||||||
|
canGoBack: false, canGoForward: false, eventCursor: 0,
|
||||||
|
};
|
||||||
|
vi.spyOn(browserApi, 'getAgentBrowserState').mockResolvedValue(closedBrowser);
|
||||||
|
vi.spyOn(browserApi, 'closeAgentBrowser').mockResolvedValue(closedBrowser);
|
||||||
|
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||||
|
render(<CodingChatPanel />);
|
||||||
|
const composer = within(await screen.findByTestId('coding-message-composer')).getByRole('textbox');
|
||||||
|
fireEvent.change(composer, { target: { value: '操作对话还没写完' } });
|
||||||
|
await waitFor(() => expect(screen.getByRole('button', { name: '智能体', exact: true })).toHaveTextContent('设计老师'));
|
||||||
|
const chatTab = screen.getByRole('tab', { name: '操作对话' });
|
||||||
|
const workTab = screen.getByRole('tab', { name: '作品', exact: true });
|
||||||
|
const toggle = screen.getByRole('button', { name: '展开老师对话栏' });
|
||||||
|
expect(toggle).toHaveAttribute('aria-expanded', 'false');
|
||||||
|
expect(toggle).toHaveAttribute('aria-controls', 'coding-consultation-dock');
|
||||||
|
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
expect(screen.getByRole('button', { name: '收起老师对话栏' })).toHaveAttribute('aria-expanded', 'true');
|
||||||
|
expect(document.getElementById('coding-consultation-dock')).toBeVisible();
|
||||||
|
expect(chatTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
expect(composer).toBeVisible();
|
||||||
|
expect(await screen.findByText('先用一句话说明获胜条件。')).toBeVisible();
|
||||||
|
const teacherInput = screen.getByRole('textbox', { name: '向智能体提问' });
|
||||||
|
await waitFor(() => expect(teacherInput).toBeEnabled());
|
||||||
|
fireEvent.change(teacherInput, { target: { value: '老师对话也还没写完' } });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '收起老师对话栏' }));
|
||||||
|
expect(document.getElementById('coding-consultation-dock')).toBeNull();
|
||||||
|
expect(screen.getByRole('button', { name: '展开老师对话栏' })).toHaveAttribute('aria-expanded', 'false');
|
||||||
|
expect(chatTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
expect(composer).toHaveValue('操作对话还没写完');
|
||||||
|
|
||||||
|
fireEvent.click(workTab);
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '展开老师对话栏' }));
|
||||||
|
expect(workTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
expect(screen.getByRole('tabpanel', { name: '作品', exact: true })).toBeVisible();
|
||||||
|
expect(screen.getByRole('textbox', { name: '向智能体提问' })).toHaveValue('老师对话也还没写完');
|
||||||
|
expect(await screen.findByText('先用一句话说明获胜条件。')).toBeVisible();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '收起老师对话栏' }));
|
||||||
|
expect(workTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
expect(screen.queryByRole('textbox', { name: '向智能体提问' })).not.toBeInTheDocument();
|
||||||
|
fireEvent.click(chatTab);
|
||||||
|
expect(composer).toHaveValue('操作对话还没写完');
|
||||||
|
expect(consultationCreate).not.toHaveBeenCalled();
|
||||||
|
expect(consultationSend).not.toHaveBeenCalled();
|
||||||
|
expect(legacySend).not.toHaveBeenCalled();
|
||||||
|
expect(conversationApi.submit).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('archives the last conversation without creating another, then restores the same running conversation', async () => {
|
it('archives the last conversation without creating another, then restores the same running conversation', async () => {
|
||||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||||
projectApi.config.mockResolvedValue({ project, config });
|
projectApi.config.mockResolvedValue({ project, config });
|
||||||
|
|||||||
Reference in New Issue
Block a user