@waterwx/dsh-novel-forge 1.3.0-alpha → 1.3.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +362 -236
- package/lib/client.js +575 -732
- package/lib/index.js +320 -9
- package/lib/types/client/api.d.ts +9 -1
- package/lib/types/client/locales.d.ts +7 -63
- package/lib/types/client/panel/TensionView.d.ts +5 -1
- package/lib/types/client/panel/TimelineView.d.ts +5 -1
- package/lib/types/client/panel/helpers.d.ts +5 -3
- package/lib/types/index.d.ts +1 -1
- package/lib/types/protocol.d.ts +44 -1
- package/lib/types/revision.d.ts +76 -0
- package/lib/types/run.d.ts +8 -0
- package/lib/types/tension.d.ts +0 -12
- package/package.json +1 -1
- package/src/client/api.ts +767 -758
- package/src/client/locales.ts +158 -418
- package/src/client/panel/NovelPanel.tsx +5559 -5517
- package/src/client/panel/TensionView.tsx +17 -1
- package/src/client/panel/TimelineView.tsx +208 -192
- package/src/client/panel/helpers.ts +7 -5
- package/src/index.ts +323 -323
- package/src/protocol.ts +2431 -2383
- package/src/revision.ts +283 -0
- package/src/routes.ts +3772 -3724
- package/src/run.ts +391 -346
- package/src/tension.ts +251 -240
package/lib/index.js
CHANGED
|
@@ -3653,6 +3653,16 @@ function renderTensionBlock(project, chapterNo) {
|
|
|
3653
3653
|
* @param project 项目状态。
|
|
3654
3654
|
* @returns 问题清单(可能为空)。
|
|
3655
3655
|
*/
|
|
3656
|
+
/**
|
|
3657
|
+
* run 型问题(连续同值 / 连续高位 / 连续低位)的受影响章号。
|
|
3658
|
+
* 返回**完整区间**而不是首尾两点:中间章同样受这条曲线问题影响,
|
|
3659
|
+
* 面板要能在每一章上给出「一键按建议修订」,合并层也要能把建议落到中间章。
|
|
3660
|
+
*/
|
|
3661
|
+
function runChapters(from, to) {
|
|
3662
|
+
const out = [];
|
|
3663
|
+
for (let no = from; no <= to; no++) out.push(no);
|
|
3664
|
+
return out;
|
|
3665
|
+
}
|
|
3656
3666
|
function detectTensionIssues(project) {
|
|
3657
3667
|
const values = [...project.chapters ?? []].sort((a, b) => a.no - b.no).map((c) => ({
|
|
3658
3668
|
no: c.no,
|
|
@@ -3681,7 +3691,7 @@ function detectTensionIssues(project) {
|
|
|
3681
3691
|
const from = values[i - run + 1].no;
|
|
3682
3692
|
issues.push({
|
|
3683
3693
|
severity: "medium",
|
|
3684
|
-
chapters:
|
|
3694
|
+
chapters: runChapters(from, values[i].no),
|
|
3685
3695
|
item: `第${from}-${values[i].no}章张力几乎无变化(连续 ${run} 章)`,
|
|
3686
3696
|
suggestion: "安排一次明显抬升或一次释放,让曲线有起伏,避免读者疲劳。"
|
|
3687
3697
|
});
|
|
@@ -3695,7 +3705,7 @@ function detectTensionIssues(project) {
|
|
|
3695
3705
|
highRun++;
|
|
3696
3706
|
if (highRun === 3) issues.push({
|
|
3697
3707
|
severity: "medium",
|
|
3698
|
-
chapters:
|
|
3708
|
+
chapters: runChapters(v.no - 2, v.no),
|
|
3699
3709
|
item: `连续 ${highRun} 章处在高位(≥80),缺少呼吸口`,
|
|
3700
3710
|
suggestion: "高潮之后补一章缓冲(关系推进、信息消化或日常),再进入下一轮对抗。"
|
|
3701
3711
|
});
|
|
@@ -3708,7 +3718,7 @@ function detectTensionIssues(project) {
|
|
|
3708
3718
|
lowRun++;
|
|
3709
3719
|
if (lowRun === 5) issues.push({
|
|
3710
3720
|
severity: "high",
|
|
3711
|
-
chapters:
|
|
3721
|
+
chapters: runChapters(v.no - 4, v.no),
|
|
3712
3722
|
item: `连续 ${lowRun} 章张力低于 40,推进可能偏慢`,
|
|
3713
3723
|
suggestion: "提前兑现一个目标、抛出一个新风险,或让配角线产生冲撞。"
|
|
3714
3724
|
});
|
|
@@ -4413,6 +4423,8 @@ const NOVEL_API = {
|
|
|
4413
4423
|
usageReset: "/api/dsh-novel-forge/usage/reset",
|
|
4414
4424
|
/** 章节历史版本(快照)。 */
|
|
4415
4425
|
snapshot: "/api/dsh-novel-forge/snapshot",
|
|
4426
|
+
/** 修订指令合并(POST:汇总审稿/时间线/张力为一轮)。 */
|
|
4427
|
+
revisionPlan: "/api/dsh-novel-forge/revision/plan",
|
|
4416
4428
|
/** 张力曲线(GET 数据 / POST 设置)。 */
|
|
4417
4429
|
tension: "/api/dsh-novel-forge/tension",
|
|
4418
4430
|
/** 提示词槽位(GET 列表 / POST 写入)。 */
|
|
@@ -9293,6 +9305,204 @@ function defaultOutputDirFor(bookName) {
|
|
|
9293
9305
|
const clean = bookName.replace(/[\\/:*?"<>|]/g, "").trim().slice(0, 40) || "未命名小说";
|
|
9294
9306
|
return join(homedir(), ".dsh", "novels", clean);
|
|
9295
9307
|
}
|
|
9308
|
+
const REVISION_SOURCE_LABELS = {
|
|
9309
|
+
review: "审稿",
|
|
9310
|
+
timeline: "时间线",
|
|
9311
|
+
tension: "张力"
|
|
9312
|
+
};
|
|
9313
|
+
/** 固定优先级:数值越小越先改。 */
|
|
9314
|
+
function revisionPriority(source, severity) {
|
|
9315
|
+
if (source === "review") return severity === "high" ? 0 : severity === "medium" ? 3 : 4;
|
|
9316
|
+
if (source === "timeline") return severity === "high" ? 1 : 3;
|
|
9317
|
+
return severity === "high" ? 2 : 2;
|
|
9318
|
+
}
|
|
9319
|
+
/** 同优先级的来源次序:审稿 > 时间线 > 张力(同级时先改让本章"判不过"的那一类)。 */
|
|
9320
|
+
const SOURCE_RANK = {
|
|
9321
|
+
review: 0,
|
|
9322
|
+
timeline: 1,
|
|
9323
|
+
tension: 2
|
|
9324
|
+
};
|
|
9325
|
+
/**
|
|
9326
|
+
* 条目在**来源内部**的原始序号(id 末段)。
|
|
9327
|
+
* 必须按数值比较:字符串比较会得到 0,1,10,11,2… 的顺序,
|
|
9328
|
+
* 于是截断时先丢「第 3 条意见」却留下「第 11 条」——等于把靠前的意见丢了。
|
|
9329
|
+
*/
|
|
9330
|
+
function sequenceOf(item) {
|
|
9331
|
+
const tail = item.id.slice(item.id.lastIndexOf(":") + 1);
|
|
9332
|
+
const n = Number.parseInt(tail, 10);
|
|
9333
|
+
return Number.isFinite(n) ? n : 0;
|
|
9334
|
+
}
|
|
9335
|
+
/** 去重键:同源 + 归一化问题描述(忽略空白与标点差异)。 */
|
|
9336
|
+
function dedupeKey(item) {
|
|
9337
|
+
return `${item.source}|${item.item.replace(/[\s,。、;:,.!?!?"'"'()()【】\[\]]/g, "").slice(0, 60)}`;
|
|
9338
|
+
}
|
|
9339
|
+
/** 排序 + 去重 + 截断。去重时保留优先级更高的那条,并累加 count。 */
|
|
9340
|
+
function mergeRevisionItems(items, cap = 8) {
|
|
9341
|
+
const sorted = [...items].sort((a, b) => {
|
|
9342
|
+
const byPriority = revisionPriority(a.source, a.severity) - revisionPriority(b.source, b.severity);
|
|
9343
|
+
if (byPriority !== 0) return byPriority;
|
|
9344
|
+
if (a.chapterNo !== b.chapterNo) return a.chapterNo - b.chapterNo;
|
|
9345
|
+
if (a.source !== b.source) return SOURCE_RANK[a.source] - SOURCE_RANK[b.source];
|
|
9346
|
+
const bySequence = sequenceOf(a) - sequenceOf(b);
|
|
9347
|
+
if (bySequence !== 0) return bySequence;
|
|
9348
|
+
return a.id.localeCompare(b.id);
|
|
9349
|
+
});
|
|
9350
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9351
|
+
const unique = [];
|
|
9352
|
+
for (const item of sorted) {
|
|
9353
|
+
const key = dedupeKey(item);
|
|
9354
|
+
const dup = seen.get(key);
|
|
9355
|
+
if (dup !== void 0) {
|
|
9356
|
+
dup.count = (dup.count ?? 1) + 1;
|
|
9357
|
+
continue;
|
|
9358
|
+
}
|
|
9359
|
+
seen.set(key, item);
|
|
9360
|
+
unique.push(item);
|
|
9361
|
+
}
|
|
9362
|
+
const kept = unique.slice(0, cap);
|
|
9363
|
+
return {
|
|
9364
|
+
items: kept,
|
|
9365
|
+
omitted: Math.max(0, unique.length - kept.length)
|
|
9366
|
+
};
|
|
9367
|
+
}
|
|
9368
|
+
/** 时间线/张力问题是否落在目标章上(chapters 为空视为全书级,跟随目标章)。 */
|
|
9369
|
+
function matchesChapter(chapters, target) {
|
|
9370
|
+
if (target === void 0) return true;
|
|
9371
|
+
const list = chapters ?? [];
|
|
9372
|
+
if (list.length === 0) return true;
|
|
9373
|
+
return list.includes(target);
|
|
9374
|
+
}
|
|
9375
|
+
function pickChapterNo(chapters, target, fallback) {
|
|
9376
|
+
if (target !== void 0 && matchesChapter(chapters, target)) return target;
|
|
9377
|
+
return chapters?.[0] ?? fallback;
|
|
9378
|
+
}
|
|
9379
|
+
function timelineToItems(issues, target) {
|
|
9380
|
+
const out = [];
|
|
9381
|
+
issues.forEach((issue, i) => {
|
|
9382
|
+
if (!matchesChapter(issue.chapters, target)) return;
|
|
9383
|
+
out.push({
|
|
9384
|
+
id: `timeline:${i}:${pickChapterNo(issue.chapters, target, 0)}`,
|
|
9385
|
+
source: "timeline",
|
|
9386
|
+
severity: issue.severity,
|
|
9387
|
+
chapterNo: pickChapterNo(issue.chapters, target, 0),
|
|
9388
|
+
item: issue.item,
|
|
9389
|
+
suggestion: issue.suggestion
|
|
9390
|
+
});
|
|
9391
|
+
});
|
|
9392
|
+
return out;
|
|
9393
|
+
}
|
|
9394
|
+
function tensionToItems(issues, target) {
|
|
9395
|
+
const out = [];
|
|
9396
|
+
issues.forEach((issue, i) => {
|
|
9397
|
+
if (!matchesChapter(issue.chapters, target)) return;
|
|
9398
|
+
out.push({
|
|
9399
|
+
id: `tension:${i}:${pickChapterNo(issue.chapters, target, 0)}`,
|
|
9400
|
+
source: "tension",
|
|
9401
|
+
severity: issue.severity,
|
|
9402
|
+
chapterNo: pickChapterNo(issue.chapters, target, 0),
|
|
9403
|
+
item: issue.item,
|
|
9404
|
+
suggestion: issue.suggestion
|
|
9405
|
+
});
|
|
9406
|
+
});
|
|
9407
|
+
return out;
|
|
9408
|
+
}
|
|
9409
|
+
/** 汇总一章(或全书)的三类建议。纯函数,不改项目状态。 */
|
|
9410
|
+
function collectRevisionItems(project, options = {}) {
|
|
9411
|
+
const target = options.chapterNo;
|
|
9412
|
+
const items = [];
|
|
9413
|
+
const chapters = (project.chapters ?? []).filter((c) => target === void 0 || c.no === target);
|
|
9414
|
+
const explicit = options.reviewIssues;
|
|
9415
|
+
if (explicit !== void 0) explicit.forEach((issue, i) => {
|
|
9416
|
+
items.push({
|
|
9417
|
+
id: `review:${target ?? 0}:${i}`,
|
|
9418
|
+
source: "review",
|
|
9419
|
+
severity: issue.severity,
|
|
9420
|
+
chapterNo: target ?? 0,
|
|
9421
|
+
item: issue.item,
|
|
9422
|
+
suggestion: issue.suggestion
|
|
9423
|
+
});
|
|
9424
|
+
});
|
|
9425
|
+
else for (const chapter of chapters) for (const [i, issue] of (chapter.review?.issues ?? []).entries()) items.push({
|
|
9426
|
+
id: `review:${chapter.no}:${i}`,
|
|
9427
|
+
source: "review",
|
|
9428
|
+
severity: issue.severity,
|
|
9429
|
+
chapterNo: chapter.no,
|
|
9430
|
+
item: issue.item,
|
|
9431
|
+
suggestion: issue.suggestion
|
|
9432
|
+
});
|
|
9433
|
+
if (options.includeTimeline ?? true) items.push(...timelineToItems(detectTimelineIssues(project), target));
|
|
9434
|
+
if (options.includeTension ?? true) items.push(...tensionToItems(detectTensionIssues(project), target));
|
|
9435
|
+
return items;
|
|
9436
|
+
}
|
|
9437
|
+
/** 渲染修订指令。格式与历史「按审稿意见修订」保持同族,模型无需重新学习。 */
|
|
9438
|
+
function renderRevisionInstruction(items, title = "按合并建议修订") {
|
|
9439
|
+
const counts = {
|
|
9440
|
+
review: 0,
|
|
9441
|
+
timeline: 0,
|
|
9442
|
+
tension: 0
|
|
9443
|
+
};
|
|
9444
|
+
for (const item of items) counts[item.source] += item.count ?? 1;
|
|
9445
|
+
return `${title}${`(审稿 ${counts.review} · 时间线 ${counts.timeline} · 张力 ${counts.tension})`}:\n${items.map((item) => {
|
|
9446
|
+
const tag = `【${REVISION_SOURCE_LABELS[item.source]}·${item.severity}】`;
|
|
9447
|
+
const where = item.chapterNo > 0 ? `第${item.chapterNo}章 ` : "";
|
|
9448
|
+
const repeat = (item.count ?? 1) > 1 ? `(合并同类 ${item.count} 条)` : "";
|
|
9449
|
+
const suggestion = item.suggestion.trim() === "" ? "" : `\n建议:${item.suggestion.trim()}`;
|
|
9450
|
+
return `${tag}${where}${item.item}${repeat}${suggestion}`;
|
|
9451
|
+
}).join("\n")}`;
|
|
9452
|
+
}
|
|
9453
|
+
/**
|
|
9454
|
+
* 构造验证基准:把时间线/张力条目转成「上一轮意见」的编号项,
|
|
9455
|
+
* 与审稿意见合并成一份报告。复核模型据此逐条核对,避免
|
|
9456
|
+
* 「审稿说已解决、时间线说没改」这类互相打架的判定。
|
|
9457
|
+
*/
|
|
9458
|
+
function buildRevisionBaseline(items, base) {
|
|
9459
|
+
const issues = items.map((item) => ({
|
|
9460
|
+
severity: item.severity,
|
|
9461
|
+
item: item.source === "review" ? item.item : `[${REVISION_SOURCE_LABELS[item.source]}] ${item.item}`,
|
|
9462
|
+
suggestion: item.suggestion
|
|
9463
|
+
}));
|
|
9464
|
+
return {
|
|
9465
|
+
score: base?.score ?? 0,
|
|
9466
|
+
passed: false,
|
|
9467
|
+
verdict: base?.verdict ?? "本轮修订基准",
|
|
9468
|
+
issues,
|
|
9469
|
+
riskScore: base?.riskScore,
|
|
9470
|
+
aiFlavor: base?.aiFlavor,
|
|
9471
|
+
aiPhrases: base?.aiPhrases,
|
|
9472
|
+
reviewedAt: base?.reviewedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
9473
|
+
};
|
|
9474
|
+
}
|
|
9475
|
+
/** 汇总 → 排序 → 指令 → 基准。面板与生产单都走这里。 */
|
|
9476
|
+
function buildRevisionPlan(project, options = {}) {
|
|
9477
|
+
const merged = mergeRevisionItems(collectRevisionItems(project, options), options.cap ?? 8);
|
|
9478
|
+
const counts = {
|
|
9479
|
+
review: 0,
|
|
9480
|
+
timeline: 0,
|
|
9481
|
+
tension: 0
|
|
9482
|
+
};
|
|
9483
|
+
for (const item of merged.items) counts[item.source] += item.count ?? 1;
|
|
9484
|
+
return {
|
|
9485
|
+
items: merged.items,
|
|
9486
|
+
instruction: renderRevisionInstruction(merged.items, options.title),
|
|
9487
|
+
counts,
|
|
9488
|
+
omitted: merged.omitted,
|
|
9489
|
+
baseline: buildRevisionBaseline(merged.items, options.baseReport)
|
|
9490
|
+
};
|
|
9491
|
+
}
|
|
9492
|
+
/** 一行摘要,用于日志与面板提示("审稿 3 · 时间线 1 · 张力 1,另截断 2 条")。 */
|
|
9493
|
+
function describeRevisionPlan(plan) {
|
|
9494
|
+
const parts = [];
|
|
9495
|
+
if (plan.counts.review > 0) parts.push(`审稿 ${plan.counts.review}`);
|
|
9496
|
+
if (plan.counts.timeline > 0) parts.push(`时间线 ${plan.counts.timeline}`);
|
|
9497
|
+
if (plan.counts.tension > 0) parts.push(`张力 ${plan.counts.tension}`);
|
|
9498
|
+
const head = parts.length === 0 ? "无可修订条目" : parts.join(" · ");
|
|
9499
|
+
return plan.omitted > 0 ? `${head}(另截断 ${plan.omitted} 条)` : head;
|
|
9500
|
+
}
|
|
9501
|
+
/** 待办文本(生产单只提示不阻塞时写入项目待办,供作者人工决定)。 */
|
|
9502
|
+
function revisionTodoText(item) {
|
|
9503
|
+
const where = item.chapterNo > 0 ? `第${item.chapterNo}章` : "全书";
|
|
9504
|
+
return `[${REVISION_SOURCE_LABELS[item.source]}] ${where}:${item.item}`;
|
|
9505
|
+
}
|
|
9296
9506
|
//#endregion
|
|
9297
9507
|
//#region src/run.ts
|
|
9298
9508
|
/**
|
|
@@ -9480,6 +9690,51 @@ var ProductionRunner = class {
|
|
|
9480
9690
|
chapter.status = "pending";
|
|
9481
9691
|
await this.produce(project, chapter);
|
|
9482
9692
|
}
|
|
9693
|
+
this.auditChapterAdvisories(chapter.no);
|
|
9694
|
+
}
|
|
9695
|
+
/**
|
|
9696
|
+
* 出章即核对曲线(建议性,不阻塞):
|
|
9697
|
+
* 规则初筛得到的时间线矛盾 + 张力曲线偏差写进运行日志;本章已完结时再落成项目待办。
|
|
9698
|
+
* 默认**不**触发任何自动修订——要看/要改由作者在面板发起「一键按建议修订」;
|
|
9699
|
+
* 只有当本章因审稿 high 本来就要修订时,这些问题才搭同一轮车(见 handleRejected),
|
|
9700
|
+
* 因此不额外增加轮次,也不会出现「改完甲又违反乙」。
|
|
9701
|
+
*/
|
|
9702
|
+
auditChapterAdvisories(chapterNo) {
|
|
9703
|
+
const config = this.deps.getConfig();
|
|
9704
|
+
const outputDir = this.bookDir ?? config.outputDir;
|
|
9705
|
+
const project = loadProject(outputDir);
|
|
9706
|
+
if (project === void 0) return;
|
|
9707
|
+
const chapter = project.chapters.find((c) => c.no === chapterNo);
|
|
9708
|
+
if (chapter === void 0) return;
|
|
9709
|
+
const plan = buildRevisionPlan(project, {
|
|
9710
|
+
chapterNo,
|
|
9711
|
+
reviewIssues: [],
|
|
9712
|
+
includeTimeline: true,
|
|
9713
|
+
includeTension: true
|
|
9714
|
+
});
|
|
9715
|
+
if (plan.items.length === 0) return;
|
|
9716
|
+
this.log(`第${chapterNo}章 曲线/时间线核对:${describeRevisionPlan(plan)}——建议性提示,未自动改动`);
|
|
9717
|
+
if (chapter.status !== "approved") return;
|
|
9718
|
+
if (plan.counts.timeline === 0 && plan.counts.tension === 0) return;
|
|
9719
|
+
const todos = project.todos ??= [];
|
|
9720
|
+
const added = [];
|
|
9721
|
+
for (const item of plan.items.slice(0, 3)) {
|
|
9722
|
+
const text = revisionTodoText(item);
|
|
9723
|
+
if (todos.some((t) => t.text === text)) continue;
|
|
9724
|
+
todos.unshift({
|
|
9725
|
+
id: `td-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
9726
|
+
text,
|
|
9727
|
+
source: "risk",
|
|
9728
|
+
done: false,
|
|
9729
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9730
|
+
});
|
|
9731
|
+
added.push(text);
|
|
9732
|
+
}
|
|
9733
|
+
if (added.length > 0) {
|
|
9734
|
+
project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9735
|
+
saveProject(outputDir, project);
|
|
9736
|
+
this.log(`第${chapterNo}章 已登记 ${added.length} 条建议性待办(可在时间线/张力面板一键按建议修订)`);
|
|
9737
|
+
}
|
|
9483
9738
|
}
|
|
9484
9739
|
/** 完整质量门:生成 → 摘要+事实 → 伏笔标记 → 审稿 → 作者复盘。 */
|
|
9485
9740
|
async produce(project, chapter) {
|
|
@@ -9560,7 +9815,8 @@ var ProductionRunner = class {
|
|
|
9560
9815
|
const config = getConfig();
|
|
9561
9816
|
const outputDir = this.bookDir ?? config.outputDir;
|
|
9562
9817
|
const no = chapter.no;
|
|
9563
|
-
const
|
|
9818
|
+
const report = chapter.review;
|
|
9819
|
+
const highs = (report?.issues ?? []).filter((i) => i.severity === "high");
|
|
9564
9820
|
if (highs.length === 0) {
|
|
9565
9821
|
chapter.status = "approved";
|
|
9566
9822
|
project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9569,9 +9825,15 @@ var ProductionRunner = class {
|
|
|
9569
9825
|
this.log(`第${no}章 豁免通过(无 high)`);
|
|
9570
9826
|
return;
|
|
9571
9827
|
}
|
|
9572
|
-
|
|
9828
|
+
let plan = buildRevisionPlan(project, {
|
|
9829
|
+
chapterNo: no,
|
|
9830
|
+
reviewIssues: highs,
|
|
9831
|
+
baseReport: report
|
|
9832
|
+
});
|
|
9833
|
+
this.log(`第${no}章 修订(${describeRevisionPlan(plan)})…`);
|
|
9834
|
+
if (plan.omitted > 0) this.log(`第${no}章 修订指令已截断 ${plan.omitted} 条(超出单轮上限,优先级低者留待下一轮)`);
|
|
9573
9835
|
for (let round = 1; round <= 2; round++) {
|
|
9574
|
-
const instr =
|
|
9836
|
+
const instr = plan.instruction;
|
|
9575
9837
|
try {
|
|
9576
9838
|
for await (const _step of rewriteChapterStream(ctx, config, project, outputDir, no, instr, void 0));
|
|
9577
9839
|
} catch (error) {
|
|
@@ -9585,7 +9847,7 @@ var ProductionRunner = class {
|
|
|
9585
9847
|
this.log(`第${no}章 第${round}轮草稿缺失,重试`);
|
|
9586
9848
|
continue;
|
|
9587
9849
|
}
|
|
9588
|
-
const verify = await reviewChapterText(ctx, config, fresh, draft,
|
|
9850
|
+
const verify = await reviewChapterText(ctx, config, fresh, draft, plan.baseline);
|
|
9589
9851
|
const highs2 = (verify.issues ?? []).filter((i) => i.severity === "high");
|
|
9590
9852
|
if (verify.passed || highs2.length === 0) {
|
|
9591
9853
|
this.applyDraft(fresh, freshCh, draft, verify);
|
|
@@ -9594,9 +9856,14 @@ var ProductionRunner = class {
|
|
|
9594
9856
|
return;
|
|
9595
9857
|
}
|
|
9596
9858
|
this.log(`第${no}章 第${round}轮仍不过(${highs2.length} high)`);
|
|
9859
|
+
plan = buildRevisionPlan(fresh ?? project, {
|
|
9860
|
+
chapterNo: no,
|
|
9861
|
+
reviewIssues: highs2,
|
|
9862
|
+
baseReport: verify
|
|
9863
|
+
});
|
|
9597
9864
|
}
|
|
9598
9865
|
if (this.state !== null) this.state.pendingManual.push(no);
|
|
9599
|
-
this.log(`第${no}章
|
|
9866
|
+
this.log(`第${no}章 2 轮修订仍不过 → 保留草稿待人工`);
|
|
9600
9867
|
}
|
|
9601
9868
|
applyDraft(project, chapter, draft, report) {
|
|
9602
9869
|
const config = this.deps.getConfig();
|
|
@@ -13046,6 +13313,49 @@ function makeRoutes(deps) {
|
|
|
13046
13313
|
}
|
|
13047
13314
|
}
|
|
13048
13315
|
};
|
|
13316
|
+
/** 修订指令合并:POST { op: 'plan', chapterNo, reviewIssues? } → 一份合并指令 + 验证基准。
|
|
13317
|
+
* 面板与生产单共用,保证「同一章只改一轮」;张力/时间线问题默认只是建议(advisory)。 */
|
|
13318
|
+
const revisionPlanRoute = {
|
|
13319
|
+
kind: "exact",
|
|
13320
|
+
path: NOVEL_API.revisionPlan,
|
|
13321
|
+
handler: async (req, res) => {
|
|
13322
|
+
if (!guard(req, res, "POST")) return;
|
|
13323
|
+
const config = getConfig();
|
|
13324
|
+
const body = await readJsonBody(req);
|
|
13325
|
+
const project = loadProject(resolveOutputDir(config, body?.bookId));
|
|
13326
|
+
if (project === void 0) {
|
|
13327
|
+
writeJson(res, 400, { error: "输出目录中没有项目" });
|
|
13328
|
+
return;
|
|
13329
|
+
}
|
|
13330
|
+
const chapterNo = body?.chapterNo;
|
|
13331
|
+
if (chapterNo !== void 0 && !project.chapters.some((c) => c.no === chapterNo)) {
|
|
13332
|
+
writeJson(res, 404, { error: `章节 ${String(chapterNo)} 不在计划中` });
|
|
13333
|
+
return;
|
|
13334
|
+
}
|
|
13335
|
+
const chapter = chapterNo === void 0 ? void 0 : project.chapters.find((c) => c.no === chapterNo);
|
|
13336
|
+
try {
|
|
13337
|
+
const plan = buildRevisionPlan(project, {
|
|
13338
|
+
chapterNo,
|
|
13339
|
+
reviewIssues: body?.reviewIssues,
|
|
13340
|
+
includeTimeline: body?.includeTimeline,
|
|
13341
|
+
includeTension: body?.includeTension,
|
|
13342
|
+
baseReport: chapter?.review,
|
|
13343
|
+
cap: 8
|
|
13344
|
+
});
|
|
13345
|
+
writeJson(res, 200, {
|
|
13346
|
+
chapterNo: chapterNo ?? 0,
|
|
13347
|
+
items: plan.items,
|
|
13348
|
+
instruction: plan.instruction,
|
|
13349
|
+
counts: plan.counts,
|
|
13350
|
+
omitted: plan.omitted,
|
|
13351
|
+
baseline: plan.baseline,
|
|
13352
|
+
summary: describeRevisionPlan(plan)
|
|
13353
|
+
});
|
|
13354
|
+
} catch (error) {
|
|
13355
|
+
writeJson(res, 500, { error: error.message });
|
|
13356
|
+
}
|
|
13357
|
+
}
|
|
13358
|
+
};
|
|
13049
13359
|
/** 章节历史版本:GET 列表;POST op=create|restore|remove|prune。 */
|
|
13050
13360
|
const snapshotRoute = {
|
|
13051
13361
|
kind: "exact",
|
|
@@ -14928,6 +15238,7 @@ function makeRoutes(deps) {
|
|
|
14928
15238
|
llmPromptRoute,
|
|
14929
15239
|
usageResetRoute,
|
|
14930
15240
|
timelineRoute,
|
|
15241
|
+
revisionPlanRoute,
|
|
14931
15242
|
snapshotRoute,
|
|
14932
15243
|
tensionRoute,
|
|
14933
15244
|
promptSlotsRoute,
|
|
@@ -15113,7 +15424,7 @@ const DEFAULT_AUTO_REVIEW_AFTER_REVISE = true;
|
|
|
15113
15424
|
/** Order of the announcement section within the tool-guidance band. */
|
|
15114
15425
|
const SECTION_ORDER = 160;
|
|
15115
15426
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
|
15116
|
-
const NOVEL_GUIDANCE = "本机已安装 dsh-novel-forge 插件(AI 编译小说工作台):侧边栏「小说工坊」入口。能力:读取 docx
|
|
15427
|
+
const NOVEL_GUIDANCE = "本机已安装 dsh-novel-forge 插件(AI 编译小说工作台):侧边栏「小说工坊」入口。能力:读取 docx 大纲、粘贴大纲文本或把 txt 全本拆章导入;用 LLM 提炼道藏(人设/世界观/金手指规则/写作红线)与大世界(境界/区域/势力);生成卷计划与结构化章节计划(本章目标/剧情要点/必达项/义务合约/章末钩子);逐章生成 3000-4000 字正文并保存为 Markdown(默认输出到用户主目录 ~/.dsh/novels);每章自动生成摘要与编年录事实、抽取故事时间线(故事内时间/地点/在场角色/事件)、自动 AI 审稿(人设/设定/红线/文笔/爽点/逻辑/反 AI/呈现/合规九维)与作者复盘;支持按审稿意见合并修订(审稿 high + 时间线矛盾 + 张力偏差合成一轮,每章最多两轮后转人工)、去 AI 味润色、章节历史版本回滚、暗线(伏笔)管理、剧情线与角色库、张力曲线、全书一致性质检与敏感词扫描、分范围导出与项目备份、批量连写(生产单,可暂停/续跑)。限制:所有 AI 操作消耗 LLM API 额度;输出目录与模型可在插件设置中修改;章节正文质量取决于大纲完整度;张力/时间线问题默认只是建议(写日志与待办),不会自动改正文。用户提到「小说 / 大纲 / 写小说 / 章节 / 审稿 / 润色」时即指本插件,请据此协作。";
|
|
15117
15428
|
/** Resolve a config-like value into the full runtime config. */
|
|
15118
15429
|
function resolveConfig(value) {
|
|
15119
15430
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* fetch, same origin; generation/rewrite/polish ride NDJSON streams read
|
|
4
4
|
* incrementally.
|
|
5
5
|
*/
|
|
6
|
-
import { type AssetsPatch, type AssetsResponse, type AddModelRequest, type AddModelResponse, type LlmModelsResponse, type LlmProvidersResponse, type LlmVendorsResponse, type RemoveProviderRequest, type RemoveProviderResponse, type BibleResponse, type ChapterResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type AdaptExecuteRequest, type AdaptRewriteFrame, type AdaptMaterializeSaveRequest, type AdaptMaterializeSaveResponse, type PluginUpdateResponse, type LlmTestResponse, type NovelConfig, type PlanResponse, type ReviewReport, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
|
|
6
|
+
import { type AssetsPatch, type AssetsResponse, type AddModelRequest, type AddModelResponse, type LlmModelsResponse, type LlmProvidersResponse, type LlmVendorsResponse, type RemoveProviderRequest, type RemoveProviderResponse, type BibleResponse, type ChapterResponse, type ConfigPatch, type ExportResponse, type ForeshadowRequest, type ForeshadowResponse, type JobFrame, type LoadOutlineResponse, type AdaptExecuteRequest, type AdaptRewriteFrame, type AdaptMaterializeSaveRequest, type AdaptMaterializeSaveResponse, type PluginUpdateResponse, type LlmTestResponse, type NovelConfig, type PlanResponse, type ReviewReport, type RevisionPlanResponse, type StatusResponse, type StyleEngineRequest, type StyleAsset, type VolumesResponse } from '../protocol.ts';
|
|
7
7
|
/** Error carrying the route's JSON error message. */
|
|
8
8
|
export declare class NovelApiError extends Error {
|
|
9
9
|
constructor(message: string);
|
|
@@ -39,6 +39,14 @@ export declare class NovelApi {
|
|
|
39
39
|
chapterCheck(no: number, text: string, previousReport?: ReviewReport): Promise<{
|
|
40
40
|
report: ReviewReport;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* 修订指令合并:把本章的审稿意见(勾选项)+ 时间线矛盾 + 张力偏差合成**一份**指令。
|
|
44
|
+
* 面板所有「按意见修订 / 一键按建议修订」入口都先走这里,保证同一章只改一轮。
|
|
45
|
+
*/
|
|
46
|
+
revisionPlan(no: number, reviewIssues?: ReviewReport['issues'], opts?: {
|
|
47
|
+
includeTimeline?: boolean;
|
|
48
|
+
includeTension?: boolean;
|
|
49
|
+
}): Promise<RevisionPlanResponse>;
|
|
42
50
|
/** 保存手动编辑的正文(自动备份 .bak;带报告则沿用落盘,否则保存后自动审稿)。 */
|
|
43
51
|
chapterSave(no: number, text: string, report?: ReviewReport): Promise<import('../protocol.ts').ChapterSaveResponse>;
|
|
44
52
|
patchConfig(patch: ConfigPatch): Promise<{
|
|
@@ -1,57 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-novel-forge —
|
|
2
|
+
* dsh-novel-forge — 面板文案词典(简体中文单语)。
|
|
3
|
+
*
|
|
4
|
+
* 定位说明:小说工坊面板是**中文单语界面**——这里就是全部用户可见文案的唯一定义处,
|
|
5
|
+
* 键位即类型(`NovelKey`),新增文案请同时在此登记,不要散落到组件里硬编码。
|
|
6
|
+
* 文档层(README / docs / CHANGELOG)仍提供英文对照,那是给人读的,不是界面语言。
|
|
3
7
|
*/
|
|
4
|
-
/**
|
|
8
|
+
/** 面板文案(简体中文)。 */
|
|
5
9
|
export declare const zh: {
|
|
6
10
|
readonly 'entry.label': "小说工坊";
|
|
7
11
|
readonly 'entry.tooltip': "AI 编译小说工作台:总纲 → 道藏 → 卷计划 → 章节计划 → 逐章生成+审稿";
|
|
8
|
-
readonly 'panel.title': "小说工坊";
|
|
9
12
|
readonly 'common.close': "关闭";
|
|
10
13
|
readonly 'common.loading': "加载中…";
|
|
11
|
-
readonly 'common.save': "保存";
|
|
12
14
|
readonly 'common.error': "错误";
|
|
13
|
-
readonly 'common.success': "成功";
|
|
14
|
-
readonly 'common.generating': "生成中…";
|
|
15
15
|
readonly 'common.chars': "字";
|
|
16
16
|
readonly 'tab.workflow': "工作台";
|
|
17
17
|
readonly 'tab.overview': "总纲";
|
|
18
18
|
readonly 'tab.plan': "章节";
|
|
19
|
-
readonly 'tab.bible': "道藏";
|
|
20
|
-
readonly 'tab.foreshadow': "暗线";
|
|
21
19
|
readonly 'tab.assistant': "AI 编辑 Agent";
|
|
22
20
|
readonly 'tab.settings': "设置";
|
|
23
|
-
readonly 'workflow.title': "创作工作台";
|
|
24
|
-
readonly 'workflow.step1': "① 加载总纲";
|
|
25
|
-
readonly 'workflow.step2': "② 提炼道藏";
|
|
26
|
-
readonly 'workflow.step3': "③ 规划卷";
|
|
27
|
-
readonly 'workflow.step4': "④ 生成章节计划";
|
|
28
|
-
readonly 'workflow.step5': "⑤ 逐章写作 + AI 审稿";
|
|
29
|
-
readonly 'workflow.step6': "⑥ 润色 / 导出";
|
|
30
|
-
readonly 'workflow.loadOutline': "读取总纲";
|
|
31
|
-
readonly 'workflow.genBible': "提炼道藏";
|
|
32
21
|
readonly 'workflow.genVolumes': "生成卷计划";
|
|
33
|
-
readonly 'workflow.genPlan': "生成章节计划";
|
|
34
|
-
readonly 'workflow.done': "已完成";
|
|
35
|
-
readonly 'workflow.todo': "待办";
|
|
36
22
|
readonly 'workflow.bibleDone': "道藏已生成({n} 条规则 / {c} 个人物 / {r} 条红线)";
|
|
37
23
|
readonly 'workflow.volumesDone': "卷计划已生成({n} 卷)";
|
|
38
24
|
readonly 'workflow.planDone': "章节计划已生成({n} 章)";
|
|
39
|
-
readonly 'workflow.progress': "进度:总纲 ✓ · 道藏 {bible} · 卷 {volumes} · 计划 {plan} · 已完成 {done}/{total} 章";
|
|
40
|
-
readonly 'overview.loadDocx': "从 docx 读取总纲";
|
|
41
|
-
readonly 'overview.loadDocxDefault': "读取默认总纲";
|
|
42
25
|
readonly 'overview.loadingOutline': "正在解析 docx…";
|
|
43
26
|
readonly 'overview.outlineHint': "总纲文本(可编辑)";
|
|
44
27
|
readonly 'overview.outlineChars': "总纲字数";
|
|
45
28
|
readonly 'overview.saveOutline': "保存总纲";
|
|
46
29
|
readonly 'overview.saved': "总纲已保存";
|
|
47
30
|
readonly 'overview.bookName': "书名";
|
|
48
|
-
readonly 'overview.loadCustom': "指定 docx 路径";
|
|
49
|
-
readonly 'overview.loadCustomHint': "绝对路径,留空使用默认";
|
|
50
31
|
readonly 'plan.generate': "生成章节计划";
|
|
51
32
|
readonly 'plan.generateHint': "章节数量";
|
|
52
33
|
readonly 'plan.count': "章";
|
|
53
|
-
readonly 'plan.empty': "暂无章节计划,请先生成";
|
|
54
|
-
readonly 'plan.chapter': "章";
|
|
55
34
|
readonly 'plan.pending': "待生成";
|
|
56
35
|
readonly 'plan.generating': "生成中";
|
|
57
36
|
readonly 'plan.written': "待审稿";
|
|
@@ -63,21 +42,15 @@ export declare const zh: {
|
|
|
63
42
|
readonly 'plan.rewrite': "修订";
|
|
64
43
|
readonly 'plan.review': "审稿";
|
|
65
44
|
readonly 'plan.polish': "去AI味";
|
|
66
|
-
readonly 'plan.writeAll': "批量生成全部";
|
|
67
45
|
readonly 'plan.writeAllPending': "批量生成剩余";
|
|
68
|
-
readonly 'plan.generated': "已生成";
|
|
69
|
-
readonly 'plan.progress': "进度";
|
|
70
46
|
readonly 'plan.beats': "剧情要点";
|
|
71
47
|
readonly 'plan.reviewReport': "审稿报告";
|
|
72
48
|
readonly 'plan.reviewScore': "评分";
|
|
73
49
|
readonly 'plan.reviewVerdict': "总评";
|
|
74
|
-
readonly 'plan.reviewIssues': "问题清单";
|
|
75
50
|
readonly 'plan.reviewPass': "通过";
|
|
76
51
|
readonly 'plan.reviewFail': "未通过";
|
|
77
|
-
readonly 'plan.approve': "手动通过";
|
|
78
52
|
readonly 'plan.summary': "章节摘要";
|
|
79
53
|
readonly 'plan.volumes': "卷";
|
|
80
|
-
readonly 'plan.noVolume': "未分卷";
|
|
81
54
|
readonly 'bible.title': "道藏";
|
|
82
55
|
readonly 'bible.gen': "提炼道藏";
|
|
83
56
|
readonly 'bible.genre': "题材基调";
|
|
@@ -89,7 +62,6 @@ export declare const zh: {
|
|
|
89
62
|
readonly 'foreshadow.title': "暗线管理";
|
|
90
63
|
readonly 'foreshadow.suggest': "建议暗线";
|
|
91
64
|
readonly 'foreshadow.none': "暂无暗线";
|
|
92
|
-
readonly 'foreshadow.status': "状态";
|
|
93
65
|
readonly 'foreshadow.planned': "计划中";
|
|
94
66
|
readonly 'foreshadow.planted': "已埋设";
|
|
95
67
|
readonly 'foreshadow.progressing': "推进中";
|
|
@@ -99,7 +71,6 @@ export declare const zh: {
|
|
|
99
71
|
readonly 'foreshadow.plantedAt': "埋设于";
|
|
100
72
|
readonly 'foreshadow.setPlanted': "标记已埋设";
|
|
101
73
|
readonly 'foreshadow.setResolved': "标记已回收";
|
|
102
|
-
readonly 'tab.facts': "编年录";
|
|
103
74
|
readonly 'tab.plotlines': "剧情线";
|
|
104
75
|
readonly 'plotlines.hint': "主线/支线/人物线/悬念线的推进管理:章节生成时会注入未完结的线,要求每章至少推进一条;关联章节记录推进节点。";
|
|
105
76
|
readonly 'plotlines.empty': "还没有剧情线——长篇小说建议先建主线与几条支线。";
|
|
@@ -121,11 +92,7 @@ export declare const zh: {
|
|
|
121
92
|
readonly 'plotlines.cancel': "取消";
|
|
122
93
|
readonly 'plotlines.edit': "编辑";
|
|
123
94
|
readonly 'plotlines.remove': "删除";
|
|
124
|
-
readonly 'plotlines.linkChapter': "关联本章";
|
|
125
95
|
readonly 'plotlines.chapters': "已关联";
|
|
126
|
-
readonly 'plotlines.workflowTitle': "剧情线进度";
|
|
127
|
-
readonly 'plotlines.workflowEmpty': "未建剧情线(去「剧情线」页创建)";
|
|
128
|
-
readonly 'sensitive.title': "敏感词检查";
|
|
129
96
|
readonly 'sensitive.scanAll': " 全书敏感词检查";
|
|
130
97
|
readonly 'sensitive.hint': "内置违禁词库硬匹配(政治/擦边/暴力/辱骂/广告/其他),发布前建议检查;只做提示,是否修改由你判断。";
|
|
131
98
|
readonly 'sensitive.clean': " 未命中任何违禁词(扫描 {n} 章)";
|
|
@@ -135,9 +102,6 @@ export declare const zh: {
|
|
|
135
102
|
readonly 'facts.title': "编年录 / 时间线({n} 条)";
|
|
136
103
|
readonly 'facts.hint': "每章生成后自动抽取「已确立事实」(人物状态/境界资源/关系变化/伏笔落地),最近 20 条注入后续章节生成,保证长期一致。";
|
|
137
104
|
readonly 'facts.backfill': "回填历史章节事实";
|
|
138
|
-
readonly 'facts.backfilled': "已从历史章节回填 {n} 条事实";
|
|
139
|
-
readonly 'settings.title': "设置";
|
|
140
|
-
readonly 'settings.outlinePath': "默认大纲路径";
|
|
141
105
|
readonly 'settings.outputDir': "输出目录";
|
|
142
106
|
readonly 'settings.provider': "模型提供商";
|
|
143
107
|
readonly 'settings.model': "模型";
|
|
@@ -146,13 +110,7 @@ export declare const zh: {
|
|
|
146
110
|
readonly 'settings.edit': "编辑";
|
|
147
111
|
readonly 'settings.delete': "删除";
|
|
148
112
|
readonly 'settings.modelCustomPlaceholder': "输入任意模型 id";
|
|
149
|
-
readonly 'settings.savedModels': "我的模型";
|
|
150
|
-
readonly 'settings.modelCards': "模型卡片(内置 + 已添加)";
|
|
151
|
-
readonly 'settings.savedModelName': "名称";
|
|
152
|
-
readonly 'settings.addModel': "添加模型";
|
|
153
|
-
readonly 'settings.catalogRefresh': "刷新厂商";
|
|
154
113
|
readonly 'settings.refreshModels': "刷新模型";
|
|
155
|
-
readonly 'settings.savedModelsHint': "手动添加常用模型(provider + 模型 id),点击即可切换当前模型;厂商模式会写入 DSH 凭据并注册路由。";
|
|
156
114
|
readonly 'settings.testConnection': "测试连通";
|
|
157
115
|
readonly 'settings.testRunning': "测试中";
|
|
158
116
|
readonly 'settings.testOk': "连通";
|
|
@@ -167,15 +125,9 @@ export declare const zh: {
|
|
|
167
125
|
readonly 'settings.reasoning.low': "low · 低思考";
|
|
168
126
|
readonly 'settings.reasoning.high': "high · 高思考";
|
|
169
127
|
readonly 'settings.reasoning.max': "max · 最高思考(质检/推演)";
|
|
170
|
-
readonly 'settings.theme': "主题";
|
|
171
|
-
readonly 'settings.themeLiquid': "iOS 液态玻璃(绿)";
|
|
172
|
-
readonly 'settings.themeNeumorph': "新拟物(双阴影)";
|
|
173
|
-
readonly 'settings.themeHint': "存于浏览器本地,即时生效";
|
|
174
128
|
readonly 'settings.chapterChars': "每章目标字数";
|
|
175
129
|
readonly 'settings.maxTokens': "单章最大输出 tokens";
|
|
176
130
|
readonly 'settings.reviewPassScore': "审稿通过分数(0-100)";
|
|
177
|
-
readonly 'settings.editorFontSize': "编辑器字号(仅编辑页显示)";
|
|
178
|
-
readonly 'settings.editorFontSizeHint': "存于浏览器本地,不影响其他设备";
|
|
179
131
|
readonly 'settings.autoReview': "生成后自动审稿";
|
|
180
132
|
readonly 'settings.autoAuthorReview': "生成后自动作者复盘";
|
|
181
133
|
readonly 'settings.autoAuthorReviewHint': "复盘钩子兑现/结尾钩子/剧情线推进/连续性/节奏趋势,每章约 2000 token";
|
|
@@ -184,7 +136,6 @@ export declare const zh: {
|
|
|
184
136
|
readonly 'settings.save': "保存设置";
|
|
185
137
|
readonly 'settings.saved': "设置已保存";
|
|
186
138
|
readonly 'settings.openFolder': "打开输出文件夹";
|
|
187
|
-
readonly 'settings.export': "导出";
|
|
188
139
|
readonly 'settings.exportTxt': "导出 TXT";
|
|
189
140
|
readonly 'settings.exportMd': "导出 Markdown";
|
|
190
141
|
readonly 'settings.exported': "已导出:{file}({chars} 字,{chapters} 章)";
|
|
@@ -194,19 +145,12 @@ export declare const zh: {
|
|
|
194
145
|
readonly 'progress.error': "第 {no} 章失败:{message}";
|
|
195
146
|
readonly 'progress.rewriting': "正在修订第 {no} 章…";
|
|
196
147
|
readonly 'progress.polishing': "正在润色第 {no} 章…";
|
|
197
|
-
readonly 'progress.empty': "生成/审稿进度将显示在这里";
|
|
198
148
|
readonly 'assistant.hint': "和 AI 编辑 Agent 讨论剧情、人设、暗线;达成一致后可让它直接修改总纲、道藏、章节内容。";
|
|
199
149
|
readonly 'assistant.placeholder': "例如:我想让第 2 章结尾加一个悬念——墟境里传来爷爷的声音…";
|
|
200
150
|
readonly 'assistant.send': "发送";
|
|
201
|
-
readonly 'assistant.toolStart': " 执行操作:{name}…";
|
|
202
|
-
readonly 'assistant.toolDone': "✓ {name} 完成:{detail}";
|
|
203
|
-
readonly 'assistant.toolError': "✗ {name} 失败:{detail}";
|
|
204
151
|
readonly 'assistant.empty': "还没有对话。和 AI 编辑 Agent 聊聊剧情吧。";
|
|
205
152
|
readonly 'status.projectNone': "输出目录中还没有项目。请先加载大纲。";
|
|
206
153
|
readonly 'status.files': "已生成文件";
|
|
207
|
-
readonly 'api.error': "请求失败";
|
|
208
154
|
};
|
|
209
|
-
/**
|
|
210
|
-
export declare const en: Record<string, string>;
|
|
211
|
-
/** Keys type for the zh dictionary. */
|
|
155
|
+
/** 面板文案键位(= zh 词典的键)。 */
|
|
212
156
|
export type NovelKey = keyof typeof zh;
|
|
@@ -10,5 +10,9 @@
|
|
|
10
10
|
import type { NovelApi } from '../api.ts';
|
|
11
11
|
export interface TensionViewProps {
|
|
12
12
|
api: NovelApi;
|
|
13
|
+
/** 一键按建议修订(合并审稿/张力/时间线后改这一章;由父级统一发起)。 */
|
|
14
|
+
onRevise?: (chapterNo: number) => void;
|
|
15
|
+
/** 外部忙碌态(修订进行中时禁用按钮)。 */
|
|
16
|
+
busy?: boolean;
|
|
13
17
|
}
|
|
14
|
-
export default function TensionView({ api }: TensionViewProps): JSX.Element;
|
|
18
|
+
export default function TensionView({ api, onRevise, busy: outerBusy }: TensionViewProps): JSX.Element;
|
|
@@ -10,5 +10,9 @@ export interface TimelineViewProps {
|
|
|
10
10
|
api: NovelApi;
|
|
11
11
|
/** 章节号列表(用于「抽取本章」下拉)。 */
|
|
12
12
|
chapters: number[];
|
|
13
|
+
/** 一键按建议修订(合并审稿/时间线/张力后改这一章;由父级统一发起)。 */
|
|
14
|
+
onRevise?: (chapterNo: number) => void;
|
|
15
|
+
/** 外部忙碌态(修订进行中时禁用按钮)。 */
|
|
16
|
+
busy?: boolean;
|
|
13
17
|
}
|
|
14
|
-
export default function TimelineView({ api, chapters }: TimelineViewProps): JSX.Element;
|
|
18
|
+
export default function TimelineView({ api, chapters, onRevise, busy: outerBusy }: TimelineViewProps): JSX.Element;
|