فهرست منبع

fix(settings): 将单章目标字数钳制改为 1000–10000

resolveChapterLengthSpec、写作设置 UI 和配置落盘共用同一上下限,避免再出现 UI 能填、运行时被改写的情况。

Co-authored-by: darknessomi <darknessomi@users.noreply.github.com>
Cursor Agent 4 هفته پیش
والد
کامیت
a98cfa6aec

+ 2 - 2
src/lib/changelog.ts

@@ -13,11 +13,11 @@ const THREE_POINT_ONE_FIVE_CHANGELOG: ChangelogEntry = {
   highlights: {
     en: [
       "[MiMo Thinking Fix] Fixed Xiaomi MiMo models hitting the thinking token cap and producing no response. MiMo is now correctly recognized as a chat-template thinking model, so the auto-retry with thinking disabled works properly. Also added max_tokens protection for all OpenAI-compatible thinking models (Qwen3, MiMo, DeepSeek, GLM-5+) to ensure the response has enough room for both reasoning and answer.",
-      "[Writing Settings] Wired de-AI batch concurrency to the chapter queue, stopped silently clamping chapter target length to 2000–6000, persisted the revision-feedback window and chat history immediately, applied retrieval Top-K to graph search, enabled prior-chapter analysis in standard mode, and removed the dead AI-review toggle superseded by workflow modes.",
+      "[Writing Settings] Wired de-AI batch concurrency to the chapter queue, clamped chapter target length to 1000–10000 (matching the writing-settings UI), persisted the revision-feedback window and chat history immediately, applied retrieval Top-K to graph search, enabled prior-chapter analysis in standard mode, and removed the dead AI-review toggle superseded by workflow modes.",
     ],
     zh: [
       "【小米 MiMo 思考上限修复】修复使用小米 MiMo 模型时频繁提示「思考上限」、只输出思考内容不出正文的问题。MiMo 现在被正确识别为 chat_template_kwargs 类型思考模型,自动关闭思考重试可正常生效;同时为所有 OpenAI 兼容思考模型(Qwen3、MiMo、DeepSeek、GLM-5+)增加 max_tokens 输出保护,确保思考和正文都有足够 token 空间,不再因思考耗尽输出配额而空响应",
-      "【写作设置生效修复】批量去 AI 味并发数接到当前章节队列;单章目标字数不再被运行时钳成 2000–6000;修改反馈窗口与对话历史立即落盘;检索 TOPK 同时约束图谱检索;前情分析在标准模式也可生效;移除已被工作流模式架空的「AI 审稿与自动返修」开关",
+      "【写作设置生效修复】批量去 AI 味并发数接到当前章节队列;单章目标字数钳制为 1000–10000(与写作设置 UI 一致);修改反馈窗口与对话历史立即落盘;检索 TOPK 同时约束图谱检索;前情分析在标准模式也可生效;移除已被工作流模式架空的「AI 审稿与自动返修」开关",
     ],
   },
 };

+ 3 - 3
src/lib/novel/deep-chapter-prompts.spec.ts

@@ -35,9 +35,9 @@ describe("resolveChapterLengthSpec", () => {
     expect(resolveChapterLengthSpec(999999).targetChars).toBe(CHAPTER_TARGET_CHARS_MAX)
   })
 
-  it("honors a configured target outside the old 2000–6000 hard clamp", () => {
-    expect(resolveChapterLengthSpec(800).targetChars).toBe(800)
-    expect(resolveChapterLengthSpec(12000).targetChars).toBe(12000)
+  it("honors a configured target inside 1000–10000 that the old 2000–6000 clamp would have rewritten", () => {
+    expect(resolveChapterLengthSpec(1500).targetChars).toBe(1500)
+    expect(resolveChapterLengthSpec(8000).targetChars).toBe(8000)
   })
 })
 

+ 2 - 2
src/lib/novel/deep-chapter-prompts.ts

@@ -6,8 +6,8 @@ export const DEEP_CHAPTER_TARGET_CHARS = 3000
 export const DEEP_CHAPTER_MIN_CHARS = 2200
 export const DEEP_CHAPTER_DRAFT_MAX_CHARS = 3500
 /** 与写作设置 UI 的单章目标字数范围保持一致,避免设置值被运行时悄悄钳死。 */
-export const CHAPTER_TARGET_CHARS_MIN = 500
-export const CHAPTER_TARGET_CHARS_MAX = 20000
+export const CHAPTER_TARGET_CHARS_MIN = 1000
+export const CHAPTER_TARGET_CHARS_MAX = 10_000
 
 /** 章节生成字数规格:由设置中的“单章目标字数”推算(issue #8)。
  *  输出 token 预算由 planChapterRequestBudget 按窗口比例规划,不再挂在此规格上。 */

+ 5 - 1
src/lib/project-store.ts

@@ -18,6 +18,7 @@ import {
   normalizeProviderConfigs,
   normalizeUserLlmConfig,
 } from "@/lib/llm-context-size"
+import { CHAPTER_TARGET_CHARS_MAX, CHAPTER_TARGET_CHARS_MIN } from "@/lib/novel/deep-chapter-prompts"
 
 const RECENT_PROJECTS_KEY = "recentProjects"
 const LAST_PROJECT_KEY = "lastProject"
@@ -793,7 +794,10 @@ function normalizeNovelConfig(
     contextTokenBudget: 0,
     recentSummaryWindow: Math.max(1, Math.min(30, config.recentSummaryWindow ?? DEFAULT_NOVEL_CONFIG.recentSummaryWindow)),
     searchTopK: Math.max(1, Math.min(20, config.searchTopK ?? DEFAULT_NOVEL_CONFIG.searchTopK)),
-    chapterTargetChars: Math.max(500, Math.min(20000, config.chapterTargetChars ?? DEFAULT_NOVEL_CONFIG.chapterTargetChars)),
+    chapterTargetChars: Math.max(
+      CHAPTER_TARGET_CHARS_MIN,
+      Math.min(CHAPTER_TARGET_CHARS_MAX, config.chapterTargetChars ?? DEFAULT_NOVEL_CONFIG.chapterTargetChars),
+    ),
     autoIngestOnSave: config.autoIngestOnSave ?? DEFAULT_NOVEL_CONFIG.autoIngestOnSave,
     autoExtractOnImport: config.autoExtractOnImport ?? DEFAULT_NOVEL_CONFIG.autoExtractOnImport,
     deepPreviousChaptersAnalysis: config.deepPreviousChaptersAnalysis ?? DEFAULT_NOVEL_CONFIG.deepPreviousChaptersAnalysis,

+ 9 - 3
src/lib/writing-settings-effectiveness.spec.ts

@@ -46,6 +46,10 @@ const reviewAdapter = readFileSync(
   resolve(__dirname, "./novel/review-adapter.ts"),
   "utf8",
 )
+const projectStore = readFileSync(
+  resolve(__dirname, "./project-store.ts"),
+  "utf8",
+)
 const chatPanel = readFileSync(
   resolve(__dirname, "../components/chat/chat-panel.tsx"),
   "utf8",
@@ -60,11 +64,13 @@ describe("writing settings still reach runtime", () => {
     expect(novelSection).toContain("updateMaxHistoryMessages")
   })
 
-  it("keeps chapter target chars UI and runtime clamp on the same 500–20000 range", () => {
-    expect(CHAPTER_TARGET_CHARS_MIN).toBe(500)
-    expect(CHAPTER_TARGET_CHARS_MAX).toBe(20000)
+  it("keeps chapter target chars UI and runtime clamp on the same 1000–10000 range", () => {
+    expect(CHAPTER_TARGET_CHARS_MIN).toBe(1000)
+    expect(CHAPTER_TARGET_CHARS_MAX).toBe(10_000)
     expect(novelSection).toContain("CHAPTER_TARGET_CHARS_MIN")
     expect(novelSection).toContain("CHAPTER_TARGET_CHARS_MAX")
+    expect(projectStore).toContain("CHAPTER_TARGET_CHARS_MIN")
+    expect(projectStore).toContain("CHAPTER_TARGET_CHARS_MAX")
     expect(novelSection).not.toContain("Math.max(2000, Math.min(6000")
   })