import type { CustomWorldAgentStage, CustomWorldWorkSummary, } from '../../../packages/shared/src/contracts/customWorldAgent.js'; import type { CustomWorldProfileRecord } from '../../../packages/shared/src/contracts/runtime.js'; import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; import { normalizeFoundationDraftProfile } from './customWorldAgentDraftCompiler.js'; import { buildDraftSummaryFromIntent, buildDraftTitleFromIntent, normalizeCreatorIntentRecord, } from './customWorldAgentIntentExtractionService.js'; import { rebuildRoleAssetCoverage, resolveRoleAssetStatusLabel, } from './customWorldAgentRoleAssetStateService.js'; import type { CustomWorldAgentSessionRecord, CustomWorldAgentSessionStore, } from './customWorldAgentSessionStore.js'; function toText(value: unknown) { return typeof value === 'string' ? value.trim() : ''; } function toRecord(value: unknown) { return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : null; } function toRecordArray(value: unknown) { return Array.isArray(value) ? value.filter((item) => item && typeof item === 'object') : []; } function truncateText(value: string, maxLength: number) { if (value.length <= maxLength) { return value; } return `${value.slice(0, Math.max(0, maxLength - 1)).trim()}…`; } function formatDraftStageLabel(stage: CustomWorldAgentStage) { if (stage === 'collecting_intent') return '收集世界锚点'; if (stage === 'clarifying') return '补齐关键锚点'; if (stage === 'foundation_review') return '准备整理底稿'; if (stage === 'object_refining') return '精修对象'; if (stage === 'visual_refining') return '视觉工坊'; if (stage === 'long_tail_review') return '扩展长尾'; if (stage === 'ready_to_publish') return '准备发布'; if (stage === 'published') return '已发布'; return '发生错误'; } function resolveDraftTitle(session: CustomWorldAgentSessionRecord) { const intent = normalizeCreatorIntentRecord(session.creatorIntent); const draftProfile = normalizeFoundationDraftProfile(session.draftProfile); return ( draftProfile?.name || buildDraftTitleFromIntent(intent) || toText(session.draftProfile?.title) || truncateText(session.seedText, 18) || '未命名草稿' ); } function resolveDraftSummary(session: CustomWorldAgentSessionRecord) { const intent = normalizeCreatorIntentRecord(session.creatorIntent); const compiledSummary = buildDraftSummaryFromIntent(intent); const draftProfile = normalizeFoundationDraftProfile(session.draftProfile); return ( draftProfile?.summary || compiledSummary || toText(session.draftProfile?.summary) || truncateText(session.seedText, 72) || '还在收集你的世界锚点。' ); } function resolveDraftCounts(session: CustomWorldAgentSessionRecord) { const draftProfile = normalizeFoundationDraftProfile(session.draftProfile); if (draftProfile) { return { playableNpcCount: [ ...new Set( [...draftProfile.playableNpcs, ...draftProfile.storyNpcs].map( (entry) => entry.id, ), ), ].length, landmarkCount: draftProfile.landmarks.length, }; } const playableNpcCount = session.draftCards.filter( (card) => card.kind === 'character', ).length; const landmarkCount = session.draftCards.filter( (card) => card.kind === 'landmark' || card.kind === 'camp', ).length; return { playableNpcCount, landmarkCount, }; } function resolveDraftRoleAssetProgress(session: CustomWorldAgentSessionRecord) { const coverage = rebuildRoleAssetCoverage(session.draftProfile); const roleVisualReadyCount = coverage.roleAssets.filter( (entry) => entry.status !== 'missing', ).length; const roleAnimationReadyCount = coverage.roleAssets.filter( (entry) => entry.status === 'complete', ).length; const leadRole = coverage.roleAssets[0]; return { roleVisualReadyCount, roleAnimationReadyCount, roleAssetSummaryLabel: leadRole ? `${leadRole.roleName} · ${resolveRoleAssetStatusLabel(leadRole.status)}` : coverage.roleAssets.length > 0 ? '角色资产进行中' : null, }; } function resolvePublishedCover(profile: Record) { const camp = toRecord(profile.camp); const playableNpcs = toRecordArray(profile.playableNpcs); const leadNpc = toRecord(playableNpcs[0]); return toText(camp?.imageSrc) || toText(leadNpc?.imageSrc) || null; } export async function listCustomWorldWorkSummaries( userId: string, dependencies: { runtimeRepository: RuntimeRepositoryPort; customWorldAgentSessions: CustomWorldAgentSessionStore; }, ) { const [profiles, sessions] = await Promise.all([ dependencies.runtimeRepository.listCustomWorldProfiles(userId), dependencies.customWorldAgentSessions.list(userId), ]); const draftItems: CustomWorldWorkSummary[] = sessions.map((session) => { const counts = resolveDraftCounts(session); const roleAssetProgress = resolveDraftRoleAssetProgress(session); return { workId: `draft:${session.sessionId}`, sourceType: 'agent_session', status: 'draft', title: resolveDraftTitle(session), subtitle: normalizeFoundationDraftProfile(session.draftProfile)?.subtitle || formatDraftStageLabel(session.stage), summary: resolveDraftSummary(session), coverImageSrc: null, updatedAt: session.updatedAt, publishedAt: null, stage: session.stage, stageLabel: formatDraftStageLabel(session.stage), playableNpcCount: counts.playableNpcCount, landmarkCount: counts.landmarkCount, roleVisualReadyCount: roleAssetProgress.roleVisualReadyCount, roleAnimationReadyCount: roleAssetProgress.roleAnimationReadyCount, roleAssetSummaryLabel: roleAssetProgress.roleAssetSummaryLabel, sessionId: session.sessionId, profileId: null, canResume: true, canEnterWorld: false, }; }); const publishedItems: CustomWorldWorkSummary[] = profiles.map((profile) => { const profileRecord = profile as CustomWorldProfileRecord & Record; const playableNpcs = toRecordArray(profileRecord.playableNpcs); const landmarks = toRecordArray(profileRecord.landmarks); const updatedAt = toText(profileRecord.updatedAt) || new Date().toISOString(); const roleVisualReadyCount = playableNpcs.filter( (entry) => Boolean(toText(entry.imageSrc)) && Boolean(toText(entry.generatedVisualAssetId)), ).length; const roleAnimationReadyCount = playableNpcs.filter( (entry) => Boolean(toText(entry.generatedAnimationSetId)), ).length; return { workId: `published:${toText(profileRecord.id) || updatedAt}`, sourceType: 'published_profile', status: 'published', title: toText(profileRecord.name) || '未命名世界', subtitle: toText(profileRecord.subtitle) || '已发布作品', summary: toText(profileRecord.summary) || '这个世界已经可以直接进入体验。', coverImageSrc: resolvePublishedCover(profileRecord), updatedAt, publishedAt: toText(profileRecord.publishedAt) || updatedAt, stage: 'published', stageLabel: '已发布', playableNpcCount: playableNpcs.length, landmarkCount: landmarks.length, roleVisualReadyCount, roleAnimationReadyCount, roleAssetSummaryLabel: roleAnimationReadyCount > 0 ? `动作已就绪 ${roleAnimationReadyCount}` : roleVisualReadyCount > 0 ? `主图已就绪 ${roleVisualReadyCount}` : null, sessionId: null, profileId: toText(profileRecord.id) || null, canResume: false, canEnterWorld: true, }; }); return [...draftItems, ...publishedItems].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt), ); }