import React, { useEffect, useMemo, useRef } from 'react'; import { Bot, CheckCircle2, Database, FileCheck2, Sparkles, UserRound } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { useTheme } from '../../contexts/ThemeContext'; import ExpertMatchCards from './ExpertMatchCards'; import StreamErrorNotice from './StreamErrorNotice'; import { ActorCardItem, BehaviorCardItem, InlineCollapsible, NarrativeCardItem, ParentGroupHeader, PurposeCardItem, STAGE_ICONS, StageHeader, ThinkingBlock, } from './SixStepCards'; import type { ActorCard, BehaviorDriverCard, InfoStageData, NarrativeCard, PurposeCard, SixStepMessage, } from './types'; export interface ExpertMatchHandlers { onSelectExpert: (messageId: string, expertId: string) => void; onRefetchExpert: (messageId: string) => void; onCreateExpert: (messageId: string) => void; onConfirmExpert: (messageId: string) => void; } interface SixStepConversationProps { messages: SixStepMessage[]; isGenerating: boolean; activeMessageId?: string | null; expertHandlers?: ExpertMatchHandlers; } interface AvatarProps { role: 'user' | 'assistant'; active?: boolean; } const Avatar: React.FC = ({ role, active }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; const Icon = role === 'assistant' ? Bot : UserRound; return (
{role === 'assistant' && active && ( )}
); }; const InfoStage: React.FC<{ data: InfoStageData; running: boolean }> = ({ data, running }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; return (
信息准备 {data.complete && ( 已完成 )} {running && ( 收集中 )}
{running ? '正在收集热搜与上下文信息...' : '已整合信息准备素材'}
} > {data.content ? (
{data.content}
) : (
{running ? '正在获取信息...' : '无内容'}
)}
); }; const MarkdownMessage: React.FC<{ content: string; isDark: boolean }> = ({ content, isDark }) => (
{content}
); const PurposeStage: React.FC<{ data: NonNullable['purpose']; running: boolean }> = ({ data, running }) => { if (!data) return null; const Icon = STAGE_ICONS.purpose; const cards = data.cards ?? []; return ( } >
目的卡片列表 {cards.length} 张
} > {cards.length === 0 ? (
等待生成目的卡片...
) : (
{cards.map((card) => ( ))}
)}
); }; const ActorStage: React.FC<{ data: NonNullable['actor']; purposes: PurposeCard[]; running: boolean; }> = ({ data, purposes, running }) => { if (!data) return null; const Icon = STAGE_ICONS.actor; const cards = data.cards ?? []; const groups = useMemo(() => { const map = new Map(); cards.forEach((card) => { const list = map.get(card.purposeId) ?? []; list.push(card); map.set(card.purposeId, list); }); return purposes .map((purpose) => ({ purpose, items: map.get(purpose.id) ?? [] })) .filter((g) => g.items.length > 0); }, [cards, purposes]); return ( } >
行为体卡片列表 {cards.length} 张
} > {groups.length === 0 ? (
等待生成行为体卡片...
) : (
{groups.map((group) => (
{group.items.map((card) => ( ))}
))}
)}
); }; const BehaviorStage: React.FC<{ data: NonNullable['behavior']; actors: ActorCard[]; running: boolean; }> = ({ data, actors, running }) => { if (!data) return null; const Icon = STAGE_ICONS.behavior; const cards = data.cards ?? []; const groups = useMemo(() => { const map = new Map(); cards.forEach((card) => { const list = map.get(card.actorId) ?? []; list.push(card); map.set(card.actorId, list); }); return actors .map((actor) => ({ actor, items: map.get(actor.id) ?? [] })) .filter((g) => g.items.length > 0); }, [cards, actors]); return ( } >
预期行为与驱动因素卡片列表 {cards.length} 张
} > {groups.length === 0 ? (
等待生成预期行为与驱动因素卡片...
) : (
{groups.map((group) => (
{group.items.map((card) => ( ))}
))}
)}
); }; const NarrativeStage: React.FC<{ data: NonNullable['narrative']; behaviors: BehaviorDriverCard[]; running: boolean; }> = ({ data, behaviors, running }) => { if (!data) return null; const Icon = STAGE_ICONS.narrative; const cards = data.cards ?? []; const groups = useMemo(() => { const map = new Map(); cards.forEach((card) => { const list = map.get(card.driverId) ?? []; list.push(card); map.set(card.driverId, list); }); return behaviors .map((behavior) => ({ behavior, items: map.get(behavior.id) ?? [] })) .filter((g) => g.items.length > 0); }, [cards, behaviors]); return ( } >
叙事卡片列表 {cards.length} 张
} > {groups.length === 0 ? (
等待生成叙事卡片...
) : (
{groups.map((group) => (
{group.items.map((card) => ( ))}
))}
)}
); }; const FinalAnswer: React.FC<{ text: string; running: boolean }> = ({ text, running }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; if (!text && !running) return null; return (
最终结果 {running && ( 正在汇总输出 )}
        {text || '准备汇总输出...'}
      
); }; const SelectedSummaryPill: React.FC<{ message: SixStepMessage }> = ({ message }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; if (!message.selectedExpert) return null; const mcps = message.selectedMcps ?? []; const labelMap: Record = { purpose: '目的', actor: '行为体', behavior_factor: '预期行为和驱动因素', narrative: '叙事', }; return (
已选专家 {message.selectedExpert.name} {mcps.length > 0 && ( <> · 执行步骤:{mcps.map((k) => labelMap[k] || k).join(' → ')} )}
); }; interface AssistantMessageProps { message: SixStepMessage; active: boolean; expertHandlers?: ExpertMatchHandlers; } const AssistantMessage: React.FC = ({ message, active, expertHandlers }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; const isMarkdown = message.outputFormat === 'markdown'; const isFollowUp = message.phase === 'follow-up'; const stages = message.stages ?? {}; const purposes = stages.purpose?.cards ?? []; const actors = stages.actor?.cards ?? []; const behaviors = stages.behavior?.cards ?? []; const isInfoRunning = message.mainStepStatus?.info === 'running'; const isPurposeRunning = message.subStepStatus?.purpose === 'running'; const isActorRunning = message.subStepStatus?.actor === 'running'; const isBehaviorRunning = message.subStepStatus?.behavior === 'running'; const isNarrativeRunning = message.subStepStatus?.narrative === 'running'; const isFinalRunning = message.mainStepStatus?.final === 'running'; const generating = isInfoRunning || message.mainStepStatus?.sixstep === 'running' || isFinalRunning; // follow-up 在等待第一个 delta 时显示加载态 const followUpGenerating = isFollowUp && active && !message.markdownContent; const noStageYet = !stages.info && !stages.purpose && !stages.actor && !stages.behavior && !stages.narrative; const expertMatch = message.expertMatch; const inExpertPhase = message.phase === 'expert-match' && !!expertMatch; const expertConfirmed = expertMatch?.status === 'confirmed' || !!message.selectedExpert; const expertActive = active && inExpertPhase && !expertConfirmed; // Only render info/stages/finalAnswer when we're in an analysis phase. // Expert-match, clarifying, and follow-up phases show their own dedicated UI instead. const showAnalysisContent = !inExpertPhase && message.phase !== 'clarifying' && message.phase !== 'follow-up'; return (
{/* Expert match phase: show expert cards before stages */} {inExpertPhase && expertMatch && ( expertHandlers?.onSelectExpert(message.id, id)} onRefetch={() => expertHandlers?.onRefetchExpert(message.id)} onCreateExpert={() => expertHandlers?.onCreateExpert(message.id)} onConfirm={() => expertHandlers?.onConfirmExpert(message.id)} /> )} {inExpertPhase && (message.errors?.length ?? 0) > 0 && ( )} {/* Selected expert / MCP summary once moved to generate phase */} {message.selectedExpert && message.phase !== 'expert-match' && ( )} {showAnalysisContent && ( <> {/* Info preparing placeholder when no stage data yet */} {isInfoRunning && noStageYet && (
正在调用信息准备,收集热搜与上下文...
)} {/* Info prepare result (collapsible) */} {stages.info && } {(isMarkdown || isFollowUp) ? ( /* ---- Markdown / Follow-up mode ---- */ <> {(message.markdownContent || generating || followUpGenerating) && ( message.markdownContent ? ( ) : (
{isFollowUp ? '正在回答你的问题...' : '正在生成推演内容...'}
) )} {(message.errors?.length ?? 0) > 0 && ( )} ) : ( /* ---- Structured mode ---- */ <> {stages.purpose && } {stages.actor && } {stages.behavior && } {stages.narrative && ( )} {(message.finalAnswer || isFinalRunning) && ( )} {(message.errors?.length ?? 0) > 0 && ( )} )} )}
); }; const UserMessage: React.FC<{ message: SixStepMessage }> = ({ message }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; return (

{message.content}

); }; const SixStepConversation: React.FC = ({ messages, isGenerating, activeMessageId, expertHandlers }) => { const { theme } = useTheme(); const isDark = theme === 'dark'; const scrollRef = useRef(null); useEffect(() => { const el = scrollRef.current; if (!el) return; el.scrollTop = el.scrollHeight; }, [messages, isGenerating]); if (messages.length === 0) { return (

开始 6 步法推演

输入问题或场景描述,模型会按照"信息准备 → 6 步法推演 → 最终结果"的流程逐步生成结构化输出。

); } return (
{messages.map((msg) => (
{msg.role === 'user' ? ( ) : ( )}
))}
); }; export default SixStepConversation;