重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+86
-2
@@ -1,11 +1,23 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { BookProject, BookSummary } from "../types/book.js";
|
||||
import type { PersistedBookSession } from "../types/book-session.js";
|
||||
import type { RunSnapshot } from "../types/run-snapshot.js";
|
||||
import { ensureUserDataDirs, getUserDataDir } from "../config/user-data-dir.js";
|
||||
import { deleteBookSession } from "./session-store.js";
|
||||
import { deleteAllRunSnapshots } from "./run-snapshot-store.js";
|
||||
|
||||
const SESSION_FILENAME = "session.json";
|
||||
|
||||
function booksDir(): string {
|
||||
return path.join(getUserDataDir(), "books");
|
||||
}
|
||||
@@ -14,6 +26,10 @@ function bookPath(id: string): string {
|
||||
return path.join(booksDir(), `${id}.json`);
|
||||
}
|
||||
|
||||
function bookDataDir(id: string): string {
|
||||
return path.join(getUserDataDir(), "books", id);
|
||||
}
|
||||
|
||||
export function listBooks(): BookSummary[] {
|
||||
ensureUserDataDirs();
|
||||
mkdirSync(booksDir(), { recursive: true });
|
||||
@@ -64,7 +80,7 @@ export function createBook(input: { title?: string }): BookProject {
|
||||
const book: BookProject = {
|
||||
id: randomUUID(),
|
||||
title: input.title?.trim() || "未命名作品",
|
||||
preview: "新建作品,选择 skill 包开始…",
|
||||
preview: "新建作品,描述你想创作什么…",
|
||||
sessionIds: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
@@ -84,6 +100,8 @@ export function updateBook(
|
||||
| "activeSessionId"
|
||||
| "activeSkillId"
|
||||
| "activeSkillName"
|
||||
| "orchestratorId"
|
||||
| "orchestratorName"
|
||||
>
|
||||
>,
|
||||
): BookProject {
|
||||
@@ -115,3 +133,69 @@ export function deleteBook(id: string): void {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/** 复制作品及其会话、游玩存档为新作品 */
|
||||
export function duplicateBook(sourceId: string, title?: string): BookProject {
|
||||
const source = getBook(sourceId);
|
||||
if (!source) throw new Error("Book 不存在");
|
||||
|
||||
const newId = randomUUID();
|
||||
const newSessionId = randomUUID();
|
||||
const now = new Date().toISOString();
|
||||
const dupTitle = title?.trim() || `${source.title} 副本`;
|
||||
|
||||
ensureUserDataDirs();
|
||||
const sourceDir = bookDataDir(sourceId);
|
||||
const destDir = bookDataDir(newId);
|
||||
|
||||
if (existsSync(sourceDir)) {
|
||||
cpSync(sourceDir, destDir, { recursive: true });
|
||||
} else {
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
}
|
||||
|
||||
const sessionFile = path.join(destDir, SESSION_FILENAME);
|
||||
let sessionIds: string[] = [];
|
||||
let activeSessionId: string | undefined;
|
||||
|
||||
if (existsSync(sessionFile)) {
|
||||
try {
|
||||
const snap = JSON.parse(readFileSync(sessionFile, "utf8")) as PersistedBookSession;
|
||||
snap.bookId = newId;
|
||||
snap.sessionId = newSessionId;
|
||||
snap.runtimeSession.id = newSessionId;
|
||||
snap.savedAt = now;
|
||||
writeFileSync(sessionFile, JSON.stringify(snap, null, 2), "utf8");
|
||||
sessionIds = [newSessionId];
|
||||
activeSessionId = newSessionId;
|
||||
} catch {
|
||||
/* ignore broken session */
|
||||
}
|
||||
}
|
||||
|
||||
const snapDir = path.join(destDir, "run-snapshots");
|
||||
if (existsSync(snapDir)) {
|
||||
for (const file of readdirSync(snapDir).filter((f) => f.endsWith(".json"))) {
|
||||
try {
|
||||
const full = path.join(snapDir, file);
|
||||
const raw = JSON.parse(readFileSync(full, "utf8")) as RunSnapshot;
|
||||
raw.bookId = newId;
|
||||
writeFileSync(full, JSON.stringify(raw, null, 2), "utf8");
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newBook: BookProject = {
|
||||
...structuredClone(source),
|
||||
id: newId,
|
||||
title: dupTitle,
|
||||
sessionIds,
|
||||
activeSessionId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
writeFileSync(bookPath(newId), JSON.stringify(newBook, null, 2), "utf8");
|
||||
return newBook;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user