feat: migrate runtime backend to node server

This commit is contained in:
victo
2026-04-08 16:41:29 +08:00
parent 9d2fc9e4b8
commit a83841ff2d
70 changed files with 8239 additions and 1561 deletions

View File

@@ -0,0 +1,174 @@
import crypto from 'node:crypto';
export type CustomWorldSessionStatus =
| 'clarifying'
| 'ready_to_generate'
| 'generating'
| 'completed'
| 'generation_error';
export type CustomWorldQuestion = {
id: string;
label: string;
question: string;
answer?: string;
};
export type CustomWorldSession = {
sessionId: string;
userId: string;
status: CustomWorldSessionStatus;
settingText: string;
creatorIntent: Record<string, unknown> | null;
generationMode: 'fast' | 'full';
questions: CustomWorldQuestion[];
result?: Record<string, unknown>;
lastError?: string;
createdAt: string;
updatedAt: string;
};
function cloneSession(session: CustomWorldSession) {
return JSON.parse(JSON.stringify(session)) as CustomWorldSession;
}
function hasPendingQuestion(questions: CustomWorldQuestion[]) {
return questions.some((question) => !question.answer?.trim());
}
function buildClarificationQuestions(
settingText: string,
creatorIntent: Record<string, unknown> | 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 {
private readonly sessions = new Map<string, Map<string, CustomWorldSession>>();
create(
userId: string,
settingText: string,
creatorIntent: Record<string, unknown> | null,
generationMode: 'fast' | 'full',
) {
const sessionId = `custom-world-session-${crypto.randomBytes(16).toString('hex')}`;
const now = new Date().toISOString();
const session: CustomWorldSession = {
sessionId,
userId,
status: 'ready_to_generate',
settingText,
creatorIntent,
generationMode,
questions: buildClarificationQuestions(settingText, creatorIntent),
createdAt: now,
updatedAt: now,
};
if (hasPendingQuestion(session.questions)) {
session.status = 'clarifying';
}
const userSessions = this.sessions.get(userId) ?? new Map<string, CustomWorldSession>();
userSessions.set(sessionId, session);
this.sessions.set(userId, userSessions);
return cloneSession(session);
}
get(userId: string, sessionId: string) {
const session = this.sessions.get(userId)?.get(sessionId);
return session ? cloneSession(session) : null;
}
answer(userId: string, sessionId: string, questionId: string, answer: string) {
const session = this.sessions.get(userId)?.get(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();
return cloneSession(session);
}
updateStatus(
userId: string,
sessionId: string,
status: CustomWorldSessionStatus,
lastError = '',
) {
const session = this.sessions.get(userId)?.get(sessionId);
if (!session) {
return null;
}
session.status = status;
session.lastError = lastError || undefined;
session.updatedAt = new Date().toISOString();
return cloneSession(session);
}
setResult(userId: string, sessionId: string, result: Record<string, unknown>) {
const session = this.sessions.get(userId)?.get(sessionId);
if (!session) {
return null;
}
session.status = 'completed';
session.lastError = undefined;
session.result = JSON.parse(JSON.stringify(result)) as Record<string, unknown>;
session.updatedAt = new Date().toISOString();
return cloneSession(session);
}
}