@waterwx/dsh-novel-forge 2.1.3 → 2.1.4
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/lib/client.js +757 -457
- package/lib/index.js +77 -22
- package/lib/types/client/api.d.ts +2 -0
- package/lib/types/client/panel/StyleView.d.ts +18 -0
- package/lib/types/engine.d.ts +26 -0
- package/lib/types/protocol.d.ts +27 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1546,12 +1546,7 @@ const BUILTIN_ANTI_AI_RULES = [
|
|
|
1546
1546
|
name: "句式重复率偏高",
|
|
1547
1547
|
avoid: "连续使用同构句式(「首先…然后…接着…最后…」),读起来机械。",
|
|
1548
1548
|
fix: "拉开句长与起句方式,打散结构。",
|
|
1549
|
-
detectPatterns: [
|
|
1550
|
-
"首先",
|
|
1551
|
-
"然后",
|
|
1552
|
-
"接着",
|
|
1553
|
-
"最后"
|
|
1554
|
-
],
|
|
1549
|
+
detectPatterns: ["首先"],
|
|
1555
1550
|
builtin: true,
|
|
1556
1551
|
key: "risk-repeated-sentence-structure",
|
|
1557
1552
|
severity: "forbidden",
|
|
@@ -5347,6 +5342,7 @@ const NOVEL_API = {
|
|
|
5347
5342
|
revisionPlan: "/api/dsh-novel-forge/revision/plan",
|
|
5348
5343
|
/** 张力曲线(GET 数据 / POST 设置)。 */
|
|
5349
5344
|
tension: "/api/dsh-novel-forge/tension",
|
|
5345
|
+
styleHistory: "/api/dsh-novel-forge/style-history",
|
|
5350
5346
|
/** 提示词槽位(GET 列表 / POST 写入)。 */
|
|
5351
5347
|
promptSlots: "/api/dsh-novel-forge/prompt-slots",
|
|
5352
5348
|
/** 故事时间线(GET 列表 / POST 操作)。 */
|
|
@@ -6745,15 +6741,15 @@ const STYLE_BASELINE_CHAPTERS = 12;
|
|
|
6745
6741
|
/** 低于此章数时中位数没有代表性,退回单一样本参照。 */
|
|
6746
6742
|
const STYLE_BASELINE_MIN_CHAPTERS = 3;
|
|
6747
6743
|
/**
|
|
6748
|
-
*
|
|
6744
|
+
* 解析本书的风格基线 —— **审稿与漂移曲线共用这一份**,保证两条路同源。
|
|
6749
6745
|
*
|
|
6750
|
-
*
|
|
6751
|
-
*
|
|
6746
|
+
* 优先用本书**已过审章节的中位数**:单一样本的噪声会整个混进比对(样本碰巧长什么样,
|
|
6747
|
+
* 后面所有章都在跟它比),多章中位数对离群值不敏感。不足 3 章时退回单一样本。
|
|
6748
|
+
* @param outputDir - 书目录;缺省时只能用写法样本。
|
|
6752
6749
|
* @param project - 当前书项目状态。
|
|
6753
|
-
* @
|
|
6754
|
-
* @returns 可注入审稿 prompt 的文本;没有可用参照时为空串。
|
|
6750
|
+
* @returns 基线;既无已过审章节也无带样本文本的写法资产时返回 undefined。
|
|
6755
6751
|
*/
|
|
6756
|
-
function
|
|
6752
|
+
function styleBaseline(outputDir, project) {
|
|
6757
6753
|
if (outputDir !== void 0) {
|
|
6758
6754
|
const approved = project.chapters.filter((c) => c.status === "approved" && c.file !== "");
|
|
6759
6755
|
if (approved.length >= STYLE_BASELINE_MIN_CHAPTERS) {
|
|
@@ -6766,22 +6762,28 @@ function styleFidelityBlock(project, bodyText, outputDir) {
|
|
|
6766
6762
|
const fp = extractStyleFingerprint(stripChapterHeadings(raw));
|
|
6767
6763
|
if (fp.sentences > 0) samples.push(fp);
|
|
6768
6764
|
}
|
|
6769
|
-
if (samples.length >= STYLE_BASELINE_MIN_CHAPTERS) {
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
}
|
|
6765
|
+
if (samples.length >= STYLE_BASELINE_MIN_CHAPTERS) return {
|
|
6766
|
+
fingerprint: mergeFingerprints(samples),
|
|
6767
|
+
label: "本书已过审 " + samples.length + " 章的中位数"
|
|
6768
|
+
};
|
|
6774
6769
|
}
|
|
6775
6770
|
}
|
|
6776
6771
|
for (const style of project.assets?.styleAssets ?? []) {
|
|
6777
6772
|
const sample = style.sourceText;
|
|
6778
6773
|
if (sample === void 0 || sample === "") continue;
|
|
6779
|
-
const
|
|
6780
|
-
if (
|
|
6781
|
-
|
|
6782
|
-
|
|
6774
|
+
const fingerprint = extractStyleFingerprint(sample);
|
|
6775
|
+
if (fingerprint.sentences === 0) continue;
|
|
6776
|
+
return {
|
|
6777
|
+
fingerprint,
|
|
6778
|
+
label: "绑定写法资产的样本文本"
|
|
6779
|
+
};
|
|
6783
6780
|
}
|
|
6784
|
-
|
|
6781
|
+
}
|
|
6782
|
+
function styleFidelityBlock(project, bodyText, outputDir) {
|
|
6783
|
+
const baseline = styleBaseline(outputDir, project);
|
|
6784
|
+
if (baseline === void 0) return "";
|
|
6785
|
+
const actual = extractStyleFingerprint(bodyText);
|
|
6786
|
+
return renderFingerprintComparison(baseline.fingerprint, actual, compareStyleFingerprint(baseline.fingerprint, actual), baseline.label);
|
|
6785
6787
|
}
|
|
6786
6788
|
/** Run the AI review on one chapter. */
|
|
6787
6789
|
async function reviewChapter(ctx, config, project, outputDir, chapterNo) {
|
|
@@ -13957,6 +13959,58 @@ function makeRoutes(deps) {
|
|
|
13957
13959
|
writeJson(res, 200, { scan: scanAiFlavor(text.slice(0, 2e4)) });
|
|
13958
13960
|
}
|
|
13959
13961
|
};
|
|
13962
|
+
/**
|
|
13963
|
+
* 风格漂移曲线:逐章风格指标 + 参照基线。
|
|
13964
|
+
*
|
|
13965
|
+
* 数据**即时算、不落盘**:语料就是章节文件本身,存一份快照只会带来陈旧问题。
|
|
13966
|
+
* 基线与审稿同源(styleBaseline),所以图上看到的就是审稿时判偏离用的那把尺子。
|
|
13967
|
+
*/
|
|
13968
|
+
const styleHistoryRoute = {
|
|
13969
|
+
kind: "exact",
|
|
13970
|
+
path: NOVEL_API.styleHistory,
|
|
13971
|
+
handler: (req, res) => {
|
|
13972
|
+
if (!guard(req, res, "GET")) return;
|
|
13973
|
+
const outputDir = resolveOutputDir(getConfig(), new URL(req.url ?? "/", "http://localhost").searchParams.get("bookId") ?? void 0);
|
|
13974
|
+
const project = loadProject(outputDir);
|
|
13975
|
+
if (project === void 0) {
|
|
13976
|
+
writeJson(res, 200, {
|
|
13977
|
+
baselineLabel: "",
|
|
13978
|
+
baseline: null,
|
|
13979
|
+
points: []
|
|
13980
|
+
});
|
|
13981
|
+
return;
|
|
13982
|
+
}
|
|
13983
|
+
const baseline = styleBaseline(outputDir, project);
|
|
13984
|
+
const points = [];
|
|
13985
|
+
for (const chapter of project.chapters) {
|
|
13986
|
+
if (chapter.file === void 0 || chapter.file === "") continue;
|
|
13987
|
+
const raw = readChapterFile(outputDir, chapter);
|
|
13988
|
+
if (raw === void 0) continue;
|
|
13989
|
+
const fingerprint = extractStyleFingerprint(stripChapterHeadings(raw));
|
|
13990
|
+
const measured = fingerprint.sentences > 0;
|
|
13991
|
+
points.push({
|
|
13992
|
+
no: chapter.no,
|
|
13993
|
+
title: chapter.title,
|
|
13994
|
+
measured,
|
|
13995
|
+
avgSentenceLength: fingerprint.avgSentenceLength,
|
|
13996
|
+
shortSentenceRatio: fingerprint.shortSentenceRatio,
|
|
13997
|
+
dialogueRatio: fingerprint.dialogueRatio,
|
|
13998
|
+
avgParagraphLength: fingerprint.avgParagraphLength,
|
|
13999
|
+
significant: baseline !== void 0 && measured ? compareStyleFingerprint(baseline.fingerprint, fingerprint).filter((d) => d.significant).map((d) => d.metric + (d.direction === "higher" ? "↑" : "↓")) : []
|
|
14000
|
+
});
|
|
14001
|
+
}
|
|
14002
|
+
writeJson(res, 200, {
|
|
14003
|
+
baselineLabel: baseline?.label ?? "",
|
|
14004
|
+
baseline: baseline === void 0 ? null : {
|
|
14005
|
+
avgSentenceLength: baseline.fingerprint.avgSentenceLength,
|
|
14006
|
+
shortSentenceRatio: baseline.fingerprint.shortSentenceRatio,
|
|
14007
|
+
dialogueRatio: baseline.fingerprint.dialogueRatio,
|
|
14008
|
+
avgParagraphLength: baseline.fingerprint.avgParagraphLength
|
|
14009
|
+
},
|
|
14010
|
+
points
|
|
14011
|
+
});
|
|
14012
|
+
}
|
|
14013
|
+
};
|
|
13960
14014
|
/** 热门题材雷达:输入平台/题材/榜单文本 → 信号 + 生产底座 + 开书创意。 */
|
|
13961
14015
|
const marketRadarRoute = {
|
|
13962
14016
|
kind: "exact",
|
|
@@ -16400,6 +16454,7 @@ function makeRoutes(deps) {
|
|
|
16400
16454
|
bookshelfImportDirRoute,
|
|
16401
16455
|
bookshelfImportTextRoute,
|
|
16402
16456
|
bookshelfImportTextPreviewRoute,
|
|
16457
|
+
styleHistoryRoute,
|
|
16403
16458
|
resetRoute,
|
|
16404
16459
|
auditRoute,
|
|
16405
16460
|
charactersRefreshRoute,
|
|
@@ -280,6 +280,8 @@ export declare class NovelApi {
|
|
|
280
280
|
assistant(message: string, onFrame: (frame: import('../protocol.ts').AssistantFrame) => void): Promise<void>;
|
|
281
281
|
/** 张力曲线:数据 + 规则核对。 */
|
|
282
282
|
tension(): Promise<import('../protocol.ts').TensionResponse>;
|
|
283
|
+
/** 风格漂移曲线:逐章指标 + 参照基线(与审稿同源)。 */
|
|
284
|
+
styleHistory(): Promise<import('../protocol.ts').StyleHistoryResponse>;
|
|
283
285
|
/** 设置张力曲线预设。 */
|
|
284
286
|
tensionPreset(preset: import('../protocol.ts').TensionCurvePreset): Promise<void>;
|
|
285
287
|
/** 设置/清除某一章的目标张力。 */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 风格漂移曲线:把「每一章离本书自己的风格基线有多远」画出来。
|
|
3
|
+
*
|
|
4
|
+
* 图上三条信息:基线(虚线,本书已过审章节的中位数)、逐章实际(折线)、显著偏离的章(红点)。
|
|
5
|
+
* 指标可切换:句均长 / 短句占比 / 对话占比 / 段落均长。
|
|
6
|
+
*
|
|
7
|
+
* 两点刻意的设计:
|
|
8
|
+
* 1. **基线与审稿同源** —— 用的是同一份 styleBaseline,所以图上看到的就是审稿判偏离用的那把尺子。
|
|
9
|
+
* 若各画各的,作者会看到两个不一致的结论。
|
|
10
|
+
* 2. **数值即时算、不落盘** —— 数据源就是章节文件本身,存一份快照只会带来陈旧问题。
|
|
11
|
+
*
|
|
12
|
+
* 只画四项长度/比率指标:句长起伏与意象密度是诊断用的,画成曲线反而难读。
|
|
13
|
+
*/
|
|
14
|
+
import type { NovelApi } from '../api.ts';
|
|
15
|
+
export interface StyleViewProps {
|
|
16
|
+
api: NovelApi;
|
|
17
|
+
}
|
|
18
|
+
export default function StyleView({ api }: StyleViewProps): JSX.Element;
|
package/lib/types/engine.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const COMPLIANCE_REDLINES: ReadonlyArray<string>;
|
|
14
14
|
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { type StyleFingerprint } from './style-fingerprint.ts';
|
|
15
16
|
import { fullTextBudget } from './model-capability.ts';
|
|
16
17
|
import { applyModelCapability } from './model-request.ts';
|
|
17
18
|
import type { AdaptationDimension, AdaptAnalyzeResponse, AdaptationMapping, AdaptationRules, AdaptProposeResponse, AuditIssue, AuthorReview, ExportScope, TimelineEvent, TimelineIssue, BreakdownResponse, ChapterPlan, Foreshadow, NovelConfig, OutlineCandidate, Plotline, PlotlineHealthReport, PlotlinePlan, ProjectState, ReviewReport, RoleRecord, RoleStatusCard, StoryBible, AddModelRequest, AddModelResponse, LlmModelsResponse, LlmVendorsResponse, LlmProvidersResponse, RemoveProviderRequest, RemoveProviderResponse, LlmTestResponse, Volume, WorldState, AdaptMaterializeRequest, AdaptMaterializeResponse, AdaptMaterializeSaveRequest, AdaptMaterializeSaveResponse, MarketRadarRequest, MarketRadarResult, MarketRadarSignal, MarketRadarBriefRequest, ProductionFoundation, MarketCreativeBrief, BookAnalysisRequest, BookAnalysisResult, IdeaInspirationRequest, IdeaInspirationResult, DirectorRequest, DirectorAdvice } from './protocol.ts';
|
|
@@ -68,6 +69,31 @@ export declare function planVolumes(ctx: Context, config: NovelConfig, outline:
|
|
|
68
69
|
* Plan chapters from an outline (optionally for one volume).
|
|
69
70
|
*/
|
|
70
71
|
export declare function planChapters(ctx: Context, config: NovelConfig, project: ProjectState, chapterCount: number, volumeNo?: number, outputDir?: string): Promise<ChapterPlan[]>;
|
|
72
|
+
/**
|
|
73
|
+
* 风格符合度事实块:拿本章指标与**本书已过审章节的中位数**(或绑定写法样本)对照。
|
|
74
|
+
*
|
|
75
|
+
* 取第一个带 sourceText 的写法资产做参照(提取时 routes.ts 会存下样本前 3000 字)。
|
|
76
|
+
* 样本过短(指标无统计意义)时返回空串——宁可不给,也不给噪声。
|
|
77
|
+
* @param project - 当前书项目状态。
|
|
78
|
+
* @param bodyText - 本章正文(已剥标题)。
|
|
79
|
+
* @returns 可注入审稿 prompt 的文本;没有可用参照时为空串。
|
|
80
|
+
*/
|
|
81
|
+
/** 风格基线:参照指纹 + 人话说明。 */
|
|
82
|
+
export interface StyleBaseline {
|
|
83
|
+
fingerprint: StyleFingerprint;
|
|
84
|
+
/** 供提示词与 UI 显示的来源说明。 */
|
|
85
|
+
label: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 解析本书的风格基线 —— **审稿与漂移曲线共用这一份**,保证两条路同源。
|
|
89
|
+
*
|
|
90
|
+
* 优先用本书**已过审章节的中位数**:单一样本的噪声会整个混进比对(样本碰巧长什么样,
|
|
91
|
+
* 后面所有章都在跟它比),多章中位数对离群值不敏感。不足 3 章时退回单一样本。
|
|
92
|
+
* @param outputDir - 书目录;缺省时只能用写法样本。
|
|
93
|
+
* @param project - 当前书项目状态。
|
|
94
|
+
* @returns 基线;既无已过审章节也无带样本文本的写法资产时返回 undefined。
|
|
95
|
+
*/
|
|
96
|
+
export declare function styleBaseline(outputDir: string | undefined, project: ProjectState): StyleBaseline | undefined;
|
|
71
97
|
/** Run the AI review on one chapter. */
|
|
72
98
|
export declare function reviewChapter(ctx: Context, config: NovelConfig, project: ProjectState, outputDir: string, chapterNo: number): Promise<ReviewReport>;
|
|
73
99
|
/**
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export declare const NOVEL_API: {
|
|
|
33
33
|
readonly revisionPlan: "/api/dsh-novel-forge/revision/plan";
|
|
34
34
|
/** 张力曲线(GET 数据 / POST 设置)。 */
|
|
35
35
|
readonly tension: "/api/dsh-novel-forge/tension";
|
|
36
|
+
readonly styleHistory: "/api/dsh-novel-forge/style-history";
|
|
36
37
|
/** 提示词槽位(GET 列表 / POST 写入)。 */
|
|
37
38
|
readonly promptSlots: "/api/dsh-novel-forge/prompt-slots";
|
|
38
39
|
/** 故事时间线(GET 列表 / POST 操作)。 */
|
|
@@ -2026,6 +2027,32 @@ export interface PlotBeatTemplate {
|
|
|
2026
2027
|
/** 适用题材。 */
|
|
2027
2028
|
applicableGenres: string[];
|
|
2028
2029
|
}
|
|
2030
|
+
/** 风格漂移曲线上的一个点(一章)。 */
|
|
2031
|
+
export interface StyleHistoryPoint {
|
|
2032
|
+
no: number;
|
|
2033
|
+
title: string;
|
|
2034
|
+
/** 该章指纹是否可用(过短的章没有统计意义) */
|
|
2035
|
+
measured: boolean;
|
|
2036
|
+
avgSentenceLength: number;
|
|
2037
|
+
shortSentenceRatio: number;
|
|
2038
|
+
dialogueRatio: number;
|
|
2039
|
+
avgParagraphLength: number;
|
|
2040
|
+
/** 超出容忍度的指标(指标 key + 方向箭头,如 avgSentenceLength↑;中文名由前端映射) */
|
|
2041
|
+
significant: string[];
|
|
2042
|
+
}
|
|
2043
|
+
/** 风格漂移曲线:逐章指标 + 参照基线。 */
|
|
2044
|
+
export interface StyleHistoryResponse {
|
|
2045
|
+
/** 基线说明,如「本书已过审 12 章的中位数」 */
|
|
2046
|
+
baselineLabel: string;
|
|
2047
|
+
/** 基线的四项关键指标(供图上画参考线);无基线时为 null */
|
|
2048
|
+
baseline: {
|
|
2049
|
+
avgSentenceLength: number;
|
|
2050
|
+
shortSentenceRatio: number;
|
|
2051
|
+
dialogueRatio: number;
|
|
2052
|
+
avgParagraphLength: number;
|
|
2053
|
+
} | null;
|
|
2054
|
+
points: StyleHistoryPoint[];
|
|
2055
|
+
}
|
|
2029
2056
|
/** 写法引擎:从样本文本提取的叙事风格资产。 */
|
|
2030
2057
|
export interface StyleAsset {
|
|
2031
2058
|
/** 资产名(如「林越式痞坏」「冷峻猎手风」)。 */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waterwx/dsh-novel-forge",
|
|
3
3
|
"description": "AI 编译纯文本小说工作台 for the dsh web GUI: import a novel outline (docx/text), plan volumes & chapters with LLM, generate chapter by chapter (3000-4000 chars each), auto AI review & book audit, and export the full book as Markdown/TXT into your chosen output folder.",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "waterwx",
|
|
7
7
|
"repository": {
|