diff --git a/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md b/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md index f55adfc8..87a93cb0 100644 --- a/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md +++ b/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md @@ -12,7 +12,7 @@ ## Scope -- Move Yuxi-configured teacher shortcuts above the composer as small pastel buttons with soft shadows; preserve exact configured label/prompt and existing send/voice/model behavior. +- Present Yuxi-configured teacher shortcuts above the composer as compact clickable text; preserve exact configured label/prompt and existing send/voice/model behavior. ## Intent And Constraints @@ -20,7 +20,7 @@ ## Outcome -- Configured teacher shortcuts now sit outside and directly above the composer. Buttons use 13px normal text (same as reply body), pale cream fill, no border and soft shadow; wrapping/overflow/keyboard focus supported. +- Configured teacher shortcuts sit outside and directly above the composer. Latest approved styling uses 13px normal green text, no fill/border/shadow, a small chevron and hover underline; wrapping/overflow/keyboard focus supported. - Adapted product/tests dependency from main 53d5b6b (bb96941); original source task record remains in its source commit, excluded from this feature boundary. Explicit empty arrays hide prompts; absent historical field retains approved fallback. Published label and prompt are unchanged. - Trial Renderer updated in place in PID 32583; Main and empty-work preview overlay remain intact. Live Alice cloud v53 supplies “继续看看咯”. @@ -83,3 +83,16 @@ - Completed the requested size step for generated reply cards only: 14px text, 10px/12px padding, 12px radius, 8px row gap, 14px chevron and 44px minimum target. Three colors, full-width layout and click behavior unchanged; configured prompt buttons retain their previously approved size/style. - Verification: typecheck, full lint (0 errors / 8 existing warnings), source/combined-stage builds and 10 browser reply-history/card interaction cases passed at 319px and 508px. - Hot-swapped trial stylesheet index-CZpr_a7Y.css without reload or focus acquisition. Actual three screenshot cards now measure 45.8px high with 14px text. Native image inspected; storage, current text inputs, operation messages, teacher history and route match before the update. No live model calls. + + +## Text Shortcut Follow-up + +- Same-task ownership resumed at fef3b3a, matching feature task/branch/worktree verified. Reused previously read unchanged project memory and reread peer scope/constraints/promotions. Latest live trial includes Code brand navigation, hidden model selector and grouped introductions; preserve these overlays. No peer semantic conflict with scoped shortcut CSS. Canonical memory remains read-only. Gate passed. +- User requests removing the shortcut background and making it clickable text. Design skill search/reference used for semantic buttons, stable hover and focus feedback; keep existing product palette/typeface rather than introduce a new system. Use 13px muted green text plus existing chevron, transparent background, no border/shadow, hover underline/darker text and visible keyboard focus. Preserve 30px hit height, wrapping and exact cloud labels/prompts. +- Plan: change only shortcut CSS, adapt existing layout assertions and current README. Verify existing narrow/wide shortcut click/keyboard/overflow scenarios, typecheck/lint/build. Copy the latest combined trial stage, apply only this CSS block, and swap its built stylesheet in the running app without remounting chats or changing desktop focus; compare live state before/after. + + +- Completed text treatment: removed fill, border and raised shadow; 13px/400 green text with chevron, darker underlined hover, stable position and keyboard outline. Kept 30px targets, cloud label/prompt, input visibility conditions and sends unchanged. This supersedes the earlier raised-button styling. +- Verification: four existing shortcut/layout scenarios passed at 319px and 508px (including click/prompt contract, eight long labels, focus, hover/pressed/reduced-motion, empty config and input/send position). Typecheck, full lint (0 errors / 8 existing warnings), source and combined trial production builds passed. Inspected browser screenshot. Task-aware docs drift and diff whitespace checks passed. +- Updated only the live stylesheet to index-_6TMWSCY.css in PID 32583; no Renderer reload, Main restart, prompt or native focus call. Copied the latest Code-brand/hidden-model/group-intro composite before applying this CSS so peer work remains. Served index/assets also prepared for subsequent normal reload. Immediate swap remained unfocused. Read-only check confirmed transparent/none/13px/30px rules; route unchanged and no localStorage changes beyond normal auth state. The app loaded 14 messages after the initial empty snapshot and later became focused with its consultation open, so live message/input snapshots were not stationary and are not claimed byte-identical. No history or draft writes were performed. Inspector closed. +- Evidence: /tmp/teacher-text-link-{browser,typecheck,lint,build,trial-build}.log, /tmp/teacher-text-link-results and trial teacher-text-shortcuts-a682d30f/verification.json. No main merge/push or cloud publication in this follow-up. diff --git a/README.md b/README.md index 437bcae8..88e1bc49 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - 咨询正文支持 Markdown 标题、列表、表格、代码围栏、HTTP(S) 链接/图片和数学公式,长代码与表格在栏内横向滚动,不执行 HTML。云端正文维持 string 合同,未知对象不会猜测转成回答。工具活动只从云端主线程的类型化工具事件及 Main 本地读取过程获得,按本次问题/工具调用身份合并名称和状态,跨云端暂停、续接仍只计一次;暂停读取不算失败,状态以实际读取结果或问题终态为准,单独折叠显示;参数、结果原文和内部错误不混入回复,思考内容不作为正文。客户端仅保存自己实际收到的工具状态,不能补回旧历史或断线期间已经过期的事件。 - 云端咨询在 local_context 声明 read_protocol=2;需先部署配套 Yuxi API 和 worker,再升级客户端。云端持久累计读取字节及批次,每轮告知模型剩余额度;预算耗尽后消费最后一批结果,并以 tool_choice=none 要求根据现有证据形成答案和说明缺口。Main 限制实际返回量并拒绝第十三批读取,区分读取达到上限、上下文失效及格式无效。服务端对未声明协议的已安装旧客户端保留原工具参数与六批边界。 - 智能体人设、职责、提示词和 Skills 由发布配置决定。Main 不追加固定教学基线、不生成“小麦”、不清空所选智能体的 Skills,只添加真实工具能力、上下文边界和本轮界面协议。智能体名称叫“老师”“朋友”或“代码顾问”不改变调用路径或权限。 -- 输入框为空时,在输入框外上方显示当前老师已发布的快捷提示;采用与回复正文一致的 13px 常规字重、无边框、浅色底与底部立体阴影的小巧圆角按钮,配有小箭头和悬停/按下反馈,横向排列并自动换行。按 Yuxi 中配置的顺序展示文案,点击直接发送该项实际提示词。Yuxi“编辑 → 快捷提示”支持最多 8 项,每项文案最多 40 字、提示词最多 6000 字;保存并发布后,目录更新即可生效,历史和未发送草稿保留。明确发布空列表隐藏全部按钮;缺少字段的历史版本保留“帮我整体看🧠”。多个长文案在限定高度内滚动,运行中禁止重复发送。上线需先更新 WS 登记接口,再更新 Yuxi 和客户端。 +- 输入框为空时,在输入框外上方显示当前老师已发布的快捷提示;采用与回复正文一致的 13px 常规字重,显示为无底色、无边框、无阴影的绿色可点击文字与小箭头;悬停时文字加深并显示下划线,键盘聚焦有清晰轮廓,保留 30px 点击高度,横向排列并自动换行。按 Yuxi 中配置的顺序展示文案,点击直接发送该项实际提示词。Yuxi“编辑 → 快捷提示”支持最多 8 项,每项文案最多 40 字、提示词最多 6000 字;保存并发布后,目录更新即可生效,历史和未发送草稿保留。明确发布空列表隐藏全部按钮;缺少字段的历史版本保留“帮我整体看🧠”。多个长文案在限定高度内滚动,运行中禁止重复发送。上线需先更新 WS 登记接口,再更新 Yuxi 和客户端。 - `reply.ts` 只声明正文与快捷回复的输出格式,语言、长度、前缀与表达风格由所选智能体的云端配置决定。客户端不再发送选择、生成、转换或维护展示组件的指令。旧话题内的组件状态、快照和解析诊断作为不透明历史数据原样保留,不校验删除、不恢复交互;旧组件修改路由已移除。 - 当前输入是按预算截取的所选操作对话已完成分支文本、明确引用、咨询正文和快捷回复,并支持上述只读原文恢复;旧组件状态、快照和未解析原文不加入模型上下文或咨询历史读取结果。没有跨全部操作会话自动读取,也未向操作 Agent 注入咨询共识。提示词不声称看过未提供的画面或试玩过作品。Yuxi 话题经云端原生执行,旧版本地智能体由 Main 调用计费网关;均不创建本地 Pi 编程 worker。 - 智能体输入裁剪区分两种计量:本地模型以约每 2 个 UTF-8 字节估算 1 Token,并计入工具与读取预留;这不是精确分词或扣费依据,模型上限与实际 usage 仍由服务端拥有。云端沿用 query 字节上限,以完整 JSON 转义后的大小裁剪来源节选。智能体指令、当前问题和明确引用不会被静默裁掉;固定配置或回复协议超限时明确提示调整配置,避免让学生反复缩短短问题或新建话题。完整来源仍可由只读工具按需读取。 diff --git a/src/pages/Chat/classroom-workspace.css b/src/pages/Chat/classroom-workspace.css index 4dffb08f..0501e0c6 100644 --- a/src/pages/Chat/classroom-workspace.css +++ b/src/pages/Chat/classroom-workspace.css @@ -52,14 +52,14 @@ .consultation-retry { display: inline-flex; align-items: center; justify-content: center; align-self: flex-start; min-height: 44px; max-width: 100%; padding: 8px 16px; border: 1.5px solid #e3ce8f; border-radius: 16px 16px 16px 6px; background: #fff2c9; color: #6f5127; box-shadow: 0 3px 0 #e7d7a6, inset 0 1px 0 #fff9e8; font-size: 14px; font-weight: 600; line-height: 24px; cursor: pointer; transition: background-color 160ms ease, border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease; } .consultation-retry:hover:not(:disabled) { background: #ffe9a9; border-color: #d6b76f; } .consultation-retry:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 1px 0 #e7d7a6, inset 0 1px 0 #fff9e8; } -.consultation-shortcuts { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px; max-height: min(192px, 24vh); overflow-y: auto; margin-bottom: 10px; padding: 3px 4px 6px; } -.consultation-hint { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; min-height: 30px; padding: 4px 10px; border: 0; border-radius: 8px; background: #f8e6b7; box-shadow: 0 2px 0 #d8c492, 0 3px 6px #8b75491a; color: #65491f; font-size: 13px; font-weight: 400; line-height: 22px; text-align: left; white-space: normal; overflow-wrap: anywhere; cursor: pointer; transition: background-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; } +.consultation-shortcuts { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 4px 16px; max-height: min(192px, 24vh); overflow-y: auto; margin-bottom: 10px; padding: 3px 4px 6px; } +.consultation-hint { display: inline-flex; align-items: center; gap: 7px; max-width: 100%; min-height: 30px; padding: 4px 2px; border: 0; border-radius: 4px; background: transparent; box-shadow: none; color: #536949; font-size: 13px; font-weight: 400; line-height: 22px; text-align: left; white-space: normal; overflow-wrap: anywhere; text-decoration: underline; text-decoration-color: transparent; text-decoration-thickness: 1px; text-underline-offset: 4px; cursor: pointer; transition: color 150ms ease, text-decoration-color 150ms ease; } .consultation-hint::after { content: ''; width: 5px; height: 5px; flex-shrink: 0; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); opacity: 0.7; } -.consultation-hint:hover:not(:disabled) { background: #f7dfa0; box-shadow: 0 3px 0 #d1b979, 0 5px 8px #8b754924; transform: translateY(-1px); } -.consultation-hint:active:not(:disabled) { background: #efd28b; box-shadow: 0 1px 0 #c7ad70; transform: translateY(1px); } -.consultation-hint:focus-visible { outline: 2px solid #7891af; outline-offset: 2px; } +.consultation-hint:hover:not(:disabled) { color: #304631; text-decoration-color: currentColor; } +.consultation-hint:active:not(:disabled) { color: #243727; } +.consultation-hint:focus-visible { outline: 2px solid #71845f; outline-offset: 3px; text-decoration-color: currentColor; } .consultation-hint:disabled, .consultation-retry:disabled { cursor: not-allowed; opacity: 0.5; } -@media (prefers-reduced-motion: reduce) { .consultation-hint, .consultation-retry { transition: none; } .consultation-hint:hover:not(:disabled), .consultation-hint:active:not(:disabled), .consultation-retry:active:not(:disabled) { transform: none; } } +@media (prefers-reduced-motion: reduce) { .consultation-hint, .consultation-retry { transition: none; } .consultation-retry:active:not(:disabled) { transform: none; } } .consultation-pane button:focus-visible { outline: 2px solid #71845f; outline-offset: 3px; } .consultation-suggestion { border-color: #e0e6d6; color: #718163; } .consultation-suggestion:hover { border-color: #bac9aa; background: #f4f7ee; } diff --git a/tests/e2e/teacher-reply-layout.spec.ts b/tests/e2e/teacher-reply-layout.spec.ts index 7ddfa5dd..10aec5bb 100644 --- a/tests/e2e/teacher-reply-layout.spec.ts +++ b/tests/e2e/teacher-reply-layout.spec.ts @@ -62,15 +62,20 @@ for (const width of [319, 508]) { await expect(shortcuts.first()).toHaveCSS('font-size', await panel.getByTestId('teacher-reply').first().evaluate(el => getComputedStyle(el).fontSize)); await expect(shortcuts.first()).toHaveCSS('font-weight', '400'); await expect(shortcuts.first()).toHaveCSS('border-top-width', '0px'); - await expect(shortcuts.first()).not.toHaveCSS('box-shadow', 'none'); + await expect(shortcuts.first()).toHaveCSS('box-shadow', 'none'); + await expect(shortcuts.first()).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); expect((await row.boundingBox())!.y + (await row.boundingBox())!.height).toBeLessThan((await composer.boundingBox())!.y); await shortcuts.first().focus(); await expect(shortcuts.first()).toHaveCSS('outline-style', 'solid'); await expect(shortcuts.first()).toHaveCSS('cursor', 'pointer'); await shortcuts.first().hover(); - await expect(shortcuts.first()).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, -1)'); + await expect(shortcuts.first()).toHaveCSS('color', 'rgb(48, 70, 49)'); + await expect(shortcuts.first()).toHaveCSS('text-decoration-color', 'rgb(48, 70, 49)'); + await expect(shortcuts.first()).toHaveCSS('transform', 'none'); await page.mouse.down(); - await expect(shortcuts.first()).toHaveCSS('transform', 'matrix(1, 0, 0, 1, 0, 1)'); + await expect(shortcuts.first()).toHaveCSS('color', 'rgb(36, 55, 39)'); + await expect(shortcuts.first()).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await expect(shortcuts.first()).toHaveCSS('transform', 'none'); await page.mouse.move(0, 0); await page.mouse.up(); await page.emulateMedia({ reducedMotion: 'reduce' }); @@ -142,7 +147,8 @@ for (const width of [319, 508]) { await expect(hint).toBeEnabled(); 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(248, 230, 183)'); + await expect(hint).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + await expect(hint).toHaveCSS('box-shadow', 'none'); const initialSend = (await send.boundingBox())!; const hintBounds = (await hint.boundingBox())!; expect(hintBounds.x).toBeLessThan(initialSend.x);