diff --git a/src/agentkit/server/frontend/src/api/types.ts b/src/agentkit/server/frontend/src/api/types.ts index e8e4401..8509a81 100644 --- a/src/agentkit/server/frontend/src/api/types.ts +++ b/src/agentkit/server/frontend/src/api/types.ts @@ -346,6 +346,8 @@ export interface IExpertSpeechData { /** round_summary event payload */ export interface IRoundSummaryData { moderator_name: string; + moderator_avatar?: string; + moderator_color?: string; content: string; round: number; continue: boolean; diff --git a/src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue b/src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue index b6e53ef..9d3983c 100644 --- a/src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue +++ b/src/agentkit/server/frontend/src/components/chat/StickyModeHeader.vue @@ -37,7 +37,7 @@ class="sticky-mode-header__avatar" :class="{ 'sticky-mode-header__avatar--lead': expert.isLead }" type="button" - :style="{ borderColor: expert.color }" + :style="{ background: expert.color, color: 'var(--text-inverse)', borderColor: expert.color }" :aria-label="`查看 ${expert.name} 详情`" :aria-expanded="openKey === expert.key" > @@ -62,7 +62,7 @@ > {{ expert.avatar }} {{ expert.name }} Lead @@ -153,11 +153,40 @@ const allExperts = computed(() => { } if (mode.value === 'board') { const list = chatStore.boardState?.experts ?? [] + // 颜色一致性:优先使用当前会话 messages 中最近的 board_speech/ + // round_summary 的 expert_color(与 MessageShell 头像同源),保证 + // 顶部卡片头像与对话头像颜色严格匹配;缺失时回退到 boardState + // 中保存的 YAML color。 + const liveColorByName = new Map() + const liveAvatarByName = new Map() + const conv = chatStore.conversations.find( + (c: { id: string; messages?: unknown[] }) => c.id === chatStore.currentConversationId, + ) + if (conv?.messages) { + // walk from latest to earliest to capture the most recent identity + for (let i = conv.messages.length - 1; i >= 0; i--) { + const m = conv.messages[i] as { + message_type?: string + expert_name?: string + expert_color?: string + expert_avatar?: string + } + if ( + (m.message_type === 'board_speech' || + m.message_type === 'board_summary') && + m.expert_name && + !liveColorByName.has(m.expert_name) + ) { + if (m.expert_color) liveColorByName.set(m.expert_name, m.expert_color) + if (m.expert_avatar) liveAvatarByName.set(m.expert_name, m.expert_avatar) + } + } + } return list.map((e: IBoardExpert, idx: number) => ({ key: `${e.name}-${idx}`, name: e.name, - avatar: e.avatar, - color: e.color, + avatar: liveAvatarByName.get(e.name) ?? e.avatar, + color: liveColorByName.get(e.name) ?? e.color, persona: e.persona, description: e.is_moderator ? '主持人' : undefined, isLead: false, diff --git a/src/agentkit/server/frontend/src/components/chat/helpers/expertIdentity.ts b/src/agentkit/server/frontend/src/components/chat/helpers/expertIdentity.ts index 6f8b8bb..b2f285e 100644 --- a/src/agentkit/server/frontend/src/components/chat/helpers/expertIdentity.ts +++ b/src/agentkit/server/frontend/src/components/chat/helpers/expertIdentity.ts @@ -14,20 +14,20 @@ export interface ExpertIdentity { color: string } -/** Non-blue palette. Order is stable — do not reshuffle (would break identity). */ +/** Neutral slate palette (GitHub/professional style). Order is stable — do not reshuffle. */ const PALETTE: ReadonlyArray = [ - "#d97706", // amber 600 - "#059669", // emerald 600 - "#7c3aed", // violet 600 - "#db2777", // pink 600 - "#0e7490", // cyan 700 - "#65a30d", // lime 600 - "#c2410c", // orange 700 - "#9333ea", // purple 600 - "#0891b2", // sky 600 - "#a16207", // yellow 700 - "#be185d", // rose 700 - "#15803d", // green 700 + "#4b5563", // slate 600 + "#6b7280", // gray 500 + "#9ca3af", // gray 400 + "#374151", // gray 800 + "#1f2937", // gray 900 + "#1e40af", // blue 800 (deep, non-vivid) + "#166534", // green 800 + "#7c2d12", // orange 900 + "#581c87", // purple 900 + "#155e75", // cyan 800 + "#92400e", // amber 800 + "#78716c", // stone 500 ] /** djb2-style string hash — stable across JS engines and reloads. */ diff --git a/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts b/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts index 1e2773b..d094f61 100644 --- a/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts +++ b/src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts @@ -55,19 +55,19 @@ export function resolveMessageType(message: IChatMessage): MessageViewType { switch (message.message_type) { case 'plan_update': return 'team_plan' - // 2026-07-01: board_* events render as plain assistant bubbles (streaming). - // The user's first @board/@team message already shows a structured card - // (UserBubble) with topic + expert count + expert list; rendering - // dedicated cards (BoardBannerCard / BoardRoundCard / BoardConclusionCard) - // for the subsequent board_started/board_speech/board_summary/ - // board_conclusion events duplicates the banner and breaks the natural - // chat flow. Fall through to 'assistant' so the content streams - // inline. + // 2026-07-02: 恢复 board_* 事件走专用卡片路径,落地方案B + // (中性灰阶头像/名字/颜色徽章/对话气泡 + 左侧3px色条)。 + // UserBubble 仅渲染用户输入的 @board 指令文本卡片, + // BoardBannerCard 渲染后端回送的 board_started 事件 (含专家 chip + 进度条), + // 二者职责不同,不构成重复。 case 'board_started': + return 'board_banner' case 'board_speech': + return 'board_speech' case 'board_summary': + return 'board_summary' case 'board_conclusion': - return 'assistant' + return 'board_conclusion' case 'debate_started': return 'debate_started' case 'debate_argument': @@ -149,42 +149,34 @@ export function useMessageRenderer(message: IChatMessage) { } } - case 'board_speech': + case 'board_speech': { + const roleTag = message.board_role === 'moderator' ? '主持' : '专家' return { type, shell: { name: message.expert_name || '专家', - meta: message.board_round ? `第 ${message.board_round} 轮${message.board_role === 'moderator' ? ' · 主持' : ''}` : time, + meta: message.board_round ? `第 ${message.board_round} 轮 · ${roleTag}` : `${roleTag}`, avatar: message.expert_avatar, color: message.expert_color, }, component: BoardRoundCard, props: { - name: message.expert_name || '专家', - avatar: message.expert_avatar || '', - color: message.expert_color, - round: message.board_round, - role: message.board_role === 'moderator' ? 'moderator' : 'expert', content: message.content || '', }, } + } case 'board_summary': return { type, shell: { name: message.expert_name || '主持人', - meta: message.board_round ? `第 ${message.board_round} 轮 · 小结` : time, + meta: message.board_round ? `第 ${message.board_round} 轮 · 小结` : '小结', avatar: message.expert_avatar, color: message.expert_color, }, component: BoardRoundCard, props: { - name: message.expert_name || '主持人', - avatar: message.expert_avatar || '', - color: message.expert_color, - round: message.board_round, - role: 'summary', content: message.content || '', }, } diff --git a/src/agentkit/server/frontend/src/components/chat/messages/BoardRoundCard.vue b/src/agentkit/server/frontend/src/components/chat/messages/BoardRoundCard.vue index 7f81aab..9e57cb2 100644 --- a/src/agentkit/server/frontend/src/components/chat/messages/BoardRoundCard.vue +++ b/src/agentkit/server/frontend/src/components/chat/messages/BoardRoundCard.vue @@ -1,19 +1,6 @@ @@ -23,37 +10,16 @@ import type { IChatMessage } from '@/api/types' import AssistantText from './AssistantText.vue' interface Props { - name: string - avatar?: string - color?: string - round?: number - role?: 'moderator' | 'expert' | 'summary' content: string } -const props = withDefaults(defineProps(), { - avatar: '', - role: 'expert', -}) - -const roleTag = computed(() => { - const tags: Record = { - moderator: '主持', - expert: '专家', - summary: '小结', - } - return tags[props.role] || '' -}) - -const avatarStyle = computed(() => { - if (props.color) { - return { background: props.color } - } - return { background: 'var(--accent-board)' } -}) +const props = defineProps() +// 方案B: BoardRoundCard 仅作为 Board 发言的纯内容容器, +// 渲染 AssistantText 的流式文本。头像/名字/轮次/角色标签等元信息 +// 全部由外层 MessageShell 的 header 负责,避免重复渲染。 const textMessage = computed(() => ({ - id: `board-${props.name}-${props.round || 0}`, + id: `board-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, role: 'assistant', content: props.content, timestamp: new Date().toISOString(), @@ -64,63 +30,5 @@ const textMessage = computed(() => ({ diff --git a/src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue b/src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue index 3975165..0a692a4 100644 --- a/src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue +++ b/src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue @@ -20,13 +20,13 @@
- + {{ expertName }} - {{ name }} + v-if="expertName || name" + class="message-shell__name" + :class="{ 'message-shell__name--expert': !!expertName }" + >{{ expertName || name }} {{ meta }} (), { font-weight: var(--font-weight-medium); } -/* U4 R10: 专家身份 badge — 彩色 pill,区别于普通 name 文本与 avatar */ -.message-shell__expert-badge { - display: inline-flex; - align-items: center; - padding: 0 var(--space-2); - border-radius: var(--radius-full); - color: var(--text-inverse, #fff); - font-size: var(--font-xs); +/* 方案B: 专家名字 — 粗体文本 + 略深色,与普通 name 区分 (无 pill 背景) */ +.message-shell__name--expert { + color: var(--text-primary); font-weight: var(--font-weight-semibold); - line-height: 1.6; - max-width: 12em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } .message-shell__meta { diff --git a/src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue b/src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue index 3655877..04337dd 100644 --- a/src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue +++ b/src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue @@ -14,37 +14,16 @@ /> - - + + - - + + - + diff --git a/src/agentkit/server/frontend/src/stores/chatStream.ts b/src/agentkit/server/frontend/src/stores/chatStream.ts index 8e099cc..39f35c9 100644 --- a/src/agentkit/server/frontend/src/stores/chatStream.ts +++ b/src/agentkit/server/frontend/src/stores/chatStream.ts @@ -1296,14 +1296,17 @@ export function dispatchWsEvent( (c) => c.id === conversationId, ); if (!conv) break; - // Stable identity for the moderator, just like expert_speech. + // Stable identity for the moderator. Prefer the event's + // moderator_avatar/moderator_color (added in 2026-07-02 so persistence + // has the same identity) and fall back to the boardState snapshot + // captured at board_started. const moderator = state.boardState.value?.experts.find( (e) => e.name === summaryData.moderator_name, ); const identity = resolveExpertIdentity( summaryData.moderator_name, - moderator?.avatar, - moderator?.color, + summaryData.moderator_avatar || moderator?.avatar, + summaryData.moderator_color || moderator?.color, ); const summaryMsg: IChatMessage = { id: generateId(), diff --git a/src/agentkit/server/frontend/src/views/LoginView.vue b/src/agentkit/server/frontend/src/views/LoginView.vue index 09d0cb7..d8ff373 100644 --- a/src/agentkit/server/frontend/src/views/LoginView.vue +++ b/src/agentkit/server/frontend/src/views/LoginView.vue @@ -181,6 +181,20 @@ onMounted(async () => { font-weight: 600; } +/* 显式绑定到 token — AntD ConfigProvider token 在 tauri 冷启动时序 + 不稳定,这里兜底强制使用项目主色(#1a1a1a 近黑),避免蓝色兜底。 */ +.login-submit.ant-btn-primary, +.login-submit.ant-btn-primary:hover, +.login-submit.ant-btn-primary:focus { + background-color: var(--color-primary, #1a1a1a); + border-color: var(--color-primary, #1a1a1a); + color: var(--text-inverse, #ffffff); +} +.login-submit.ant-btn-primary:hover { + background-color: var(--color-primary-hover, #2f2f2f); + border-color: var(--color-primary-hover, #2f2f2f); +} + .login-remember { margin-bottom: 16px; }