import crypto from 'node:crypto'; import type { JsonObject } from '../../../packages/shared/src/contracts/common.js'; import type { CustomWorldGenerationMode, CustomWorldQuestion, CustomWorldSessionRecord, CustomWorldSessionStatus, } from '../../../packages/shared/src/contracts/runtime.js'; import type { RuntimeRepositoryPort } from '../repositories/runtimeRepository.js'; export type CustomWorldSession = { sessionId: string; status: CustomWorldSessionStatus; settingText: string; creatorIntent: JsonObject | null; generationMode: CustomWorldGenerationMode; questions: CustomWorldQuestion[]; result?: JsonObject; lastError?: string; createdAt: string; updatedAt: string; }; function cloneSession(session: CustomWorldSession) { return JSON.parse(JSON.stringify(session)) as CustomWorldSession; } function toSessionRecord(session: CustomWorldSession): CustomWorldSessionRecord { return { sessionId: session.sessionId, status: session.status, settingText: session.settingText, creatorIntent: session.creatorIntent, generationMode: session.generationMode, questions: session.questions, result: session.result, lastError: session.lastError, createdAt: session.createdAt, updatedAt: session.updatedAt, }; } function toSession(record: CustomWorldSessionRecord) { return cloneSession({ sessionId: record.sessionId, status: record.status, settingText: record.settingText, creatorIntent: record.creatorIntent ?? null, generationMode: record.generationMode, questions: record.questions, result: record.result, lastError: record.lastError, createdAt: record.createdAt, updatedAt: record.updatedAt, }); } function hasPendingQuestion(questions: CustomWorldQuestion[]) { return questions.some((question) => !question.answer?.trim()); } function buildClarificationQuestions( settingText: string, creatorIntent: JsonObject | null, ) { const questions: CustomWorldQuestion[] = []; const worldHook = typeof creatorIntent?.worldHook === 'string' ? creatorIntent.worldHook.trim() : ''; const playerPremise = typeof creatorIntent?.playerPremise === 'string' ? creatorIntent.playerPremise.trim() : ''; const openingSituation = typeof creatorIntent?.openingSituation === 'string' ? creatorIntent.openingSituation.trim() : ''; const coreConflicts = Array.isArray(creatorIntent?.coreConflicts) ? creatorIntent.coreConflicts : []; if (!worldHook && settingText.trim().length < 24) { questions.push({ id: 'world_hook', label: '世界核心', question: '请用一句话补充这个世界最核心的命题或独特卖点。', }); } if (!playerPremise) { questions.push({ id: 'player_premise', label: '玩家身份', question: '玩家在这个世界里是什么身份、立场或来历?', }); } if (!openingSituation) { questions.push({ id: 'opening_situation', label: '开局处境', question: '故事开局时,玩家正处于什么局面?', }); } if (coreConflicts.length === 0) { questions.push({ id: 'core_conflict', label: '核心冲突', question: '这个世界当前最核心的冲突、危机或悬念是什么?', }); } return questions; } export class CustomWorldSessionStore { constructor( private readonly runtimeRepository: RuntimeRepositoryPort, ) {} async create( userId: string, settingText: string, creatorIntent: JsonObject | null, generationMode: CustomWorldGenerationMode, ) { const sessionId = `custom-world-session-${crypto.randomBytes(16).toString('hex')}`; const now = new Date().toISOString(); const session: CustomWorldSession = { sessionId, status: 'ready_to_generate', settingText, creatorIntent, generationMode, questions: buildClarificationQuestions(settingText, creatorIntent), createdAt: now, updatedAt: now, }; if (hasPendingQuestion(session.questions)) { session.status = 'clarifying'; } await this.runtimeRepository.upsertCustomWorldSession( userId, sessionId, toSessionRecord(session), ); return cloneSession(session); } async list(userId: string) { const sessions = await this.runtimeRepository.listCustomWorldSessions(userId); return sessions.map((session) => toSession(session)); } async get(userId: string, sessionId: string) { const session = await this.runtimeRepository.getCustomWorldSession( userId, sessionId, ); return session ? toSession(session) : null; } async answer( userId: string, sessionId: string, questionId: string, answer: string, ) { const session = await this.get(userId, sessionId); if (!session) { return null; } const question = session.questions.find((item) => item.id === questionId); if (!question) { return null; } question.answer = answer; session.status = hasPendingQuestion(session.questions) ? 'clarifying' : 'ready_to_generate'; session.updatedAt = new Date().toISOString(); await this.runtimeRepository.upsertCustomWorldSession( userId, sessionId, toSessionRecord(session), ); return cloneSession(session); } async updateStatus( userId: string, sessionId: string, status: CustomWorldSessionStatus, lastError = '', ) { const session = await this.get(userId, sessionId); if (!session) { return null; } session.status = status; session.lastError = lastError || undefined; session.updatedAt = new Date().toISOString(); await this.runtimeRepository.upsertCustomWorldSession( userId, sessionId, toSessionRecord(session), ); return cloneSession(session); } async setResult(userId: string, sessionId: string, result: JsonObject) { const session = await this.get(userId, sessionId); if (!session) { return null; } session.status = 'completed'; session.lastError = undefined; session.result = JSON.parse(JSON.stringify(result)) as JsonObject; session.updatedAt = new Date().toISOString(); await this.runtimeRepository.upsertCustomWorldSession( userId, sessionId, toSessionRecord(session), ); return cloneSession(session); } }