From 23c98ea94eb8a782c0c0982871b37e32b0100d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Mon, 28 Sep 2026 19:48:08 +0800 Subject: [PATCH 01/19] docs: record latest temporary app update verification --- .../20260928-merge-reply-cleanup-eb8d39b3.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md index eb041382..227f01f1 100644 --- a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md +++ b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md @@ -72,3 +72,21 @@ - `typecheck`, full `lint:check` (0 errors / 8 prior warnings), `build:vite`, `git diff --check` and required documentation structure passed. Task drift retains the same 5 unchanged imported source-document diagnostics from the previous integration; current follow-up modifies only this owned record plus the 3 listed test/metadata files relative to `405846c`. - Impact: stale expectations made the full validation command fail although the intended auth projection and v7 extension were operating correctly; stale release-proof metadata could mislabel an artifact report. Repair restores meaningful release validation without downgrading runtime behavior. Formal package verification remains the packaging owner's responsibility. - Continue the authorized repository handoff with a normal fast-forward push to main after commit; no app restart, package, cloud publication, real model request or subagent creation. + +## Follow-up: Update Existing Temporary Application + +- User now explicitly requests the latest project on the temporary application; this supersedes this task's previous no-restart scope only for the temporary app. No subagents, foreground activation, production installation changes, cloud publication or intentional model requests. +- Same-task ownership re-established; source and remote main are `b645cfa`. Reuse the previously verified source/build and unchanged project/architecture/gates; own task and current peer scopes read. Installation peer owns formal signed-app validation, while this update targets only the already established temporary bundle/profile/port. No semantic conflict. Context gate Passed. +- Existing trial: PID 36109, root `/var/folders/mx/ltbgwsks5_n8zllj69p_wbcw0000gn/T/makelore-trial-tdq_nuah`, Host API 54599, same isolated user-data. It still runs older Main 9adab45 and Renderer 784fa8c plus a style overlay. Formal `/Applications/Makelore.app` PID 40478 stays untouched. +- Plan: read-only inspect trial readiness/active requests and preserve its drafts/profile; prepare an ignored background-only bootstrap for the verified current build, gracefully stop only the exact trial process, back up the retained profile and restart the same bundle/profile/port without focus; verify loaded Main/Renderer, new UI/protocol and preserved data. Do not rewrite foreign source worktrees. + + +### Temporary Application Outcome And Verification + +- Updated the existing `Makelore Teacher Trial.app` in place to verified source `b645cfa312ca23e9a44758211a43f4934a44a70f`, both Main and Renderer; old PID 36109 quit gracefully and current PID 62668 serves the same Host API port 54599. The bootstrap and build are ignored local outputs in this owned checkout. Formal app PID 40478 remains unchanged. +- Before stopping, verified no active coding or teacher request, saved window/storage state and copied the full isolated profile after exit. Backups, build hashes and verification artifacts are under the trial root's `latest-main-update-b645cfa/`; no profile contents or credentials are committed. +- Verified current Renderer asset `index-DKXograg.js`, retained route `#/chat`, same authenticated identity, both projects and active project, and all 10 original teacher draft values. On first startup the Renderer account projection was empty although Main's existing `/api/auth/me` returned the original identity; invoking the existing auth store's normal `init()` restored it without changing login credentials or product authentication code. +- Captured and inspected the actual workspace without activation. Existing teacher selectors are present. Teacher chat pane remains closed; no new model reply was requested, and removal of retired components/old footer is covered by the previously passed source regressions and 28 headless layout cases, not claimed as a new live conversation test. +- Startup and window restoration used background launch and native inactive showing. NSWorkspace monitoring during both phases recorded no trial foreground activation; final window is visible and unfocused. Closed the temporary Node inspector; port 9229 has no listener while port 54599 still belongs to the current trial PID. +- No product source changes were needed for this update; reuse the immediately preceding passing full tests/typecheck/lint/build. Required document structure and whitespace checks pass. The same five unchanged source-task documents remain inherited drift findings relative to the task's original pre-merge base; only this owned record changes in this follow-up. +- Retain this checkout because the live temporary app now executes its compiled build. This local update record does not change the already pushed product revision or formal packaging handoff. From 3f5d1a8b65784ec42e51ced1b9e04f96c6843c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 11:47:43 +0800 Subject: [PATCH 02/19] =?UTF-8?q?fix(ui):=20rename=20teacher=20help=20butt?= =?UTF-8?q?ons=20to=20=E5=B8=AE=E6=88=91=E7=9C=8B=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260928-merge-reply-cleanup-eb8d39b3.md | 17 ++++++ README.md | 4 +- src/pages/Chat/TeacherChatPanel.tsx | 6 +-- tests/e2e/pi-coding-first-chat.spec.ts | 4 +- tests/e2e/teacher-reply-layout.spec.ts | 10 ++-- tests/unit/coding-teacher-ui.test.tsx | 54 +++++++++---------- 6 files changed, 56 insertions(+), 39 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md index 227f01f1..863c0c4a 100644 --- a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md +++ b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md @@ -90,3 +90,20 @@ - Startup and window restoration used background launch and native inactive showing. NSWorkspace monitoring during both phases recorded no trial foreground activation; final window is visible and unfocused. Closed the temporary Node inspector; port 9229 has no listener while port 54599 still belongs to the current trial PID. - No product source changes were needed for this update; reuse the immediately preceding passing full tests/typecheck/lint/build. Required document structure and whitespace checks pass. The same five unchanged source-task documents remain inherited drift findings relative to the task's original pre-merge base; only this owned record changes in this follow-up. - Retain this checkout because the live temporary app now executes its compiled build. This local update record does not change the already pushed product revision or formal packaging handoff. + + +## Follow-up: Rename The Teacher Help Button + +- User requests changing the current page's “继续看看” button to exactly “帮我看看”. Continue this same delivered UI/trial follow-up in its owned checkout; no subagents or desktop activation. +- Project Context Loaded: official start/status verify task ID, feature mode, branch, worktree and original base above. Entry, own record and peer scopes rechecked; unchanged positioning/current-state/architecture/teacher boundaries from the preceding turn reused. Active peers concern formal signed installation and an unfinished language skill; neither owns this trial Renderer label. No semantic conflict. Gate Passed. +- Scope/plan: replace the two teacher help-entry labels and the fresh help request's matching visible text, retain their playful style, handler and retry/history protections; update existing UI/E2E expectations, run focused checks and required production compile, then refresh the temporary page without foreground activation if no request/draft would be interrupted. No cloud configuration or formal-app change. + + +### Button Rename Outcome And Verification + +- Both composer-help and failed/interrupted suggestion retry now display exactly “帮我看看”; fresh suggestion requests carry the matching text. Existing styles, intent, pending-request identity, historical request text, references and draft protection are unchanged. README and existing unit/layout/Electron expectations reflect the new copy. +- Typecheck, ESLint on all four changed TS/TSX files, `git diff --check` and production `build:vite` passed. Existing UI suite passed 71 tests; three targeted headless browser cases passed (retry plus empty-composer controls at 319px/508px), including click payload, draft retention and positioning. No new tests or full suite rerun were needed for this text-only change; native foreground E2E was not launched. +- Updated the running trial Renderer without restarting Main PID 62668. Preflight found no text in current textareas, no stop controls and no active Pi workers. Backed up compiled assets and pre-refresh UI storage under trial `help-label-update-20260929/`; retained old Main lazy-load assets and the background launcher. +- Actual loaded Renderer is `index-DARbkmvA.js`, containing the new label and no “继续看看👀”. Route remains `#/chat`; all 10 persisted teacher drafts are unchanged. The trial was already hidden and signed out before this edit (pre-refresh persisted user/access token absent and refresh unavailable); it stays hidden and signed out, with `/api/auth/me` returning 401. No login was fabricated or restored, and no live teacher request was sent. The component behavior was checked in headless fixtures instead. +- Focus monitor recorded only the existing foreground WeChat process, no trial activation. Temporary inspector closed after verification. Formal installation remains untouched. This label follow-up is committed locally; no additional remote publication requested this turn. +- Required document structure passes; inherited drift diagnostic is still limited to the same five source task records imported by the prior merge, not new foreign edits. Retain this checkout for the live trial build. diff --git a/README.md b/README.md index 7d1f6c91..e95248f4 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,9 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - 主动发言采用紧贴小头像的短气泡;只有存在真实主动消息或运营欢迎语时才显示这组浮层,收起消息时头像和气泡一起消失,顶部入口仍保留。长消息在气泡中最多显示三行,点击接回原智能体对话查看全文。不提供“智能体偶尔来看看”开关或本地模拟巡看控制,既有自动跟进与真实消息接收逻辑保留。作品原生预览对整组气泡和头像测量避让,避免图片被原生页面遮住。 - 新的单会话入口停止旧版前端定时跟进派发;项目主动观察由独立功能衔接,不能重新创建可见话题。未下发目录的旧版入口仍保留原跟进合同:前端每 5 分钟请求一次智能体跟进;窗口隐藏、来源归档、主对话正在执行、智能体正在回复或学生正在智能体栏写草稿时延后。Main 再检查在线启停、来源归属、项目级冷却和已完成文本指纹,未变上下文按下述十五分钟冷却处理。跟进沿用运营模型、已发布 Skills 与当前智能体话题,生成符合所选配置的具体建议或引导;以主动智能体发言持久化,不伪造学生消息。模型调用沿用智能体计费规则。 - 进入项目时,智能体头像旁先显示运营发布的欢迎语气泡,不调用模型、不声称已检查项目;点击智能体进入咨询时立即收起欢迎语,并按账号记住已查看,关闭、重新打开、切换项目或刷新后不重复弹出;主动收起也保留同样记录。真实的未读主动建议优先替换欢迎语,打开咨询不会自动将这些消息标为已读。主动发言在头像旁直接显示气泡,不抢焦点、不盖住作品;作品区按气泡与原生预览的实际交叠范围避让,已有工具栏高度计入计算,气泡位于独立智能体栏时不额外留白,窗口/分栏尺寸变化和收起气泡时实时归还空间;“和智能体聊聊”接回同一段咨询,“等会儿聊”收起并保留历史。已有未处理气泡时不堆叠新邀请;咨询已展开时仍显示真实未读主动消息气泡,直到学生主动点击查看或收起,期间的其他回复不会吞掉未读邀请。五分钟检查计时在刷新和项目切换后保留;智能体咨询里的新讨论也算进展,即使操作对话没有变化也会继续跟进;上下文完全未变时放缓到十五分钟一次,不永久停止。事件连接丢失时,每十五秒只读同步未完成回复,收到结果后停止轮询,不重复调用模型。失败如实提示,不以本地文案替代模型回复。已接受请求恢复使用原身份,退出账号和来源删除仍沿用 Main 中止边界。 -- 智能体支持自由提问;输入框为空时,底部显示暖黄色小按钮“继续看看👀”,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“继续看看👀”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 +- 智能体支持自由提问;输入框为空时,底部显示暖黄色小按钮“帮我看看”,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“帮我看看”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 - 老师咨询只展示老师正文和可点击的快捷回复卡片,学生也可在“和老师聊聊”输入框自由输入。点击卡片原样发送该回复,保留当前输入草稿;旧话题的数据不会恢复想法板、结构图、流程图或对照表及其操作。 -- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在“继续看看👀”、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 +- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在“帮我看看”、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 - Main 对咨询回复统一使用 `{reply, quickReplies}`,本地读取工具的前言和云端非最终片段不当作最终回答;完整解析后展示正文和卡片,主动进展提醒仍直接展示正文。普通 Markdown、JSON 数据和代码示例保持正文。兼容读取旧 `{intro, questions}` 和带 `tool` 的回复,只提取正文与快捷回复,不恢复或更新组件。新输出超过 3 条快捷回复时保留完整正文、提示格式错误并保留原始回答,不展示超额卡片或静默截取;历史中已有的更多卡片在读取与后续保存时仍原样保留。独立资源上限只用于防护,不做旧式字数截断;格式错误的原文默认折叠、字面显示,不再次送进模型。 - 回复中的引号或换行格式有误时,仅在正文边界明确、修复后整体合法的情况下恢复完整文字,不把半句话当成完整回答。附加建议损坏时仍展示完整正文;正文无法确认完整时显示“这次回复未能完整显示”,由学生点击“重新回答”,沿用原问题、引用与来源;持续聊天须回到原项目和操作对话后重试,新一轮按当前发布版本执行,旧话题仍使用绑定版本,保留当前草稿,连续点击不重复发送。打开聊天或旧话题会从保留的原文中在本地恢复可确认的正文,不调用模型,不因显示修复而改写磁盘记录;原始内容继续折叠保留。不完整的回答和建议不进入后续模型上下文。 - 咨询正文支持 Markdown 标题、列表、表格、代码围栏、HTTP(S) 链接/图片和数学公式,长代码与表格在栏内横向滚动,不执行 HTML。云端正文维持 string 合同,未知对象不会猜测转成回答。工具活动只从云端主线程的类型化工具事件及 Main 本地读取过程获得,按本次问题/工具调用身份合并名称和状态,跨云端暂停、续接仍只计一次;暂停读取不算失败,状态以实际读取结果或问题终态为准,单独折叠显示;参数、结果原文和内部错误不混入回复,思考内容不作为正文。客户端仅保存自己实际收到的工具状态,不能补回旧历史或断线期间已经过期的事件。 diff --git a/src/pages/Chat/TeacherChatPanel.tsx b/src/pages/Chat/TeacherChatPanel.tsx index 215616ed..2feac588 100644 --- a/src/pages/Chat/TeacherChatPanel.tsx +++ b/src/pages/Chat/TeacherChatPanel.tsx @@ -471,7 +471,7 @@ export function TeacherChatPanel({ (request) => request.status === 'preparing' || request.status === 'running' ); const helpUnavailable = busy || !topicEnabled || Boolean(running); - const askForSuggestions = () => void send({ text: '继续看看👀', intent: 'suggestions' }); + const askForSuggestions = () => void send({ text: '帮我看看', intent: 'suggestions' }); const changeText = (next: string) => { setText(next); persistDraft(next, references, pending.current); }; const removeReferences = () => { setReferences([]); persistDraft(text, [], pending.current); }; const questions = definition?.suggested_questions ?? []; @@ -541,7 +541,7 @@ export function TeacherChatPanel({ {request.suggestedQuestions?.map((question, index) => )} } - {['failed', 'cancelled', 'interrupted'].includes(request.status) && } + {['failed', 'cancelled', 'interrupted'].includes(request.status) && } : request.response ? :
{['running', 'preparing'].includes(request.status) ? {label}正在想… : ['cancelled', 'interrupted'].includes(request.status) ? '这次先停在这里。想继续时可以再说说。' : ''}
} {request.intent !== 'suggestions' && request.status === 'completed' && !request.replyIncomplete && Boolean(request.suggestedQuestions?.length) &&
{request.suggestedQuestions?.map((question, index) => )} @@ -585,7 +585,7 @@ export function TeacherChatPanel({ className="min-h-[60px] w-full resize-none border-0 bg-transparent text-[13px] leading-6 outline-none placeholder:text-muted-foreground/65" />
- {!draftRevision && !text.trim() && } + {!draftRevision && !text.trim() && }
{running ? : } diff --git a/tests/e2e/pi-coding-first-chat.spec.ts b/tests/e2e/pi-coding-first-chat.spec.ts index 58b94c8b..8f7549c6 100644 --- a/tests/e2e/pi-coding-first-chat.spec.ts +++ b/tests/e2e/pi-coding-first-chat.spec.ts @@ -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')}); diff --git a/tests/e2e/teacher-reply-layout.spec.ts b/tests/e2e/teacher-reply-layout.spec.ts index 69b53bce..a45e3771 100644 --- a/tests/e2e/teacher-reply-layout.spec.ts +++ b/tests/e2e/teacher-reply-layout.spec.ts @@ -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,7 @@ 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('帮我看看'); await expect(hint).toHaveCSS('background-color', 'rgb(255, 242, 201)'); const initialSend = (await send.boundingBox())!; const hintBounds = (await hint.boundingBox())!; @@ -109,9 +109,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('留着我自己的草稿'); diff --git a/tests/unit/coding-teacher-ui.test.tsx b/tests/unit/coding-teacher-ui.test.tsx index 6df59675..188f4741 100644 --- a/tests/unit/coding-teacher-ui.test.tsx +++ b/tests/unit/coding-teacher-ui.test.tsx @@ -496,13 +496,13 @@ describe('teacher side chat', () => { })); render(); const input = screen.getByLabelText('向智能体提问'); - const help = () => screen.getByText('继续看看👀', { selector: 'form .consultation-hint' }); + const help = () => screen.getByText('帮我看看', { 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('帮我看看', { 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('帮我看看', { 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('帮我看看', { 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(); - await waitFor(() => expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeEnabled()); - fireEvent.click(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })); + await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled()); + fireEvent.click(screen.getByText('帮我看看', { 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(); await waitFor(() => expect(screen.getByLabelText('向智能体提问')).toBeEnabled()); expect(screen.getByLabelText('向智能体提问')).toHaveValue('我还没写完的问题'); - expect(screen.queryByText('继续看看👀', { selector: 'form .consultation-hint' })).not.toBeInTheDocument(); + expect(screen.queryByText('帮我看看', { 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('帮我看看', { 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(); - await waitFor(() => expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeEnabled()); + await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled()); expect(api.send).not.toHaveBeenCalled(); - fireEvent.click(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })); + fireEvent.click(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })); await screen.findByText('网络暂不可用'); expect(JSON.parse(JSON.stringify(api.send.mock.calls[0][2]))).toEqual(original); view.unmount(); render(); - await waitFor(() => expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeEnabled()); + await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled()); expect(api.send).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })); + fireEvent.click(screen.getByText('帮我看看', { 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(); - await waitFor(() => expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeEnabled()); + await waitFor(() => expect(screen.getByText('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled()); expect(api.send).not.toHaveBeenCalled(); - fireEvent.click(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })); + fireEvent.click(screen.getByText('帮我看看', { 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('帮我看看', { 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(); - await waitFor(() => expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeEnabled()); + await waitFor(() => expect(screen.getByText('帮我看看', { 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('帮我看看', { 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('帮我看看', { selector: '.consultation-retry' })).toBeEnabled()); expect(screen.getByLabelText('向智能体提问')).toHaveValue('我的自由提问继续留着'); expect(api.send).toHaveBeenCalledTimes(1); - fireEvent.click(screen.getByText('继续看看👀', { selector: '.consultation-retry' })); + fireEvent.click(screen.getByText('帮我看看', { 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('帮我看看', { selector: 'form .consultation-hint' })).toBeEnabled(); view.unmount(); render(); 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('帮我看看', { 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(); - const hint = () => screen.getByText('继续看看👀', { selector: 'form .consultation-hint' }); + const hint = () => screen.getByText('帮我看看', { 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(); await waitFor(() => expect(screen.getByText('智能体暂未开放,历史仍可查看。')).toBeVisible()); const input = screen.getByLabelText('向智能体提问'); - expect(screen.getByText('继续看看👀', { selector: 'form .consultation-hint' })).toBeDisabled(); + expect(screen.getByText('帮我看看', { 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('帮我看看', { selector: 'form .consultation-hint' })).not.toBeInTheDocument(); fireEvent.keyDown(input, { key: 'Enter' }); expect(api.send).not.toHaveBeenCalled(); }); From e4b99d5e3200194d347f020e52655f921c725895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 11:57:29 +0800 Subject: [PATCH 03/19] fix(ui): clarify teacher help prompt and fit narrow panels --- .../20260928-merge-reply-cleanup-eb8d39b3.md | 15 ++++++ README.md | 4 +- src/pages/Chat/TeacherChatPanel.tsx | 8 +-- tests/e2e/pi-coding-first-chat.spec.ts | 4 +- tests/e2e/teacher-reply-layout.spec.ts | 11 ++-- tests/unit/coding-teacher-ui.test.tsx | 54 +++++++++---------- 6 files changed, 56 insertions(+), 40 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md index 863c0c4a..467fca80 100644 --- a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md +++ b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md @@ -107,3 +107,18 @@ - Actual loaded Renderer is `index-DARbkmvA.js`, containing the new label and no “继续看看👀”. Route remains `#/chat`; all 10 persisted teacher drafts are unchanged. The trial was already hidden and signed out before this edit (pre-refresh persisted user/access token absent and refresh unavailable); it stays hidden and signed out, with `/api/auth/me` returning 401. No login was fabricated or restored, and no live teacher request was sent. The component behavior was checked in headless fixtures instead. - Focus monitor recorded only the existing foreground WeChat process, no trial activation. Temporary inspector closed after verification. Formal installation remains untouched. This label follow-up is committed locally; no additional remote publication requested this turn. - Required document structure passes; inherited drift diagnostic is still limited to the same five source task records imported by the prior merge, not new foreign edits. Retain this checkout for the live trial build. + + +## Follow-up: Explicit Unsure-What-To-Ask Button Copy + +- User refines the same two button labels to exactly “不知道问什么,你先帮我看看👀”. Continue this owned UI/trial follow-up without subagents, foreground activation or remote publication. +- Project Context Loaded: official same-task start/status fields verified; own record/current peer scopes rechecked and unchanged entry/project/teacher context reused. Formal signed-app and unspecified language-skill peers do not conflict with this bounded copy change. Gate Passed. +- Plan: change labels and new help request text, preserving historical fixtures/pending payloads; update existing expectations, verify narrow/wide wrapping and stable send-button position, make only necessary layout accommodation, run focused validation/build and refresh the temporary Renderer with state protection. + + +### Longer Button Copy Outcome And Verification + +- Both help entries and fresh suggestion text now use exactly “不知道问什么,你先帮我看看👀”. Preserve previous payloads in legacy fixtures/history and all request/intent/draft safeguards. Narrow buttons wrap after the comma with the second phrase and emoji kept together; the composer action row reserves 68px so hiding the two-line button while typing does not move Send. Existing warm-yellow styling remains. +- Initial narrow-layout regression exposed an 11px Send shift with two-line text. Reserved action height resolves it without changing dispatch. All 71 focused UI tests, typecheck, changed-file ESLint and final production build pass. All 3 targeted headless layout cases pass at 319px/508px, including clipping, position, click payload and draft preservation; inspected narrow screenshot confirms balanced semantic wrapping and attached emoji. +- Refreshed only the existing trial Renderer (Main remains PID 62668). Preflight found empty textareas, no stop controls and an idle Pi worker; saved current UI state/build backups under `help-long-label-update-20260929/`. Actual loaded `index-CaxXl4aZ.js` contains the full new copy. Current route and authenticated identity preserved, all 11 pre-refresh teacher drafts unchanged, window remains visible/unfocused. Consultation pane closes on page refresh; no extra teacher/operation model request was made to reopen it. +- Temporary inspector closed. No formal-app change, model/cloud configuration publication or remote push. Required doc structure and whitespace checks pass; same inherited five foreign-source doc diagnostics remain from the original integration base. Retain checkout for running trial. diff --git a/README.md b/README.md index e95248f4..8fbbe35e 100644 --- a/README.md +++ b/README.md @@ -174,9 +174,9 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - 主动发言采用紧贴小头像的短气泡;只有存在真实主动消息或运营欢迎语时才显示这组浮层,收起消息时头像和气泡一起消失,顶部入口仍保留。长消息在气泡中最多显示三行,点击接回原智能体对话查看全文。不提供“智能体偶尔来看看”开关或本地模拟巡看控制,既有自动跟进与真实消息接收逻辑保留。作品原生预览对整组气泡和头像测量避让,避免图片被原生页面遮住。 - 新的单会话入口停止旧版前端定时跟进派发;项目主动观察由独立功能衔接,不能重新创建可见话题。未下发目录的旧版入口仍保留原跟进合同:前端每 5 分钟请求一次智能体跟进;窗口隐藏、来源归档、主对话正在执行、智能体正在回复或学生正在智能体栏写草稿时延后。Main 再检查在线启停、来源归属、项目级冷却和已完成文本指纹,未变上下文按下述十五分钟冷却处理。跟进沿用运营模型、已发布 Skills 与当前智能体话题,生成符合所选配置的具体建议或引导;以主动智能体发言持久化,不伪造学生消息。模型调用沿用智能体计费规则。 - 进入项目时,智能体头像旁先显示运营发布的欢迎语气泡,不调用模型、不声称已检查项目;点击智能体进入咨询时立即收起欢迎语,并按账号记住已查看,关闭、重新打开、切换项目或刷新后不重复弹出;主动收起也保留同样记录。真实的未读主动建议优先替换欢迎语,打开咨询不会自动将这些消息标为已读。主动发言在头像旁直接显示气泡,不抢焦点、不盖住作品;作品区按气泡与原生预览的实际交叠范围避让,已有工具栏高度计入计算,气泡位于独立智能体栏时不额外留白,窗口/分栏尺寸变化和收起气泡时实时归还空间;“和智能体聊聊”接回同一段咨询,“等会儿聊”收起并保留历史。已有未处理气泡时不堆叠新邀请;咨询已展开时仍显示真实未读主动消息气泡,直到学生主动点击查看或收起,期间的其他回复不会吞掉未读邀请。五分钟检查计时在刷新和项目切换后保留;智能体咨询里的新讨论也算进展,即使操作对话没有变化也会继续跟进;上下文完全未变时放缓到十五分钟一次,不永久停止。事件连接丢失时,每十五秒只读同步未完成回复,收到结果后停止轮询,不重复调用模型。失败如实提示,不以本地文案替代模型回复。已接受请求恢复使用原身份,退出账号和来源删除仍沿用 Main 中止边界。 -- 智能体支持自由提问;输入框为空时,底部显示暖黄色小按钮“帮我看看”,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“帮我看看”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 +- 智能体支持自由提问;输入框为空时,底部显示暖黄色小按钮“不知道问什么,你先帮我看看👀”,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“不知道问什么,你先帮我看看👀”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 - 老师咨询只展示老师正文和可点击的快捷回复卡片,学生也可在“和老师聊聊”输入框自由输入。点击卡片原样发送该回复,保留当前输入草稿;旧话题的数据不会恢复想法板、结构图、流程图或对照表及其操作。 -- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在“帮我看看”、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 +- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在“不知道问什么,你先帮我看看👀”、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 - Main 对咨询回复统一使用 `{reply, quickReplies}`,本地读取工具的前言和云端非最终片段不当作最终回答;完整解析后展示正文和卡片,主动进展提醒仍直接展示正文。普通 Markdown、JSON 数据和代码示例保持正文。兼容读取旧 `{intro, questions}` 和带 `tool` 的回复,只提取正文与快捷回复,不恢复或更新组件。新输出超过 3 条快捷回复时保留完整正文、提示格式错误并保留原始回答,不展示超额卡片或静默截取;历史中已有的更多卡片在读取与后续保存时仍原样保留。独立资源上限只用于防护,不做旧式字数截断;格式错误的原文默认折叠、字面显示,不再次送进模型。 - 回复中的引号或换行格式有误时,仅在正文边界明确、修复后整体合法的情况下恢复完整文字,不把半句话当成完整回答。附加建议损坏时仍展示完整正文;正文无法确认完整时显示“这次回复未能完整显示”,由学生点击“重新回答”,沿用原问题、引用与来源;持续聊天须回到原项目和操作对话后重试,新一轮按当前发布版本执行,旧话题仍使用绑定版本,保留当前草稿,连续点击不重复发送。打开聊天或旧话题会从保留的原文中在本地恢复可确认的正文,不调用模型,不因显示修复而改写磁盘记录;原始内容继续折叠保留。不完整的回答和建议不进入后续模型上下文。 - 咨询正文支持 Markdown 标题、列表、表格、代码围栏、HTTP(S) 链接/图片和数学公式,长代码与表格在栏内横向滚动,不执行 HTML。云端正文维持 string 合同,未知对象不会猜测转成回答。工具活动只从云端主线程的类型化工具事件及 Main 本地读取过程获得,按本次问题/工具调用身份合并名称和状态,跨云端暂停、续接仍只计一次;暂停读取不算失败,状态以实际读取结果或问题终态为准,单独折叠显示;参数、结果原文和内部错误不混入回复,思考内容不作为正文。客户端仅保存自己实际收到的工具状态,不能补回旧历史或断线期间已经过期的事件。 diff --git a/src/pages/Chat/TeacherChatPanel.tsx b/src/pages/Chat/TeacherChatPanel.tsx index 2feac588..6203fbf7 100644 --- a/src/pages/Chat/TeacherChatPanel.tsx +++ b/src/pages/Chat/TeacherChatPanel.tsx @@ -471,7 +471,7 @@ export function TeacherChatPanel({ (request) => request.status === 'preparing' || request.status === 'running' ); const helpUnavailable = busy || !topicEnabled || Boolean(running); - const askForSuggestions = () => void send({ text: '帮我看看', intent: 'suggestions' }); + const askForSuggestions = () => void send({ text: '不知道问什么,你先帮我看看👀', intent: 'suggestions' }); const changeText = (next: string) => { setText(next); persistDraft(next, references, pending.current); }; const removeReferences = () => { setReferences([]); persistDraft(text, [], pending.current); }; const questions = definition?.suggested_questions ?? []; @@ -541,7 +541,7 @@ export function TeacherChatPanel({ {request.suggestedQuestions?.map((question, index) => )}
} - {['failed', 'cancelled', 'interrupted'].includes(request.status) && } + {['failed', 'cancelled', 'interrupted'].includes(request.status) && } : request.response ? :
{['running', 'preparing'].includes(request.status) ? {label}正在想… : ['cancelled', 'interrupted'].includes(request.status) ? '这次先停在这里。想继续时可以再说说。' : ''}
} {request.intent !== 'suggestions' && request.status === 'completed' && !request.replyIncomplete && Boolean(request.suggestedQuestions?.length) &&
{request.suggestedQuestions?.map((question, index) => )} @@ -583,9 +583,9 @@ export function TeacherChatPanel({