mirror of
https://github.com/Narcooo/inkos.git
synced 2026-08-29 07:14:24 +08:00
feat: genre CLI commands and pipeline genre passthrough
- genre list/show/create/copy CLI commands - runner and agent pipeline pass genre to all agents - revise command supports --mode flag
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { Command } from "commander";
|
||||
import { writeFile, mkdir, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { listAvailableGenres, readGenreProfile, getBuiltinGenresDir } from "@actalk/inkos-core";
|
||||
import { findProjectRoot, log, logError } from "../utils.js";
|
||||
|
||||
export const genreCommand = new Command("genre")
|
||||
.description("Manage genre profiles");
|
||||
|
||||
genreCommand
|
||||
.command("list")
|
||||
.description("List all available genre profiles (built-in + project)")
|
||||
.action(async () => {
|
||||
try {
|
||||
const root = findProjectRoot();
|
||||
const genres = await listAvailableGenres(root);
|
||||
|
||||
if (genres.length === 0) {
|
||||
log("No genre profiles found.");
|
||||
return;
|
||||
}
|
||||
|
||||
log("Available genres:\n");
|
||||
for (const g of genres) {
|
||||
const tag = g.source === "project" ? "[project]" : "[builtin]";
|
||||
log(` ${g.id.padEnd(12)} ${g.name.padEnd(8)} ${tag}`);
|
||||
}
|
||||
log(`\nTotal: ${genres.length} genre(s)`);
|
||||
} catch (e) {
|
||||
logError(`Failed to list genres: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
genreCommand
|
||||
.command("show")
|
||||
.description("Display a genre profile")
|
||||
.argument("<id>", "Genre ID (e.g. xuanhuan, urban, horror)")
|
||||
.action(async (id: string) => {
|
||||
try {
|
||||
const root = findProjectRoot();
|
||||
const { profile, body } = await readGenreProfile(root, id);
|
||||
|
||||
log(`Genre: ${profile.name} (${profile.id})\n`);
|
||||
log(` Chapter types: ${profile.chapterTypes.join(", ")}`);
|
||||
log(` Fatigue words: ${profile.fatigueWords.join(", ")}`);
|
||||
log(` Numerical system: ${profile.numericalSystem}`);
|
||||
log(` Power scaling: ${profile.powerScaling}`);
|
||||
log(` Era research: ${profile.eraResearch}`);
|
||||
log(` Pacing rule: ${profile.pacingRule}`);
|
||||
log(` Satisfaction types: ${profile.satisfactionTypes.join(", ")}`);
|
||||
log(` Audit dimensions: ${profile.auditDimensions.join(", ")}`);
|
||||
|
||||
if (body) {
|
||||
log(`\n--- Body ---\n${body}`);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(`Failed to show genre: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
genreCommand
|
||||
.command("create")
|
||||
.description("Scaffold a new genre profile in the project genres/ directory")
|
||||
.argument("<id>", "Genre ID (e.g. scifi, wuxia, romance)")
|
||||
.option("--name <name>", "Genre display name", "")
|
||||
.option("--numerical", "Enable numerical system", false)
|
||||
.option("--power", "Enable power scaling", false)
|
||||
.option("--era", "Enable era research", false)
|
||||
.action(async (id: string, opts) => {
|
||||
try {
|
||||
const root = findProjectRoot();
|
||||
const genresDir = join(root, "genres");
|
||||
const filePath = join(genresDir, `${id}.md`);
|
||||
|
||||
// Check if already exists
|
||||
try {
|
||||
await readFile(filePath, "utf-8");
|
||||
logError(`Genre profile already exists: ${filePath}`);
|
||||
process.exit(1);
|
||||
} catch { /* file doesn't exist, good */ }
|
||||
|
||||
await mkdir(genresDir, { recursive: true });
|
||||
|
||||
const name = opts.name || id;
|
||||
const template = `---
|
||||
name: ${name}
|
||||
id: ${id}
|
||||
chapterTypes: ["推进章", "布局章", "过渡章", "回收章"]
|
||||
fatigueWords: ["震惊", "不可思议", "难以置信"]
|
||||
numericalSystem: ${opts.numerical}
|
||||
powerScaling: ${opts.power}
|
||||
eraResearch: ${opts.era}
|
||||
pacingRule: "每2-3章有一个明确的进展或反馈"
|
||||
satisfactionTypes: ["目标达成", "困难克服", "真相揭示"]
|
||||
auditDimensions: [1,2,3,6,7,8,9,10,13,14,15,16,17,18,19]
|
||||
---
|
||||
|
||||
## 题材禁忌
|
||||
|
||||
- (根据题材添加禁忌)
|
||||
|
||||
## 叙事指导
|
||||
|
||||
(根据题材描述叙事重心和风格要求)
|
||||
`;
|
||||
|
||||
await writeFile(filePath, template, "utf-8");
|
||||
log(`Created genre profile: ${filePath}`);
|
||||
log(`Edit the file to customize chapter types, fatigue words, rules, etc.`);
|
||||
} catch (e) {
|
||||
logError(`Failed to create genre: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
genreCommand
|
||||
.command("copy")
|
||||
.description("Copy a built-in genre profile to project for customization")
|
||||
.argument("<id>", "Genre ID to copy (e.g. xuanhuan)")
|
||||
.action(async (id: string) => {
|
||||
try {
|
||||
const root = findProjectRoot();
|
||||
const builtinDir = getBuiltinGenresDir();
|
||||
const srcPath = join(builtinDir, `${id}.md`);
|
||||
const genresDir = join(root, "genres");
|
||||
const destPath = join(genresDir, `${id}.md`);
|
||||
|
||||
// Check if project override already exists
|
||||
try {
|
||||
await readFile(destPath, "utf-8");
|
||||
logError(`Project genre profile already exists: ${destPath}`);
|
||||
process.exit(1);
|
||||
} catch { /* doesn't exist, good */ }
|
||||
|
||||
let content: string;
|
||||
try {
|
||||
content = await readFile(srcPath, "utf-8");
|
||||
} catch {
|
||||
logError(`Built-in genre "${id}" not found. Use 'inkos genre list' to see available genres.`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
await mkdir(genresDir, { recursive: true });
|
||||
await writeFile(destPath, content, "utf-8");
|
||||
log(`Copied to: ${destPath}`);
|
||||
log(`This project-level copy will override the built-in profile.`);
|
||||
} catch (e) {
|
||||
logError(`Failed to copy genre: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Command } from "commander";
|
||||
import { PipelineRunner } from "@actalk/inkos-core";
|
||||
import { PipelineRunner, type ReviseMode } from "@actalk/inkos-core";
|
||||
import { loadConfig, createClient, findProjectRoot, log, logError } from "../utils.js";
|
||||
|
||||
export const reviseCommand = new Command("revise")
|
||||
.description("Revise a chapter based on audit issues")
|
||||
.argument("<book-id>", "Book ID")
|
||||
.argument("[chapter]", "Chapter number (defaults to latest)")
|
||||
.option("--mode <mode>", "Revise mode: polish, rewrite, rework", "rewrite")
|
||||
.option("--json", "Output JSON")
|
||||
.action(async (bookId: string, chapterStr: string | undefined, opts) => {
|
||||
try {
|
||||
@@ -20,9 +21,10 @@ export const reviseCommand = new Command("revise")
|
||||
});
|
||||
|
||||
const chapterNumber = chapterStr ? parseInt(chapterStr, 10) : undefined;
|
||||
if (!opts.json) log(`Revising "${bookId}"${chapterNumber ? ` chapter ${chapterNumber}` : " (latest)"}...`);
|
||||
const mode = opts.mode as ReviseMode;
|
||||
if (!opts.json) log(`Revising "${bookId}"${chapterNumber ? ` chapter ${chapterNumber}` : " (latest)"} [mode: ${mode}]...`);
|
||||
|
||||
const result = await pipeline.reviseDraft(bookId, chapterNumber);
|
||||
const result = await pipeline.reviseDraft(bookId, chapterNumber, mode);
|
||||
|
||||
if (opts.json) {
|
||||
log(JSON.stringify(result, null, 2));
|
||||
|
||||
@@ -15,6 +15,7 @@ import { draftCommand } from "./commands/draft.js";
|
||||
import { auditCommand } from "./commands/audit.js";
|
||||
import { reviseCommand } from "./commands/revise.js";
|
||||
import { agentCommand } from "./commands/agent.js";
|
||||
import { genreCommand } from "./commands/genre.js";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
@@ -38,5 +39,6 @@ program.addCommand(draftCommand);
|
||||
program.addCommand(auditCommand);
|
||||
program.addCommand(reviseCommand);
|
||||
program.addCommand(agentCommand);
|
||||
program.addCommand(genreCommand);
|
||||
|
||||
program.parse();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type OpenAI from "openai";
|
||||
import { PipelineRunner, type PipelineConfig } from "./runner.js";
|
||||
import type { Platform, Genre } from "../models/book.js";
|
||||
import type { ReviseMode } from "../agents/reviser.js";
|
||||
|
||||
/** Tool definitions for the agent loop (OpenAI function calling format). */
|
||||
const TOOLS: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||
@@ -38,12 +39,13 @@ const TOOLS: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||
type: "function",
|
||||
function: {
|
||||
name: "revise_chapter",
|
||||
description: "修订指定章节。根据审计问题做最小幅度修正。",
|
||||
description: "修订指定章节。根据审计问题修正。支持三种模式:polish(润色)、rewrite(改写)、rework(重写)。",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
bookId: { type: "string", description: "书籍ID" },
|
||||
chapterNumber: { type: "number", description: "章节号(不填则修订最新章)" },
|
||||
mode: { type: "string", enum: ["polish", "rewrite", "rework"], description: "修订模式(默认rewrite)" },
|
||||
},
|
||||
required: ["bookId"],
|
||||
},
|
||||
@@ -95,7 +97,7 @@ const TOOLS: OpenAI.Chat.Completions.ChatCompletionTool[] = [
|
||||
type: "function",
|
||||
function: {
|
||||
name: "read_truth_files",
|
||||
description: "读取书籍的三大真相文件(状态卡、资源账本、伏笔池)+ 世界观和卷纲。",
|
||||
description: "读取书籍的长期记忆(状态卡、资源账本、伏笔池)+ 世界观和卷纲。",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -152,22 +154,40 @@ export async function runAgentLoop(
|
||||
const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: "system",
|
||||
content: `你是 InkOS 的智能编排 agent。你可以调用工具来管理网文创作的全流程。
|
||||
content: `你是 InkOS 小说写作 Agent。用户是小说作者,你帮他管理从建书到成稿的全过程。
|
||||
|
||||
你的能力:
|
||||
- 扫描市场趋势(scan_market)
|
||||
- 创建新书(create_book)
|
||||
- 写草稿(write_draft)
|
||||
- 审计章节(audit_chapter)
|
||||
- 修订章节(revise_chapter)
|
||||
- 查看书籍状态(get_book_status)
|
||||
- 读取真相文件(read_truth_files)
|
||||
- 列出所有书(list_books)
|
||||
- 一键完整管线(write_full_pipeline)
|
||||
## 工具
|
||||
|
||||
根据用户的自然语言指令,自主决定调用哪些工具、什么顺序。
|
||||
如果用户只给了题材或创意但没有明确要扫描市场,直接跳过雷达,用用户提供的信息创建书籍。
|
||||
每完成一步,简要汇报进展。`,
|
||||
| 工具 | 作用 |
|
||||
|------|------|
|
||||
| list_books | 列出所有书 |
|
||||
| get_book_status | 查看书的章数、字数、审计状态 |
|
||||
| read_truth_files | 读取长期记忆(状态卡、资源账本、伏笔池)和设定(世界观、卷纲、本书规则) |
|
||||
| create_book | 建书,生成世界观、卷纲、本书规则(自动加载题材 genre profile) |
|
||||
| write_draft | 写一章草稿(自动加载 genre profile + book_rules) |
|
||||
| audit_chapter | 审计章节(18维度,按题材条件启用) |
|
||||
| revise_chapter | 修订章节(支持 polish/rewrite/rework 三种模式) |
|
||||
| write_full_pipeline | 完整管线:写 → 审 → 改(如需要) |
|
||||
| scan_market | 扫描平台排行榜,分析市场趋势 |
|
||||
|
||||
## 长期记忆
|
||||
|
||||
每本书有三个长期记忆文件,是 Agent 写作和审计的事实依据:
|
||||
- **current_state.md** — 角色位置、关系、已知信息、当前冲突
|
||||
- **particle_ledger.md** — 物品/资源账本,每笔增减有据可查
|
||||
- **pending_hooks.md** — 已埋伏笔、推进状态、预期回收时机
|
||||
|
||||
## 管线逻辑
|
||||
|
||||
- audit 返回 passed=true → 不需要 revise
|
||||
- audit 返回 passed=false 且有 critical → 调 revise,改完可以再 audit
|
||||
- write_full_pipeline 会自动走完 写→审→改,适合不需要中间干预的场景
|
||||
|
||||
## 规则
|
||||
|
||||
- 用户提供了题材/创意但没说要扫描市场 → 跳过 scan_market,直接 create_book
|
||||
- 用户说了书名/bookId → 直接操作,不需要先 list_books
|
||||
- 每完成一步,简要汇报进展`,
|
||||
},
|
||||
{ role: "user", content: instruction },
|
||||
];
|
||||
@@ -298,6 +318,7 @@ async function executeTool(
|
||||
const result = await pipeline.reviseDraft(
|
||||
args.bookId as string,
|
||||
args.chapterNumber as number | undefined,
|
||||
(args.mode as ReviseMode) ?? "rewrite",
|
||||
);
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ import type OpenAI from "openai";
|
||||
import type { BookConfig } from "../models/book.js";
|
||||
import type { ChapterMeta } from "../models/chapter.js";
|
||||
import type { NotifyChannel } from "../models/project.js";
|
||||
import type { GenreProfile } from "../models/genre-profile.js";
|
||||
import { ArchitectAgent } from "../agents/architect.js";
|
||||
import { WriterAgent } from "../agents/writer.js";
|
||||
import { ContinuityAuditor } from "../agents/continuity.js";
|
||||
import { ReviserAgent } from "../agents/reviser.js";
|
||||
import { ReviserAgent, type ReviseMode } from "../agents/reviser.js";
|
||||
import { RadarAgent } from "../agents/radar.js";
|
||||
import type { RadarSource } from "../agents/radar-source.js";
|
||||
import { readGenreProfile } from "../agents/rules-reader.js";
|
||||
import { StateManager } from "../state/manager.js";
|
||||
import { dispatchNotification } from "../notify/dispatcher.js";
|
||||
import type { AgentContext } from "../agents/base.js";
|
||||
@@ -54,7 +56,7 @@ export interface TruthFiles {
|
||||
readonly pendingHooks: string;
|
||||
readonly storyBible: string;
|
||||
readonly volumeOutline: string;
|
||||
readonly styleGuide: string;
|
||||
readonly bookRules: string;
|
||||
}
|
||||
|
||||
export interface BookStatusInfo {
|
||||
@@ -87,6 +89,11 @@ export class PipelineRunner {
|
||||
};
|
||||
}
|
||||
|
||||
private async loadGenreProfile(genre: string): Promise<{ profile: GenreProfile }> {
|
||||
const parsed = await readGenreProfile(this.config.projectRoot, genre);
|
||||
return { profile: parsed.profile };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Atomic operations (composable by OpenClaw or agent mode)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -102,8 +109,9 @@ export class PipelineRunner {
|
||||
|
||||
await this.state.saveBookConfig(book.id, book);
|
||||
|
||||
const { profile: gp } = await this.loadGenreProfile(book.genre);
|
||||
const foundation = await architect.generateFoundation(book, this.config.externalContext);
|
||||
await architect.writeFoundationFiles(bookDir, foundation);
|
||||
await architect.writeFoundationFiles(bookDir, foundation, gp.numericalSystem);
|
||||
await this.state.saveChapterIndex(book.id, []);
|
||||
}
|
||||
|
||||
@@ -115,6 +123,8 @@ export class PipelineRunner {
|
||||
const bookDir = this.state.bookDir(bookId);
|
||||
const chapterNumber = await this.state.getNextChapterNumber(bookId);
|
||||
|
||||
const { profile: gp } = await this.loadGenreProfile(book.genre);
|
||||
|
||||
const writer = new WriterAgent(this.agentCtx(bookId));
|
||||
const output = await writer.writeChapter({
|
||||
book,
|
||||
@@ -133,7 +143,7 @@ export class PipelineRunner {
|
||||
await writeFile(filePath, `# 第${chapterNumber}章 ${output.title}\n\n${output.content}`, "utf-8");
|
||||
|
||||
// Save truth files
|
||||
await writer.saveChapter(bookDir, output);
|
||||
await writer.saveChapter(bookDir, output, gp.numericalSystem);
|
||||
|
||||
// Update index
|
||||
const existingIndex = await this.state.loadChapterIndex(bookId);
|
||||
@@ -160,6 +170,7 @@ export class PipelineRunner {
|
||||
|
||||
/** Audit the latest (or specified) chapter. Read-only, no lock needed. */
|
||||
async auditDraft(bookId: string, chapterNumber?: number): Promise<AuditResult & { readonly chapterNumber: number }> {
|
||||
const book = await this.state.loadBookConfig(bookId);
|
||||
const bookDir = this.state.bookDir(bookId);
|
||||
const targetChapter = chapterNumber ?? (await this.state.getNextChapterNumber(bookId)) - 1;
|
||||
if (targetChapter < 1) {
|
||||
@@ -168,7 +179,7 @@ export class PipelineRunner {
|
||||
|
||||
const content = await this.readChapterContent(bookDir, targetChapter);
|
||||
const auditor = new ContinuityAuditor(this.agentCtx(bookId));
|
||||
const result = await auditor.auditChapter(bookDir, content, targetChapter);
|
||||
const result = await auditor.auditChapter(bookDir, content, targetChapter, book.genre);
|
||||
|
||||
// Update index with audit result
|
||||
const index = await this.state.loadChapterIndex(bookId);
|
||||
@@ -188,9 +199,10 @@ export class PipelineRunner {
|
||||
}
|
||||
|
||||
/** Revise the latest (or specified) chapter based on audit issues. */
|
||||
async reviseDraft(bookId: string, chapterNumber?: number): Promise<ReviseResult> {
|
||||
async reviseDraft(bookId: string, chapterNumber?: number, mode: ReviseMode = "rewrite"): Promise<ReviseResult> {
|
||||
const releaseLock = await this.state.acquireBookLock(bookId);
|
||||
try {
|
||||
const book = await this.state.loadBookConfig(bookId);
|
||||
const bookDir = this.state.bookDir(bookId);
|
||||
const targetChapter = chapterNumber ?? (await this.state.getNextChapterNumber(bookId)) - 1;
|
||||
if (targetChapter < 1) {
|
||||
@@ -207,14 +219,18 @@ export class PipelineRunner {
|
||||
// Re-audit to get structured issues (index only stores strings)
|
||||
const content = await this.readChapterContent(bookDir, targetChapter);
|
||||
const auditor = new ContinuityAuditor(this.agentCtx(bookId));
|
||||
const auditResult = await auditor.auditChapter(bookDir, content, targetChapter);
|
||||
const auditResult = await auditor.auditChapter(bookDir, content, targetChapter, book.genre);
|
||||
|
||||
if (auditResult.passed) {
|
||||
return { chapterNumber: targetChapter, wordCount: content.length, fixedIssues: ["No issues to fix"] };
|
||||
}
|
||||
|
||||
const { profile: gp } = await this.loadGenreProfile(book.genre);
|
||||
|
||||
const reviser = new ReviserAgent(this.agentCtx(bookId));
|
||||
const reviseOutput = await reviser.reviseChapter(bookDir, content, targetChapter, auditResult.issues);
|
||||
const reviseOutput = await reviser.reviseChapter(
|
||||
bookDir, content, targetChapter, auditResult.issues, mode, book.genre,
|
||||
);
|
||||
|
||||
if (reviseOutput.revisedContent.length === 0) {
|
||||
throw new Error("Reviser returned empty content");
|
||||
@@ -238,7 +254,7 @@ export class PipelineRunner {
|
||||
if (reviseOutput.updatedState !== "(状态卡未更新)") {
|
||||
await writeFile(join(storyDir, "current_state.md"), reviseOutput.updatedState, "utf-8");
|
||||
}
|
||||
if (reviseOutput.updatedLedger !== "(账本未更新)") {
|
||||
if (gp.numericalSystem && reviseOutput.updatedLedger && reviseOutput.updatedLedger !== "(账本未更新)") {
|
||||
await writeFile(join(storyDir, "particle_ledger.md"), reviseOutput.updatedLedger, "utf-8");
|
||||
}
|
||||
if (reviseOutput.updatedHooks !== "(伏笔池未更新)") {
|
||||
@@ -283,17 +299,17 @@ export class PipelineRunner {
|
||||
}
|
||||
};
|
||||
|
||||
const [currentState, particleLedger, pendingHooks, storyBible, volumeOutline, styleGuide] =
|
||||
const [currentState, particleLedger, pendingHooks, storyBible, volumeOutline, bookRules] =
|
||||
await Promise.all([
|
||||
readSafe(join(storyDir, "current_state.md")),
|
||||
readSafe(join(storyDir, "particle_ledger.md")),
|
||||
readSafe(join(storyDir, "pending_hooks.md")),
|
||||
readSafe(join(storyDir, "story_bible.md")),
|
||||
readSafe(join(storyDir, "volume_outline.md")),
|
||||
readSafe(join(storyDir, "style_guide.md")),
|
||||
readSafe(join(storyDir, "book_rules.md")),
|
||||
]);
|
||||
|
||||
return { currentState, particleLedger, pendingHooks, storyBible, volumeOutline, styleGuide };
|
||||
return { currentState, particleLedger, pendingHooks, storyBible, volumeOutline, bookRules };
|
||||
}
|
||||
|
||||
/** Get book status overview. */
|
||||
@@ -333,6 +349,7 @@ export class PipelineRunner {
|
||||
const book = await this.state.loadBookConfig(bookId);
|
||||
const bookDir = this.state.bookDir(bookId);
|
||||
const chapterNumber = await this.state.getNextChapterNumber(bookId);
|
||||
const { profile: gp } = await this.loadGenreProfile(book.genre);
|
||||
|
||||
// 1. Write chapter
|
||||
const writer = new WriterAgent(this.agentCtx(bookId));
|
||||
@@ -349,6 +366,7 @@ export class PipelineRunner {
|
||||
bookDir,
|
||||
output.content,
|
||||
chapterNumber,
|
||||
book.genre,
|
||||
);
|
||||
|
||||
let finalContent = output.content;
|
||||
@@ -367,6 +385,8 @@ export class PipelineRunner {
|
||||
output.content,
|
||||
chapterNumber,
|
||||
auditResult.issues,
|
||||
"rewrite",
|
||||
book.genre,
|
||||
);
|
||||
|
||||
if (reviseOutput.revisedContent.length > 0) {
|
||||
@@ -379,6 +399,7 @@ export class PipelineRunner {
|
||||
bookDir,
|
||||
finalContent,
|
||||
chapterNumber,
|
||||
book.genre,
|
||||
);
|
||||
|
||||
// Update state files from revision
|
||||
@@ -386,7 +407,7 @@ export class PipelineRunner {
|
||||
if (reviseOutput.updatedState !== "(状态卡未更新)") {
|
||||
await writeFile(join(storyDir, "current_state.md"), reviseOutput.updatedState, "utf-8");
|
||||
}
|
||||
if (reviseOutput.updatedLedger !== "(账本未更新)") {
|
||||
if (gp.numericalSystem && reviseOutput.updatedLedger && reviseOutput.updatedLedger !== "(账本未更新)") {
|
||||
await writeFile(join(storyDir, "particle_ledger.md"), reviseOutput.updatedLedger, "utf-8");
|
||||
}
|
||||
if (reviseOutput.updatedHooks !== "(伏笔池未更新)") {
|
||||
@@ -410,7 +431,7 @@ export class PipelineRunner {
|
||||
|
||||
// Save original state files if not revised
|
||||
if (!revised) {
|
||||
await writer.saveChapter(bookDir, output);
|
||||
await writer.saveChapter(bookDir, output, gp.numericalSystem);
|
||||
}
|
||||
|
||||
// 5. Update chapter index
|
||||
|
||||
Reference in New Issue
Block a user