From a3c02e74649c743c445fb2304bec9e0ac954ad96 Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Sat, 26 Sep 2026 16:11:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=98=BE=E7=A4=BA=E4=B8=8B=E5=8F=91?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E7=9A=84=E4=BA=91=E7=AB=AF=E5=9B=BE?= =?UTF-8?q?=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260926-agent-avatar-ml-3c3686ec.md | 49 +++++++++++++++++++ README.md | 1 + shared/coding-teacher.ts | 1 + src/pages/Chat/TeacherAvatar.tsx | 17 +++++++ src/pages/Chat/TeacherChatPanel.tsx | 9 ++-- src/pages/Chat/TeacherCompanion.tsx | 9 ++-- tests/e2e/fixtures/teacher-preview-layout.tsx | 5 +- tests/e2e/teacher-preview-layout.spec.ts | 20 ++++++++ tests/unit/coding-teacher-ui.test.tsx | 10 ++++ tests/unit/teacher-companion.test.tsx | 18 +++++++ 10 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260926-agent-avatar-ml-3c3686ec.md create mode 100644 src/pages/Chat/TeacherAvatar.tsx diff --git a/.project-docs/30-worklog/tasks/20260926-agent-avatar-ml-3c3686ec.md b/.project-docs/30-worklog/tasks/20260926-agent-avatar-ml-3c3686ec.md new file mode 100644 index 00000000..f10af482 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260926-agent-avatar-ml-3c3686ec.md @@ -0,0 +1,49 @@ +# Task: Diagnose distributed agent avatar propagation + +## Identity + +- Task ID: 20260926-agent-avatar-ml-3c3686ec +- Mode: Feature +- Branch: codex/20260926-agent-avatar-ml-3c3686ec-agent-avatar-ml +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260926-agent-avatar-ml-3c3686ec +- Base commit: 9147b509c71f3ea765e5aaa8f542fd0a74bd4653 +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Diagnose and repair the missing Yuxi icon across publication, distribution and client rendering. User authorized implementation after the source diagnosis. + +## Intent And Constraints + +- Preserve immutable publication/replay and old-topic semantics, student billing, source storage ownership and existing local avatar fallback. +- No live configuration, deployment, automatic synchronization, main-branch merge or modification of peer worktrees. +- Concurrent ownership and Planning Gate passed in the same isolated task worktree. Same-base project memory and peer task scopes were retained from diagnosis; no new peers at resumption. Historical placeholder peer scopes remain unknown with no identified semantic conflict. +- Plan completed: froze the icon in Yuxi, resolved and preserved it in Works Square, rendered it consistently in Makelore, then verified each boundary. The one user-authorized fresh read-only reviewer approved the complete change with no blocking findings. + +## Outcome + +- Added optional icon_url to the distributed TeacherDefinition. TeacherAvatar handles the configured image and falls back to avatar_id when absent or when loading fails; new image URLs can be attempted after an earlier failure. +- Topbar entries, legacy/history companion entry, invitation speaker, panel header, welcome and assistant message rows use TeacherAvatar. Custom images use normal rendering; bundled pixel art keeps pixelated rendering. +- Updated README and added actual UI/browser regressions. + +## Verification + +- RED: both new tests failed with built-in avatar src instead of the expected cloud URL before implementation. +- GREEN: pnpm exec vitest run tests/unit/teacher-companion.test.tsx tests/unit/coding-teacher-ui.test.tsx: 56 passed. +- pnpm exec tsc --noEmit and focused ESLint passed. pnpm run build:vite passed for renderer, Main, preload and utility worker; existing Browserslist/chunk warnings remain. +- Real Edge Playwright distributed-icons case passed, including actual image decoding, independent 404 fallback, normal image rendering and the invitation speaker. Screenshot inspected in test-results; test route supplies deterministic image bytes, not production OSS. +- No installed Electron package or live production visual acceptance. + +## Follow-ups + +- Independent review passed. One non-blocking evidence gap remains: no combined test updates catalog metadata while an old topic is already open; existing separate data-flow tests and source inspection support the pinned-topic behavior. +- Task documentation drift checks passed across all three repositories; commit the scoped result for integration. +- Deploy compatible Yuxi, Works Square and Makelore versions, then use Operations sync to publish the existing agent's icon. Older topics keep their original published definitions. + +## Promotion Candidates + +- Target: .project-docs/20-architecture/system-overview.md and integrated current-state when useful. +- Proposal: record the versioned icon contract: native Yuxi icon becomes a frozen teacher snapshot field, WS resolves the public URL, client renders it with local fallback. +- Evidence: production diff plus regression results above. Future impact: visual identity follows the same publication boundary as other agent metadata. +- Semantic conflicts: none identified. Human confirmation: implementation authorized; canonical promotion and main-branch merge remain a separate integration task. diff --git a/README.md b/README.md index 48a918ee..d6531b2d 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - 当前输入是按预算截取的所选操作对话已完成分支文本、明确引用、咨询历史和适用的当前组件,并支持上述只读原文恢复;没有跨全部操作会话自动读取,也未向操作 Agent 注入咨询共识。“先这些”仍仅结束本轮整理,保留各条目状态,不确认全部候选、不自动制作。提示词不宣称这些系统能力已接通,也不声称看过未提供的画面或试玩过作品。Yuxi 话题经云端原生执行,旧版本地智能体由 Main 调用计费网关;均不创建本地 Pi 编程 worker。 - 智能体输入裁剪区分两种计量:本地模型以约每 2 个 UTF-8 字节估算 1 Token,并计入工具与读取预留;这不是精确分词或扣费依据,模型上限与实际 usage 仍由服务端拥有。云端沿用 query 字节上限,以完整 JSON 转义后的大小裁剪来源节选。智能体指令、当前问题和明确引用不会被静默裁掉;固定配置或当前整理本身超限时明确提示调整配置,避免让学生反复缩短短问题或新建话题。完整来源仍可由只读工具按需读取。 - `tests/fixtures/teacher-guidance-examples.json` 提供宠物、种植、射击、蛋糕的人工多轮输入输出样例,由真实解析器、上下文编译器与讨论状态机校验;样例不加载到运行时,也不代表真实模型教学效果已验收。云端定义由 Yuxi 维护并由运营显式同步下发;本地代码修改不会自动改写云端配置或学生已有话题的固定发布版本。 +- 下发智能体的图标随发布版本保存,顶栏、咨询面板、欢迎区和消息使用定义中的 `icon_url`;未设置或图片加载失败时使用内置头像。运营在 Yuxi 更换图标后需同步下发配置,新话题采用新版本,已有话题保持原图标。相对图片地址由服务端使用 Yuxi 公网入口补全。 - 咨询话题及未发送草稿按当前云端账号、项目保存;切换操作对话仍延续该项目下的咨询,每轮发送时绑定当时的操作对话来源。创建话题时固定发布版本,每轮重新检查启停。关闭面板或切换会话不取消已接受的问题,退出账号会中止。请求先落盘再调用模型,同一请求标识不重复发送,重启后未完成请求标记中断。云端断线按游标和持久状态恢复;停止或退出账号会取消原云端问题,下次提问先停止旧的未完成问题再绑定新上下文。只接收云端主线程回答,子智能体文本不混入正文。没有操作对话时也可咨询,但无操作文本上下文。 - 删除操作对话会停止正在使用该来源的回复,保留项目级咨询历史;归档来源需先恢复后才能引用。原有来源会话级智能体 API 与存储保持兼容,原路径历史不自动迁移到新项目级咨询栏。运营草稿试聊仍使用 `niancode://coding-teacher/preview?draft_revision=N` 打开独立示例上下文,不读取学生项目,要求桌面当前账号具有运营权限。 diff --git a/shared/coding-teacher.ts b/shared/coding-teacher.ts index 41ac4d9d..4bc24280 100644 --- a/shared/coding-teacher.ts +++ b/shared/coding-teacher.ts @@ -16,6 +16,7 @@ export interface TeacherDefinition { name: string; description: string; avatar_id: string; + icon_url?: string | null; welcome_message: string; suggested_questions: string[]; system_prompt: string; diff --git a/src/pages/Chat/TeacherAvatar.tsx b/src/pages/Chat/TeacherAvatar.tsx new file mode 100644 index 00000000..bf71b2f7 --- /dev/null +++ b/src/pages/Chat/TeacherAvatar.tsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; +import { getAgentAvatarSrc } from '@/lib/agent-avatars'; +import type { TeacherDefinition } from '../../../shared/coding-teacher'; + +/** 展示发布版本的图标,缺图或加载失败时使用原有内置头像。 */ +export function TeacherAvatar({ definition, className }: { + definition?: Pick | null; + className?: string; +}) { + const [failedIcon, setFailedIcon] = useState(null); + const icon = definition?.icon_url; + const custom = Boolean(icon && icon !== failedIcon); + return setFailedIcon(icon!) : undefined} />; +} diff --git a/src/pages/Chat/TeacherChatPanel.tsx b/src/pages/Chat/TeacherChatPanel.tsx index e09ee563..004daa61 100644 --- a/src/pages/Chat/TeacherChatPanel.tsx +++ b/src/pages/Chat/TeacherChatPanel.tsx @@ -12,7 +12,7 @@ import type { } from '../../../shared/coding-teacher'; import { TeacherDiscussionPanel, DiscussionFocus } from './TeacherDiscussionPanel'; import type { TeacherDiscussionAction } from '../../../shared/coding-teacher'; -import { getAgentAvatarSrc } from '@/lib/agent-avatars'; +import { TeacherAvatar } from './TeacherAvatar'; import { TeacherReply } from './TeacherReply'; export function TeacherChatPanel({ @@ -67,7 +67,6 @@ export function TeacherChatPanel({ const [historyOpen, setHistoryOpen] = useState(false); const [focus, setFocus] = useState<{ topicId: string; id: string; label: string } | null>(null); const [definition, setDefinition] = useState(null); - const avatar = getAgentAvatarSrc(definition?.avatar_id); const externalTopicRef = useRef(externalTopic); externalTopicRef.current = externalTopic; const selectedAgentId = selectedAgent?.teacher_id; @@ -383,7 +382,7 @@ export function TeacherChatPanel({