机制深度解析)
Cherry Studio 消息头 Author-First 改造消息作者快照MessageSnapshot机制深度解析【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio本指南基于 CherryHQ/cherry-studio 仓库中的 breaking-change 文档v2-refactor-temp/docs/breaking-changes/2026-06-30-message-header-author-first.md展开。该变更PR #16318severity: notice重构了消息头部的身份展示逻辑每条消息现在以产生它的助手chat 中的 Assistant或智能体session 中的 Agent为主角展示头像与名称模型信息降级为次要的弱化标签并在消息发送时把作者 模型的完整身份快照冻结到消息上。读完本文你将理解快照的数据结构、写入时机、迁移规则与渲染优先级以及无实时回退设计背后的原因。变更概述头部从模型优先变为作者优先在本次变更之前Cherry Studio 的消息头部默认以模型为视觉主导model-first。变更后每条消息的头部改为主展示产生该消息的助手assistant对应 chat 话题或智能体agent对应 agent session——以大号头像 名称的形式呈现次展示模型以小型、弱化muted的次级标签text-foreground-tertiary text-xs样式跟在作者名之后形如Assistant name · model身份冻结作者与模型的信息在消息发送那一刻被冻结snapshot到消息记录上。之后即使实时实体被重命名或删除历史消息依然展示生成时的原始身份。对用户而言这一变更的直观影响是已有会话的头部将从模型优先读法变成 助手名 · 模型 的作者优先读法且历史消息在作者/模型被改名或删除后仍保持原样。快照的数据结构一条扁平、自包含的作者身份快照的运行时 Schema 定义在 src/shared/data/types/message.ts 的 Snapshot Types (immutable records captured at message creation time) 一节包含两层/** Model identity captured at message creation time. */ export const ModelSnapshotSchema z.strictObject({ id: z.string(), name: z.string(), provider: z.string(), group: z.string().optional() }) /** Per-message snapshot of the producing author (chat assistant or session agent) */ export const MessageSnapshotSchema z.strictObject({ id: z.string(), name: z.string(), emoji: z.string().optional(), model: ModelSnapshotSchema })设计要点作者拥有模型模型以model字段嵌套在作者快照内部而不是平行字段。作者助手/智能体与模型是一对一捕获的整体身份不记录 kindchat 与 session 的差异已由消息所属 topic 隐含且两者在收敛因此快照保持单一扁平结构不额外记录assistant | agent类型ModelSnapshot完整保留 provider 与 group即使模型后续从 provider 中被移除快照仍能还原其 provider 归属用于头像渲染与展示名。在 UI 消息层快照挂载在CherryUIMessageMetadata.messageSnapshot见 src/shared/data/types/message.ts 中 Snapshot of the producing author (assistant|agent, model nested) captured at creation在持久化层MessageSchema.messageSnapshot是MessageSnapshotSchema.nullable().optional()的可空字段。持久化message 表中的 JSON 快照列数据库层面快照以 JSON 形式存储在message表的一个独立列中定义见 src/main/data/db/schemas/message.ts// Snapshot of model at message creation time messageSnapshot: text({ mode: json }).$typeMessageSnapshot(),与modelIdFK 指向user_model(id)删除模型时onDelete: set null不同messageSnapshot是去引用化的自包含 JSON blob不依赖任何外键存活。这正是重命名/删除实时实体不影响历史消息的底层保障modelId可能会因模型删除而被置空但快照列永远保留原始身份。服务层对该列的读写可在 src/main/data/services/MessageService.ts 中确认读取时messageSnapshot: parseJson(row.messageSnapshot)第 151 行写入时随 DTO 透传messageSnapshot: dto.messageSnapshot第 1184、1341 行并在复制/重放消息时携带messageSnapshot: sourceMessage.messageSnapshot第 2381 行。写入时机新消息在发送时捕获快照新消息的快照由运行时在消息创建/持久化路径上写入。从源码结构看相关链路分布在 src/main/ai/streamManager/context/AgentChatContextProvider.ts、PersistentChatContextProvider.ts、TemporaryChatContextProvider.ts 及 src/main/ai/runtime/types.ts 等处——这些上下文 Provider 负责在 assistant 回合开始/结束时把作者与模型快照落入消息元数据。可以推断快照在 assistant 回合持久化的那一刻send time被捕获并写入与消息的data.parts、stats等列同批落库。一旦快照存在头部渲染即被冻结后续切换默认模型、修改或删除助手/智能体都不会再移动历史消息的头部。渲染优先级快照优先仅缺席时回退头部组件的核心实现位于 src/renderer/components/chat/messages/frame/MessageHeader.tsx其关键逻辑在 122-127 行// Producing author (assistant/agent) snapshotted at creation — shown first; the model is secondary. // Once a snapshot exists the header is frozen: consult the live profile only when its entirely absent, // so editing/deleting the live entity never changes a past messages name or avatar. const authorSnapshot message.messageSnapshot const authorName authorSnapshot ? authorSnapshot.name : assistantProfile?.name const authorAvatar authorSnapshot ? authorSnapshot.emoji : assistantProfile?.avatar渲染决策链有快照messageSnapshot存在名称取snapshot.name头像取snapshot.emoji完全不查询实时 profile无快照回退到实时assistantProfile?.name / avatar会话当前助手配置头像渲染158-173 行优先级为作者快照头像 → 模型图标 → 首字母 fallbackfirstLetter(authorName)首字母大写。模型身份的次级展示189-196 行同样优先读取消息自身快照中的模型getMessageListItemModel(message)定义于 src/renderer/components/chat/messages/utils/messageListItem.ts并仅在showModelIdentity为真时渲染——即作者优先、模型次要的视觉层级。该组件的渲染行为有测试覆盖src/renderer/components/chat/messages/frame/tests/MessageHeader.test.tsx以及列表适配层的 homeMessageListAdapter.test.tsx 与 agentMessageListAdapter.test.tsx。无快照时的模型回退规则对没有快照的行主要是变更前生成、或迁移时无法解析的行文档明确了头部模型回退规则只回退行内存储的modelId头部展示的模型取自消息行自身的modelIdUniqueModelId形如providerId::modelId无实时模型回退切换默认模型、更换话题助手/智能体不会让历史消息的头部跟随变化——头部是读时静态的既无快照也无modelId头部直接不展示模型标签一个例外会话导出session export场景中导出作为一次性快照而非实时刷新的头部仍会以当前 agent 模型作为最后兜底填充这类行。迁移行为v1 导入消息的按需快照文档对迁移行为有精确描述v1 导入的 chat 消息在迁移时若 topic 的助手与消息的模型均可解析就会获得作者快照仅当无法解析时这些行保持无快照状态。实现位置在 src/main/data/migration/v2/migrators/ChatMigrator.ts// v1 couples a topic to one assistant → snapshot it onto assistant-role messages so the // header shows it after deletion. Built once per topic; transformMessage gates it by role. const assistantSnapshot resolvedAssistantId this.assistantLookup.has(resolvedAssistantId) ? buildAssistantSnapshot(resolvedAssistantId, this.assistantLookup.get(resolvedAssistantId)!) : undefined要点v1 模型下一个 topic 与一个 assistant 强耦合因此快照按 topic 构建一次buildAssistantSnapshot再按消息角色仅 assistant 角色由transformMessage写入只有resolvedAssistantId能通过 v2 助手 ID 重映射且能在助手表中查到时才构建快照助手引用悬空/孤立orphanedAssistantTopics计数的 topic 不产生快照对应消息行落入无快照分支快照缺失行如无助手归属的 v1 消息依赖行内modelId完成模型展示与上面无快照回退规则一致。对用户与发布管理的影响总结场景行为新发送消息发送时捕获作者助手/智能体 模型完整快照头部作者优先展示已有会话的旧消息头部读法变为 助手名 · 模型模型弱化为次级标签助手/智能体被重命名或删除历史消息不受影响快照冻结仅无快照行回退到实时 profile默认模型切换历史消息头部不变无实时模型回退v1 导入消息迁移时助手与模型可解析则写入快照否则保持无快照状态用户操作无需任何操作自动生效数据影响纯展示 每条消息的元数据增强无数据丢失该变更定性为purely presentational per-message metadata属于 UI/元数据层面的增强不涉及数据迁移风险或破坏性改动severity: notice发布管理者无需额外动作。整个机制的三层结构——shared 层的 Schema 定义src/shared/data/types/message.ts、DB 层的 JSON 列src/main/data/db/schemas/message.ts、渲染层的优先级逻辑src/renderer/components/chat/messages/frame/MessageHeader.tsx——共同保证了 Cherry Studio 消息头部身份展示的稳定性和历史可追溯性。【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考