import type { CreativeAgentSessionSnapshot, CreativeAgentSseEvent, CreativeAgentStage, CreativeTargetSessionBinding, } from '../../../packages/shared/src/contracts/creativeAgent'; import type { PuzzleCreativeTemplateProtocol, PuzzleCreativeTemplateSelection, PuzzleLevelGenerationMode, PuzzleSupportedLevelMode, } from '../../../packages/shared/src/contracts/puzzleCreativeTemplate'; export const CREATIVE_AGENT_STAGE_LABEL: Record = { idle: '等待输入', perceiving: '正在理解素材', thinking: '正在构思', remembering: '正在整理上下文', selecting_puzzle_template: '正在选择拼图模板', waiting_template_confirmation: '等待确认', planning_puzzle_levels: '正在规划关卡', acting: '正在生成草稿', reflecting: '正在检查结果', collaborating: '正在协作收口', target_ready: '草稿已就绪', waiting_user: '等待补充', failed: '生成失败', }; const CREATIVE_AGENT_STAGE_DONE_LABEL: Partial< Record > = { perceiving: '素材已理解', thinking: '构思已完成', remembering: '上下文已整理', selecting_puzzle_template: '模板已选择', planning_puzzle_levels: '关卡已规划', acting: '草稿已生成', reflecting: '结果已检查', collaborating: '协作已收口', target_ready: '草稿已就绪', }; const CREATIVE_AGENT_STAGE_WAITING_LABEL: Partial< Record > = { idle: '等待输入', waiting_template_confirmation: '等待确认', waiting_user: '等待补充', failed: '生成失败', }; const CREATIVE_AGENT_STAGE_IDLE_LABEL: Record = { idle: '等待输入', perceiving: '理解素材', thinking: '构思方向', remembering: '整理上下文', selecting_puzzle_template: '选择拼图模板', waiting_template_confirmation: '等待确认', planning_puzzle_levels: '规划关卡', acting: '生成草稿', reflecting: '检查结果', collaborating: '协作收口', target_ready: '草稿就绪', waiting_user: '等待补充', failed: '生成失败', }; export const CREATIVE_AGENT_TIMELINE: CreativeAgentStage[] = [ 'perceiving', 'thinking', 'remembering', 'selecting_puzzle_template', 'planning_puzzle_levels', 'acting', 'reflecting', 'collaborating', 'target_ready', ]; export type CreativeAgentTargetSelectionStage = | 'puzzle-agent-workspace' | 'puzzle-result'; export function resolveCreativeAgentTargetSelectionStage( targetStage: CreativeTargetSessionBinding['targetStage'], ): CreativeAgentTargetSelectionStage { return targetStage === 'puzzle-agent-workspace' ? 'puzzle-agent-workspace' : 'puzzle-result'; } export function createCreativeAgentClientMessageId(prefix = 'creative-agent') { return `${prefix}-${Date.now().toString(36)}-${Math.random() .toString(36) .slice(2, 8)}`; } function resolveTemplateLevelMode( supportedLevelMode: PuzzleSupportedLevelMode, defaultLevelCount: number, ): PuzzleLevelGenerationMode { if (supportedLevelMode === 'single') { return 'single_level'; } if (supportedLevelMode === 'multi') { return 'multi_level'; } return defaultLevelCount > 1 ? 'multi_level' : 'single_level'; } function resolveTemplatePlannedLevelCount( supportedLevelMode: PuzzleSupportedLevelMode, defaultLevelCount: number, ) { if (supportedLevelMode === 'single') { return 1; } if (supportedLevelMode === 'multi') { return Math.max(2, defaultLevelCount); } return Math.max(1, defaultLevelCount); } export function buildPuzzleTemplateSelectionFromProtocol( template: PuzzleCreativeTemplateProtocol, ): PuzzleCreativeTemplateSelection { const plannedLevelCount = resolveTemplatePlannedLevelCount( template.supportedLevelMode, template.defaultLevelCount, ); return { templateId: template.templateId, title: template.title, reason: template.summary, costRange: template.costRange, supportedLevelMode: template.supportedLevelMode, selectedLevelMode: resolveTemplateLevelMode( template.supportedLevelMode, plannedLevelCount, ), plannedLevelCount, requiresUserConfirmation: true, }; } export type CreativeAgentStageDisplayStatus = | 'active' | 'done' | 'waiting' | 'idle'; export function getCreativeAgentStageDisplayLabel( stage: CreativeAgentStage, status: CreativeAgentStageDisplayStatus, ) { if (status === 'done') { return CREATIVE_AGENT_STAGE_DONE_LABEL[stage] ?? CREATIVE_AGENT_STAGE_LABEL[stage]; } if (status === 'waiting') { return CREATIVE_AGENT_STAGE_WAITING_LABEL[stage] ?? CREATIVE_AGENT_STAGE_LABEL[stage]; } if (status === 'idle') { return CREATIVE_AGENT_STAGE_IDLE_LABEL[stage]; } return CREATIVE_AGENT_STAGE_LABEL[stage]; } export type CreativeAgentProcessTone = | 'active' | 'done' | 'info' | 'warning' | 'danger'; export type CreativeAgentProcessItem = { id: string; meta: string; title: string; detail: string | null; detailLines: string[]; tone: CreativeAgentProcessTone; }; const CREATIVE_AGENT_TOOL_LABEL: Record = { retrieve_puzzle_template_catalog: '读取拼图模板', select_puzzle_template: '选择拼图模板', confirm_puzzle_template: '确认模板', create_puzzle_agent_session: '创建拼图草稿', compile_puzzle_creative_draft: '编译拼图草稿', plan_puzzle_level_images: '规划关卡图片', generate_puzzle_level_images: '生成关卡图片', apply_puzzle_draft_natural_language_edit: '写回草稿修改', validate_puzzle_result_preview: '校验草稿预览', start_puzzle_draft_test_run: '启动拼图试玩', }; type ProcessBuildContext = { activeStageEventIndex: number; eventLog: CreativeAgentSseEvent[]; isComplete: boolean; }; function formatPuzzleLevelMode(mode: PuzzleLevelGenerationMode) { return mode === 'single_level' ? '单关卡' : '多关卡'; } function formatTargetStage(stage: CreativeTargetSessionBinding['targetStage']) { return stage === 'puzzle-agent-workspace' ? '拼图工作区' : stage === 'puzzle-runtime' ? '拼图运行态' : '拼图结果页'; } function resolveToolLabel(toolName: string) { return CREATIVE_AGENT_TOOL_LABEL[toolName] ?? toolName; } function buildStageProcessItem( event: Extract, index: number, context: ProcessBuildContext, ): CreativeAgentProcessItem { const stage = event.data.stage; const isWaiting = stage === 'waiting_template_confirmation' || stage === 'waiting_user'; const isActive = index === context.activeStageEventIndex && !context.isComplete && !isWaiting && stage !== 'target_ready' && stage !== 'failed'; const tone: CreativeAgentProcessTone = stage === 'failed' ? 'danger' : isWaiting ? 'warning' : isActive ? 'active' : 'done'; return { id: `${index}-stage-${stage}`, meta: '阶段', title: getCreativeAgentStageDisplayLabel( stage, isActive ? 'active' : isWaiting ? 'waiting' : 'done', ), detail: '阶段切换', detailLines: [], tone, }; } function buildEventProcessItem( event: CreativeAgentSseEvent, index: number, context: ProcessBuildContext, ): CreativeAgentProcessItem | null { switch (event.event) { case 'stage': return buildStageProcessItem(event, index, context); case 'tool_started': { const toolLabel = resolveToolLabel(event.data.toolName); const isToolResolved = context.isComplete || context.eventLog.slice(index + 1).some( (nextEvent) => nextEvent.event === 'tool_completed' && (nextEvent.data.toolCallId === event.data.toolCallId || nextEvent.data.toolName === event.data.toolName), ); return { id: `${index}-tool-started-${event.data.toolCallId}`, meta: '工具调用', title: `开始:${toolLabel}`, detail: event.data.summary ?? event.data.toolName, detailLines: [], tone: isToolResolved ? 'done' : 'active', }; } case 'tool_completed': { const toolLabel = resolveToolLabel(event.data.toolName); return { id: `${index}-tool-completed-${event.data.toolCallId}`, meta: '工具完成', title: `完成:${toolLabel}`, detail: event.data.summary ?? event.data.toolName, detailLines: [], tone: 'done', }; } case 'puzzle_template_selection': return { id: `${index}-template-${event.data.selection.templateId}`, meta: '模板', title: `选择 ${event.data.selection.title}`, detail: event.data.selection.reason, detailLines: [ `${formatPuzzleLevelMode(event.data.selection.selectedLevelMode)} · ${event.data.selection.plannedLevelCount} 关`, ], tone: 'info', }; case 'puzzle_template_catalog': return { id: `${index}-template-catalog`, meta: '模板', title: `读取 ${event.data.templates.length} 个模板`, detail: event.data.templates .slice(0, 3) .map((template) => template.title) .join('、') || null, detailLines: [], tone: 'info', }; case 'puzzle_cost_range': return { id: `${index}-cost-${event.data.costRange.minPoints}-${event.data.costRange.maxPoints}`, meta: '消耗', title: `预计 ${event.data.costRange.minPoints}-${event.data.costRange.maxPoints} 泥点`, detail: event.data.costRange.reason, detailLines: [], tone: 'info', }; case 'puzzle_level_plan': { const candidateCount = event.data.plan.levels.reduce( (total, level) => total + level.candidateCount, 0, ); return { id: `${index}-level-plan-${event.data.plan.templateId}`, meta: '关卡', title: `规划 ${event.data.plan.levels.length} 个关卡`, detail: `${formatPuzzleLevelMode(event.data.plan.mode)} · ${candidateCount} 张候选图`, detailLines: event.data.plan.levels.slice(0, 4).map((level) => [ level.levelName, level.pictureDescription, level.pictureReference ? `参考图:${level.pictureReference}` : null, ] .filter(Boolean) .join(' · '), ), tone: 'info', }; } case 'reflection': return { id: `${index}-reflection`, meta: '检查', title: event.data.pass ? '检查通过' : '需要调整', detail: event.data.summary, detailLines: event.data.warnings, tone: event.data.pass ? 'done' : 'warning', }; case 'target_session': return { id: `${index}-target-${event.data.binding.targetSessionId}`, meta: '目标', title: '拼图草稿已绑定', detail: formatTargetStage(event.data.binding.targetStage), detailLines: [`目标会话:${event.data.binding.targetSessionId}`], tone: 'done', }; case 'error': return { id: `${index}-error-${event.data.code}`, meta: '异常', title: event.data.message, detail: event.data.code, detailLines: [], tone: 'danger', }; case 'done': return { id: `${index}-done`, meta: '完成', title: '本轮完成', detail: null, detailLines: [], tone: 'done', }; case 'agent_message_delta': case 'thought_summary_delta': case 'session': return null; } return null; } function buildThoughtSummaryItems( eventLog: CreativeAgentSseEvent[], isComplete: boolean, ): CreativeAgentProcessItem[] { const thoughtMap = new Map(); let latestThoughtId: string | null = null; for (const event of eventLog) { if (event.event !== 'thought_summary_delta') { continue; } const currentText = thoughtMap.get(event.data.thoughtId) ?? ''; thoughtMap.set(event.data.thoughtId, `${currentText}${event.data.textDelta}`); latestThoughtId = event.data.thoughtId; } const items: CreativeAgentProcessItem[] = []; for (const [thoughtId, text] of thoughtMap.entries()) { const detail = text.trim(); if (!detail) { continue; } items.push({ id: `thought-${thoughtId}`, meta: '思考', title: '思考摘要', detail, detailLines: [], tone: !isComplete && thoughtId === latestThoughtId ? 'active' : 'done', }); } return items; } function buildSessionFallbackItems( session: CreativeAgentSessionSnapshot | null, eventLog: CreativeAgentSseEvent[], ): CreativeAgentProcessItem[] { if (!session) { return []; } const items: CreativeAgentProcessItem[] = []; if ( session.puzzleTemplateCatalog.length > 0 && !eventLog.some((event) => event.event === 'puzzle_template_catalog') ) { items.push({ id: `session-template-catalog-${session.puzzleTemplateCatalog.length}`, meta: '模板', title: `读取 ${session.puzzleTemplateCatalog.length} 个模板`, detail: session.puzzleTemplateCatalog .slice(0, 3) .map((template) => template.title) .join('、'), detailLines: [], tone: 'info', }); } if ( session.puzzleTemplateSelection && !eventLog.some((event) => event.event === 'puzzle_template_selection') ) { items.push({ id: `session-template-${session.puzzleTemplateSelection.templateId}`, meta: '模板', title: `选择 ${session.puzzleTemplateSelection.title}`, detail: session.puzzleTemplateSelection.reason, detailLines: [ `${formatPuzzleLevelMode(session.puzzleTemplateSelection.selectedLevelMode)} · ${session.puzzleTemplateSelection.plannedLevelCount} 关`, ], tone: 'info', }); } if ( session.puzzleImageGenerationPlan && !eventLog.some((event) => event.event === 'puzzle_level_plan') ) { const plan = session.puzzleImageGenerationPlan; items.push({ id: `session-level-plan-${plan.templateId}`, meta: '关卡', title: `规划 ${plan.levels.length} 个关卡`, detail: `${formatPuzzleLevelMode(plan.mode)} · ${plan.estimatedCostRange.minPoints}-${plan.estimatedCostRange.maxPoints} 泥点`, detailLines: plan.levels.slice(0, 4).map((level) => [ level.levelName, level.pictureDescription, level.pictureReference ? `参考图:${level.pictureReference}` : null, ] .filter(Boolean) .join(' · '), ), tone: 'info', }); } if ( session.targetBinding && !eventLog.some((event) => event.event === 'target_session') ) { items.push({ id: `session-target-${session.targetBinding.targetSessionId}`, meta: '目标', title: '拼图草稿已绑定', detail: formatTargetStage(session.targetBinding.targetStage), detailLines: [`目标会话:${session.targetBinding.targetSessionId}`], tone: 'done', }); } if ( items.length === 0 && session.stage !== 'idle' && !eventLog.some((event) => event.event === 'stage') ) { const isWaiting = session.stage === 'waiting_template_confirmation' || session.stage === 'waiting_user'; const isDone = session.stage === 'target_ready'; const tone: CreativeAgentProcessTone = session.stage === 'failed' ? 'danger' : isWaiting ? 'warning' : isDone ? 'done' : 'active'; items.push({ id: `session-stage-${session.stage}`, meta: '阶段', title: getCreativeAgentStageDisplayLabel( session.stage, tone === 'active' ? 'active' : tone === 'done' ? 'done' : 'waiting', ), detail: '当前状态', detailLines: [], tone, }); } return items; } export function buildCreativeAgentProcessItems( eventLog: CreativeAgentSseEvent[], session: CreativeAgentSessionSnapshot | null, ) { const terminalStageSeen = eventLog.some( (event) => event.event === 'stage' && (event.data.stage === 'waiting_template_confirmation' || event.data.stage === 'target_ready' || event.data.stage === 'waiting_user' || event.data.stage === 'failed'), ); const isComplete = eventLog.some((event) => event.event === 'done') || terminalStageSeen || session?.stage === 'waiting_template_confirmation' || session?.stage === 'target_ready' || session?.stage === 'waiting_user' || session?.stage === 'failed'; const activeStageEventIndex = eventLog.reduce( (latestIndex, event, index) => (event.event === 'stage' ? index : latestIndex), -1, ); const context: ProcessBuildContext = { activeStageEventIndex, eventLog, isComplete, }; return [ ...buildThoughtSummaryItems(eventLog, isComplete), ...eventLog .map((event, index) => buildEventProcessItem(event, index, context)) .filter((item): item is CreativeAgentProcessItem => Boolean(item)), ...buildSessionFallbackItems(session, eventLog), ].slice(-24); }