AionUi 团队协作运行体验优化设计解析:身份色系统、视图切换与 Warmup 闸门实现
2026/9/10 21:42:44 网站建设 项目流程

AionUi 团队协作运行体验优化设计解析:身份色系统、视图切换与 Warmup 闸门实现

【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20+ more CLI Agent | Customize your assistants | Team them up|Star if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi

导读

本文基于 team-runtime-experience.design.md(及其配套 team-runtime-experience.md),系统讲解 AionUi 桌面端团队详情页的协作体验优化方案:如何用「身份色系统」解决多成员并行协作时分不清消息归属的问题,如何按团队记忆切换并行/单聊视图,以及如何用「Leader ready」闸门让团队初始化(Warmup)从静默卡顿变成有引导、不阻塞的流程。读完本文,你将理解整套方案的架构决策(全部限定渲染层、持久化走localStorage)、核心算法的纯函数实现,以及它在 packages/desktop/src/renderer/pages/team/ 下的真实落地代码,可直接对照源码复现与二次开发。


1. 背景与名词约定

在进入设计细节前,先明确几个贯穿全文的概念(定义见 PRD 名词节):

名词含义
成员实例(slot)团队里的一个成员位,以slot_id唯一标识。同一个助手可以被拉进团队多次,每次是一个独立成员实例(不同slot_id),各自拥有独立会话
Leader团队里role === 'leader'的成员,恒为成员列表第一个,不可移除、不可拖拽
身份色团队详情页内为区分成员而临时赋予的颜色,仅在团队详情页生效,是纯展示辅助,不是业务数据
Warmup进入团队后,后端拉起各成员运行时 + 注入协作环境(MCP)的初始化阶段

设计基调明确了两点:全部改动限定渲染层(前端),不改 aioncore 后端与团队数据结构;所有持久化一律走浏览器localStorage,不落库。配色取自 AionUi 既有 token(品牌色及其低饱和邻近色系),颜色只作「身份标识」,不做整格彩底,保持界面灰白干净的基调。

设计文档同时划定了本次范围:只优化「创建团队之后、在团队详情页协作」的体验,不涉及团队创建流程;头像容器统一、确认型弹窗轻量统一等被列为「后续单独专题」,不在本次范围内。


2. 现状调研:决定架构的关键事实

设计方案不是凭空而起,而是基于渲染层现有代码的九条事实(F1–F9)推导出来的。文档以表格形式给出,每条都附有出处,以下完整继承并给出源码层面的确认:

#事实出处(设计文档标注,路径均已换算为仓库根目录相对路径)对方案的影响
F1TeamTabsProvider已是每团队一实例,已有按team_id分键的 localStorage(team-active-slot-*team-assistant-order-*TeamTabsContext.tsx(team-active-slot-${team_id}出现在第 77 行,排序 key 前缀team-assistant-order-在第 35 行)身份色映射、视图模式复用同一层与同款 key 风格
F2运行时事件ITeamAgentRuntimeStatusEventslot_id+status: 'pending' / 'ready' / 'failed'teamTypes.ts 中TeamAgentRuntimeStatus定义,事件体含slot_idconversation_idstatuserror?可单独判定 Leader ready→ Warmup 闸门成立
F3membershipMutationBusy是全员聚合忙碌,不区分 LeaderteamMembershipMutationBusy.tsWarmup 需在 session 层额外派生 leader-ready
F4teammate 消息只带senderConversationId(无slot_id消息渲染链路身份色按 conversation_id 取色;需要conversationId → 颜色的解析
F5发送框预填走getSendBoxDraftHook(type)(conversation_id).mutate(...),SWR 同 key 自动同步到 SendBox发送框草稿 hook 与TeamChatEmptyState建议卡「告诉 Leader」直接复用,无需新机制
F6并行/全屏已有fullscreenSlotId(TeamPageContent 本地 state)TeamPage.tsx升级为显式、持久化的viewMode(不新造全屏逻辑)
F7空状态 Leader 分支有 subtitle + 3 建议卡;建议卡 onClick 为fillDraft(label)TeamChatEmptyState.tsxLeader 问候语替换 subtitle,纯文案改动
F8选中/列高亮当前散落在 TeamPage(leader 硬编码 primary 色)与 TeamTabs(active class)TeamPage.tsx、TeamTabs.tsx身份色系统统一收口,替换这些硬编码
F9成员实例键为slot_idconversation_idslot_idassistants[]上一一对应TeamPage.tsx、teamTypesconversationId → slot_id索引即可打通消息与成员

架构含义(设计文档的核心判断):身份色的「真源」是slot_id(成员实例),但消息侧只认conversation_id。因此身份色系统对外暴露两个查询——colorOf(slot_id)colorOfConversation(conversation_id),内部用assistants[]维护conversation_id → slot_id索引打通两者。


3. 模块总览

方案在现有TeamTabsProvider(每团队一实例)上叠加三个新 hook,并向 context 追加导出:

TeamTabsProvider (已存在,每团队一实例) ├── useTeamMemberColors(team_id, assistants) 新增:身份色真源 + 持久化 ├── useTeamViewMode(team_id) 新增:并行/单聊,持久化 ├── useTeamWarmup(team_id) 新增:warmup 闸门/进度 └── context 追加导出: colorOf / colorOfConversation / viewMode / setViewMode / warmup

新增纯函数/组件:

  • team/identity/teamMemberColors.ts— 色板 + 分配算法(纯函数,可单测)
  • team/identity/useTeamMemberColors.ts— 持久化 hook(含conversation_id → slot_id索引)
  • team/identity/TeamIdentityContext.tsx— 身份色 context
  • team/components/TeamMemberCapsuleBar— 胶囊成员栏(替换 TeamTabs 呈现)
  • team/components/TeamWarmupOverlay.tsx— warmup 遮罩
  • team/components/TeamViewToggle.tsx— 标题行视图切换
  • 改造:消息渲染(气泡色条 + 彩名)、TeamChatEmptyState(问候语 + 告诉 Leader)、TeamPage(列高亮用身份色)

以上模块在 packages/desktop/src/renderer/pages/team/ 下均已落地:identity/(纯函数与 hook)、hooks/useTeamViewMode.tsuseTeamWarmup.tsTeamTabsContext.tsx等)、components/TeamWarmupOverlay.tsxTeamViewToggle.tsxTeamTabs.tsx等)。

设计原则:身份色系统是唯一色源,所有用色处(胶囊/气泡/列/遮罩)都从它取,杜绝各处硬编码 primary(消除 F8 的分散)。


4. 身份色系统(PRD §1)

4.1 色板

设计文档给出了初始色板:低饱和 slate 邻近色,取自 AionUi 品牌基调;每色给 accent(主)+ soft(浅底)两档,浅底用color-mix在运行时计算,故源码只存 accent。

仓库落地实现见 teamMemberColors.ts:

/** 身份色板:品牌 slate 邻近的低饱和色。索引 0 固定给 Leader(品牌色)。 */ export const TEAM_MEMBER_PALETTE = [ 'var(--brand)', // 0 = Leader '#5c9ea4', // 雾青 '#b58a5e', // 暖褐 '#9481bf', // 藕紫 '#c07d97', // 豆沙玫 '#6ba07e', // 灰绿 '#4f8ac9', // 雾蓝 '#c99a4b', // 琥珀 ] as const; export const LEADER_COLOR_INDEX = 0;

设计文档同时说明深色模式处理策略:这些 hex 在深色下仍是中性可辨的低饱和色;如需微调,后续在default-color-scheme.css暗色块加对应--team-mX覆盖,teamMemberColors改为引用var(--team-mX)。首版直接用 hex,避免铺开主题工作量。

4.2 分配算法:钉死 + 释放复用

身份色的稳定性规则(来自 PRD §1):颜色绑定成员实例(slot_id),一经分配即钉死;其他成员的新增/删除/重排都不改变一个成员已有的颜色;新成员取当前未被占用的颜色(优先复用被移除成员释放出的颜色);成员数超出色板时循环复用。

设计文档给出纯函数骨架,仓库实现见 assignMemberColors(实现略作整理,逻辑与设计一致):

export function assignMemberColors(prev: Record<string, number>, assistants: MemberLike[]): Record<string, number> { const next: Record<string, number> = {}; const used = new Set<number>(); const paletteLen = TEAM_MEMBER_PALETTE.length; // 1) Leader 固定色号 0 const leader = assistants.find((a) => a.role === 'leader'); if (leader) { next[leader.slot_id] = LEADER_COLOR_INDEX; used.add(LEADER_COLOR_INDEX); } // 2) 已分配过的成员沿用原色号(钉死) for (const a of assistants) { if (a.slot_id in next) continue; const previous = prev[a.slot_id]; if (previous !== undefined) { next[a.slot_id] = previous; used.add(previous); } } // 3) 新成员取未占用的最小非 0 色号;色板占满后对长度取模循环 let cursor = 1; const nextFreeIndex = (): number => { if (used.size < paletteLen - 1) { let idx = 1; while (used.has(idx)) idx++; return idx; } const idx = cursor % paletteLen || 1; // 保底非 0(0 属 Leader) cursor++; return idx; }; for (const a of assistants) { if (a.slot_id in next) continue; const idx = nextFreeIndex(); next[a.slot_id] = idx; used.add(idx); } return next; }

算法要点:移除成员时该 slot 不在assistants[]里 → 自然从next消失 = 释放色号;其余 slot 走「沿用」分支而不变色。循环仅在成员数超过色板时发生(罕见),且循环项位置相隔远、不易混淆。

同文件还提供取色辅助函数 memberColorValue:未知 slot 回退到 Leader 色(安全兜底)。该纯函数有独立单测覆盖(分配/钉死/释放/循环用例),见 tests/unit/renderer/team/teamMemberColors.test.ts。

4.3 持久化与 Hook

设计文档给出的useTeamMemberColors骨架(key 为team-member-colors-${team_id},值 =Record<slot_id, colorIndex>)在仓库中完整落地为 useTeamMemberColors.ts:

export function useTeamMemberColors(team_id: string, assistants: TeamAssistant[]): TeamMemberColorResolver { const key = storageKey(team_id); // `team-member-colors-${team_id}` const [colorMap, setColorMap] = useState<Record<string, number>>(() => readColorMap(key)); // 成员列表变化时增量重算:新成员补色、被移除成员释放色,已有成员钉死不变。 useEffect(() => { setColorMap((prev) => { const next = assignMemberColors(prev, assistants); if (shallowEqual(prev, next)) return prev; try { localStorage.setItem(key, JSON.stringify(next)); } catch { // storage full / unavailable — 颜色仍在内存生效,忽略持久化失败 } return next; }); }, [assistants, key]); // conversation_id -> slot_id 索引:消息只携带 senderConversationId,需借此回到成员实例取色。 const conversationToSlot = useMemo(() => { const index: Record<string, string> = {}; for (const a of assistants) { if (a.conversation_id) index[a.conversation_id] = a.slot_id; } return index; }, [assistants]); return useMemo( () => ({ colorOf: (slot_id) => memberColorValue(colorMap, slot_id), colorOfConversation: (conversation_id) => memberColorValue(colorMap, conversationToSlot[conversation_id ?? '']), }), [colorMap, conversationToSlot] ); }

实现细节:readColorMap对畸形 JSON 有 try/catch 兜底;shallowEqual避免无变化时无谓写 storage;持久化失败(storage 满/不可用)被吞掉,颜色仍在内存生效。conversationToSlot索引正是 F9 的落地——消息侧只认conversation_id,借助该索引回到成员实例取色。

挂载点TeamTabsProvider已持有team_id+assistants,在 TeamTabsContext.tsx 中调用useTeamMemberColors,并将colorOf/colorOfConversation并入 context value(第 158-188 行),供 TeamTabs、TeamPage 列以及消息侧使用。

4.4 消息侧取色(F4 的解法)

MessageText目前不在 TeamTabsProvider 子树内的保证性不足(它在会话渲染链里)。设计文档给了两个方案:

  • 方案 a(推荐):team 会话渲染链上已知team_idassistants,在AssistantChatSlot → TeamChatView传入一个resolveSenderColor(senderConversationId)回调,透传到 MessageList/MessageText。改动局部、不引入全局 context。
  • 方案 b:新建一个TeamIdentityContext提供colorOfConversation,MessageText 里useContext(可选、非 team 场景返回 undefined → 不显示色条)。

首版走 a:沿现有 props 链把resolveSenderColor传到消息组件;非团队消息该回调不存在 → 行为不变。仓库中 TeamIdentityContext.tsx 也已提供,供需要 context 的场景使用。

4.5 用色落地(CSS 变量注入)

统一做法:给需要着色的容器设style={{ '--mc': colorOf(slot_id) }},CSS 里用var(--mc)+color-mix出浅底:

  • 胶囊底:background: color-mix(in srgb, var(--mc) 9%, var(--bg-base));选中态 16% +box-shadow: 0 0 0 1.5px var(--mc)
  • 列选中:box-shadow: inset 0 0 0 2px var(--mc);列头color-mix(... 8% ...)
  • 气泡:发送者名color: var(--mc);气泡border-left: 3px solid var(--mc)

PRD 同时强调:身份色不作用于头像——头像保持原样(整图圆形,不加描边、不换底色),且颜色只作身份标识,不做整格彩底。


5. 视图切换:并行 / 单聊(PRD §4)

5.1 按团队记忆的 viewMode

useTeamViewMode(team_id)viewMode: 'parallel' | 'single',keyteam-view-mode-${team_id},默认'parallel'。挂TeamTabsProvider,context 暴露viewMode/setViewMode

仓库实现见 useTeamViewMode.ts,并已扩展出第三种模式:

/** * - parallel:所有成员对话列并排(默认)。 * - single:全屏显示当前选中的成员。 * - board:只读的「消息 & 任务」看板视图。 */ export type TeamViewMode = 'parallel' | 'single' | 'board'; const storageKey = (team_id: string): string => `team-view-mode-${team_id}`; const readViewMode = (team_id: string): TeamViewMode => { try { const stored = localStorage.getItem(storageKey(team_id)); if (stored === 'single') return 'single'; if (stored === 'board' || stored === 'flow') return 'board'; // migrate legacy 'flow' return 'parallel'; } catch { return 'parallel'; } };

注意readViewMode对旧值'flow'做了迁移(归入'board'),且读取失败一律回退'parallel',保证各团队独立记忆、默认并行。

5.2 TeamPage 渲染改造

设计文档给出的改造路径:把现有fullscreenSlotId ? 全屏 : 并行的判断,替换为viewMode === 'single' ? 单列(activeSlotId) : 并行

  • 单聊显示activeSlotId对应成员(复用现全屏那段 JSX,slot 来源从fullscreenSlotId换成activeSlotId)。
  • fullscreenSlotId本地 state 移除;原「点全屏图标」改为「切到单聊 + switchTab 到该 slot」。
  • 选中成员被移除时回退 Leader:已有逻辑覆盖(TeamTabsContext.tsx 中检测activeSlotId不在assistants[]时回退到 Leader 或第一个成员,并写回 storage)。
  • 视图切换控件TeamViewToggle放 ChatLayout 标题行右侧(实现见 TeamViewToggle.tsx)。
  • Warmup 期间允许切视图:TeamViewToggle 不受 warmup 禁用影响(纯前端布局变化,不改团队状态)。

视图是团队整体的属性 → 按团队记忆;并行视图与单聊视图共用同一份选中状态(activeSlotId),两视图切换时保持连贯;单聊视图下当前成员被移除 → 回退到 Leader,仍留在单聊视图。


6. Warmup 初始化状态(PRD §7)

6.1 问题与闸门决策

进入团队时后端在拉起成员运行时、注入协作环境,现状是静默禁用若干操作,用户只觉得「卡了一下」,还可能去做无效操作(发消息、加成员)。方案的核心决策:

  • 结束闸门 = Leader ready:遮罩在 Leader 运行时就绪时即撤除,不等全体成员——用户进团队第一件事是跟 Leader 说目标,Leader 一好就能开工;这也从根本上规避「一个成员起不来就卡死整个团队」。
  • 遮罩期间禁止:添加成员、移除成员、重命名成员、发消息(遮罩盖住输入区,天然禁用)。
  • 遮罩期间允许:切换视图、切换/浏览成员、滚动。
  • 失败与超时兜底:某 teammate 失败 → 遮罩早已撤除,该成员在自己的胶囊/列上显示「启动失败 · 可重试」;Leader 失败或超时未就绪 → 遮罩转为错误态(重试/返回),防止后端未发事件等极端情况下无限等待。

技术前置确认:方案依赖「前端能单独判断 Leader 的运行时就绪信号」。该信号已从 F2 确认存在——ITeamAgentRuntimeStatusEventslot_id+status(见 teamTypes.ts,TeamAgentRuntimeStatus = 'dormant' | 'pending' | 'ready' | 'failed'),因此闸门方案成立且仍为纯前端。

6.2 useTeamWarmup:派生 leader-ready 与 session 状态机

设计文档给出初版骨架(监听agentRuntimeStatusChanged+ 超时定时器),仓库实现 useTeamWarmup.ts 在此基础上进一步演进为以 session 状态为主、runtime 状态为辅的双通道:

export type TeamWarmupPhase = 'warming' | 'ready' | 'error'; export function useTeamWarmup(team_id: string): TeamWarmupState { const [phase, setPhase] = useState<TeamWarmupPhase>(team_id ? 'warming' : 'ready'); const [runtimeStatus, setRuntimeStatus] = useState<Map<string, TeamWarmupMemberState>>(() => new Map()); const [ensureAttempt, setEnsureAttempt] = useState(0); useEffect(() => { if (!team_id) { setPhase('ready'); setRuntimeStatus(new Map()); return; } let cancelled = false; setPhase('warming'); setRuntimeStatus(new Map()); const unsubRuntime = ipcBridge.team.agentRuntimeStatusChanged.on((event: ITeamAgentRuntimeStatusEvent) => { if (event.team_id !== team_id || cancelled) return; setRuntimeStatus((prev) => { const next = new Map(prev); next.set(event.slot_id, { status: event.status, error: event.error }); return next; }); }); const unsubSessionStatus = ipcBridge.team.sessionStatusChanged.on((event: ITeamSessionStatusChangedEvent) => { if (event.team_id !== team_id || cancelled) return; if (event.status === 'starting') setPhase('warming'); else if (event.status === 'ready') setPhase('ready'); else if (event.status === 'failed') setPhase('error'); // 'stopped':idle 回收停止,交由发送框以可恢复提示呈现,不触发页面级遮罩 }); return () => { cancelled = true; unsubRuntime(); unsubSessionStatus(); }; }, [team_id]); useEffect(() => { if (!team_id) return; let cancelled = false; setPhase('warming'); ipcBridge.team.ensureSession .invoke({ team_id }) .then(() => { if (!cancelled) setPhase('ready'); }) .catch(() => { if (!cancelled) setPhase('error'); }); return () => { cancelled = true; }; }, [team_id, ensureAttempt]); const retry = useCallback(() => { if (!team_id) return; setPhase('warming'); setEnsureAttempt((attempt) => attempt + 1); }, [team_id]); return { phase, runtimeStatus, retry }; }

设计文档中特别标注的「需实现时校验」问题(进入团队时 Leader 若已 ready,statusMap初值是否已是非 pending)在实现中被更稳健的方案取代:ensureSession.invoke的 Promise 作为事件遗漏时的兜底,同时 session 状态流(sessionStatusChanged)被视为 ready/failed 转换的权威来源,runtime 事件仅提供 per-slot 诊断细节(用于胶囊/列上的成员级失败态)。stopped状态不触发页面级遮罩,由发送框以可恢复提示呈现、下次发送时惰性恢复——这是从设计到实现的重要演进点。

6.3 遮罩组件与禁用清单

TeamWarmupOverlay(见 TeamWarmupOverlay.tsx)按 phase 分支渲染:

  • phase === 'warming':磨砂遮罩(backdrop-filter: blur(3px)+bg color-mix(--bg-1 78%))+ 成员头像从左到右逐个点亮(依据各 slot 的 statusMap ready 数)+「唤醒中 N/M」+ 品牌色进度条。
  • phase === 'error':错误态卡片(文案 + 重试/返回)。重试 = 重新触发进入团队的初始化(复用现有进入路径 / 重建,不新增后端接口)。
  • phase === 'ready':不渲染(撤除)。
  • 渲染位置:TeamPageContent 内容区之上(.warmwrap定位父级,覆盖 chat 区,不盖标题行——标题行的视图切换仍可用)。

禁用清单接线:加/删/改成员已有membershipMutationBusy门控(TeamTabs、TeamPage 移除处理),warmup 与之高度重合,保持即可;加号(TeamAddMemberPopoverdisabled)同理;发消息被遮罩覆盖 chat 区(含 SendBox)天然禁用;允许切视图、切成员、滚动(控件在标题行/成员栏,不被遮罩覆盖)。


7. 成员栏胶囊化(PRD §2 / §3)

TeamMemberCapsuleBar替换TeamTabs的呈现,但保留其 context 消费与>switchTab(leaderSlotId); // 选中 Leader setViewMode('single'); // 切单聊全屏 Leader(可选,按体验) // 预填 Leader 会话草稿(F5),不自动发 const draft = getSendBoxDraftHook(kindOf(leaderConv.type), initial)(leaderConv.conversation_id); draft.mutate((prev) => ({ ...prev, content: t('team.addMember.tellLeaderPrefill') }));

核心思路来自 F5:发送框预填走getSendBoxDraftHook(type)(conversation_id).mutate(...),SWR 同 key 自动同步到 SendBox——「告诉 Leader」直接复用该机制,无需新机制。预填文案team.addMember.tellLeaderPrefill= 「帮我在团队里加一个擅长 ___ 的成员」,不自动发送,光标定位让用户补全后自行发出。

8.2 Leader 问候

TeamChatEmptyState的 Leader 空状态 subtitle 替换为(新 i18n keyteam.emptyState.leaderGreeting):

你好,我是 Leader,负责理解你的目标并协调团队。描述你想做的事,我来安排。

保留下方已有的 3 个提示词快捷卡片不动(F7:建议卡 onClick =fillDraft(label)纯文案层面替换 subtitle)。入口不做空状态——用户一定有可添加的助手。


9. i18n 新增 key

key用途
team.view.parallel/team.view.single视图切换标签
team.warmup.title/team.warmup.progress(带 {n}/{m})遮罩文案
team.warmup.timeout/team.warmup.leaderFailed/team.warmup.retry错误态
team.member.startFailed/team.member.retryteammate 失败态
team.addMember.tellLeaderHint/team.addMember.tellLeaderCta/team.addMember.tellLeaderPrefill告诉 Leader
team.emptyState.leaderGreetingLeader 问候

准确翻译 en-US / zh-CN / zh-TW,其余 locale 英文兜底;改后跑node scripts/generate-i18n-types.js+node scripts/check-i18n.js(脚本位于 scripts/ 目录)。


10. 分阶段落地与验收

每阶段流程:改动 →bunx tsc --noEmit+oxlint+ 相关单测 +bun run package起 dev/CDP 自查 → 交验收 → 通过后按聚焦 commit。

阶段内容独立验收点
1身份色系统 + 胶囊成员栏 + 气泡区分 + 选中态高亮多成员下颜色区分清晰、选中态明显、增删成员颜色稳定(别人不变色)
2并行/单聊视图切换(按团队记忆)两视图切换连贯、按团队记忆、选中态共享、移除回退 Leader
3Warmup 状态 + 禁用清单 + 失败/超时兜底Leader 就绪即可用、某成员失败不卡死、超时有兜底、期间可切视图、发消息被挡
4添加成员虚线胶囊 + 「找 Leader 拉人」引导 + Leader 问候语入口常驻可见、引导可切 Leader 预填不自动发、问候到位

11. 风险与回归

设计文档列出的四类风险值得在实现与回归时逐一核对:

  • 测试契约:胶囊栏替换 TeamTabs DOM,需同步 team 相关单测/E2E 的 selector(本仓有 [E2E SYNC] 约定)。身份色纯函数的单元测试已先行落地(teamMemberColors.test.ts)。
  • 主题回归:身份色用 hex + color-mix,深色模式需目视核对;多套自定义主题用:has()覆盖过 modal,需确认不误伤团队页新类。
  • leader-ready 信号:阶段 3 实现前必须先在代码/CDP 确认信号可取。该信号已由ITeamAgentRuntimeStatusEventslot_id+status字段确认存在(teamTypes.ts),且实现侧额外引入sessionStatusChanged+ensureSession兜底,形成双通道冗余。
  • 性能:身份色映射为 O(成员数) 纯函数,随assistants变化重算,无忧。

结语

这套方案的价值在于:把「多成员分不清、初始化静默卡顿」两个体验问题,收敛为三个可独立测试的渲染层能力——slot_id为真源的身份色系统(colorOf/colorOfConversation双查询 + localStorage 持久化)、按团队记忆的视图模式、以 Leader ready 为闸门的 Warmup 状态机。所有状态归属与存储位置在 PRD 附录 A 中有完整清单(视图模式、身份色映射走新 key,当前选中成员、成员排序复用既有 key),全部为纯前端改动,不改 aioncore 与团队数据结构。对照 packages/desktop/src/renderer/pages/team/ 下的落地代码,即可完整复现这套团队协作体验的优化链路。

【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20+ more CLI Agent | Customize your assistants | Team them up|Star if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询