583 lines
17 KiB
TypeScript
583 lines
17 KiB
TypeScript
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<CreativeAgentStage, string> = {
|
|
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<CreativeAgentStage, string>
|
|
> = {
|
|
perceiving: '素材已理解',
|
|
thinking: '构思已完成',
|
|
remembering: '上下文已整理',
|
|
selecting_puzzle_template: '模板已选择',
|
|
planning_puzzle_levels: '关卡已规划',
|
|
acting: '草稿已生成',
|
|
reflecting: '结果已检查',
|
|
collaborating: '协作已收口',
|
|
target_ready: '草稿已就绪',
|
|
};
|
|
|
|
const CREATIVE_AGENT_STAGE_WAITING_LABEL: Partial<
|
|
Record<CreativeAgentStage, string>
|
|
> = {
|
|
idle: '等待输入',
|
|
waiting_template_confirmation: '等待确认',
|
|
waiting_user: '等待补充',
|
|
failed: '生成失败',
|
|
};
|
|
|
|
const CREATIVE_AGENT_STAGE_IDLE_LABEL: Record<CreativeAgentStage, string> = {
|
|
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<string, string> = {
|
|
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<CreativeAgentSseEvent, { event: 'stage' }>,
|
|
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<string, string>();
|
|
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);
|
|
}
|