From 40a3479e2a5ef78689d0b8a407afd74f0ef3c92f Mon Sep 17 00:00:00 2001 From: shiyue Date: Wed, 18 Mar 2026 00:20:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=20help=20=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E5=8F=8A=E5=A4=9A=E9=A1=B9=E5=8A=9F=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.go | 22 +++++++++++++++++-- app/runtime.go | 2 +- go.mod | 4 ++-- go.sum | 2 ++ main.go | 44 ++++++++++++++++++++++++++++++++++++-- prompts/architect-long.md | 3 ++- prompts/architect-mid.md | 1 + prompts/architect-short.md | 1 + prompts/coordinator.md | 12 ++++++++++- 9 files changed, 82 insertions(+), 9 deletions(-) diff --git a/app/run.go b/app/run.go index 24537d3..02b930c 100644 --- a/app/run.go +++ b/app/run.go @@ -93,7 +93,7 @@ func Run(cfg Config, refs tools.References, prompts Prompts, styles map[string]s } log.Printf("新建模式:%s", cfg.NovelName) promptText := fmt.Sprintf( - "请创作一部小说,章节数量由你根据故事需要自行决定。若题材与冲突天然适合长篇连载,请优先规划为分层长篇结构,而不是压缩成短篇式梗概。要求如下:\n\n%s", + "请创作一部小说。若需求中有明确章节数,请严格遵守;若无明确章节数且题材适合长篇连载,请优先规划为分层长篇结构。要求如下:\n\n%s", cfg.Prompt, ) if err := coordinator.Prompt(promptText); err != nil { @@ -302,7 +302,7 @@ type recoveryResult struct { // determineRecovery 根据 Progress 和 RunMeta 判断恢复类型和 Prompt 文本。 // 章节总数完全来自 Progress.TotalChapters(由大纲自动设定),不再由外部传入。 func determineRecovery(progress *domain.Progress, runMeta *domain.RunMeta) recoveryResult { - if progress == nil { + if progress == nil || progress.Phase == domain.PhaseInit { return recoveryResult{IsNew: true} } guidance := planningTierGuidance(runMeta) @@ -313,6 +313,24 @@ func determineRecovery(progress *domain.Progress, runMeta *domain.RunMeta) recov return prompt + "\n" + guidance } + // 规划阶段恢复:premise 已保存但 outline/characters 尚未完成 + if progress.Phase == domain.PhasePremise { + return recoveryResult{ + PromptText: withGuidance("前提设定已保存,但大纲/角色设定尚未完成。请调用 novel_context 查看已有设定,然后继续完成 outline、characters、world_rules 的规划,完成后开始逐章写作。"), + Label: "规划恢复:继续生成大纲/角色设定", + } + } + + // 规划阶段恢复:outline 已保存但写作尚未开始 + if progress.Phase == domain.PhaseOutline { + return recoveryResult{ + PromptText: withGuidance(fmt.Sprintf( + "大纲规划已完成,尚未开始写作。请从第 1 章开始逐章写作,总共需要写 %d 章。", + progress.TotalChapters)), + Label: fmt.Sprintf("写作恢复:大纲就绪,从第1章开始(共 %d 章)", progress.TotalChapters), + } + } + if progress.InProgressChapter > 0 { ch := progress.InProgressChapter return recoveryResult{ diff --git a/app/runtime.go b/app/runtime.go index 5960359..fd264e0 100644 --- a/app/runtime.go +++ b/app/runtime.go @@ -198,7 +198,7 @@ func (rt *Runtime) Start(prompt string) error { } promptText := fmt.Sprintf( - "请创作一部小说,章节数量由你根据故事需要自行决定。若题材与冲突天然适合长篇连载,请优先规划为分层长篇结构,而不是压缩成短篇式梗概。要求如下:\n\n%s", + "请创作一部小说。若需求中有明确章节数,请严格遵守;若无明确章节数且题材适合长篇连载,请优先规划为分层长篇结构。要求如下:\n\n%s", prompt, ) if err := rt.coordinator.Prompt(promptText); err != nil { diff --git a/go.mod b/go.mod index e50a0e4..79c2a60 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/voocel/ainovel-cli -go 1.25.5 +go 1.25 require ( github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 github.com/voocel/agentcore v1.5.3 - github.com/voocel/litellm v1.6.2 + github.com/voocel/litellm v1.6.3 ) require ( diff --git a/go.sum b/go.sum index e47eebc..c58e57b 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/voocel/litellm v1.6.2 h1:TJ1s7B7UqgV86O1EcuwQTZua0FK1tbOg0+oUsDmgmuA= github.com/voocel/litellm v1.6.2/go.mod h1:6MBUu3I4DHm7h72Vl+3nqLruSwYmgqMf/I9BGoordJ4= +github.com/voocel/litellm v1.6.3 h1:FKHx+XQbXCZVvjnnMk2kuJ5dyXuXa8j5MVStWL7NaQs= +github.com/voocel/litellm v1.6.3/go.mod h1:6MBUu3I4DHm7h72Vl+3nqLruSwYmgqMf/I9BGoordJ4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= diff --git a/main.go b/main.go index a85adb8..d7cf939 100644 --- a/main.go +++ b/main.go @@ -62,11 +62,11 @@ func buildConfig(style string) app.Config { } cfg := app.Config{ - NovelName: "novel", + NovelName: envOr("NOVEL_NAME", ""), Provider: provider, APIKey: apiKey, BaseURL: baseURL, - ModelName: "stepfun/step-3.5-flash:free", + ModelName: envOr("LLM_MODEL", "stepfun/step-3.5-flash:free"), Style: style, } return cfg @@ -76,9 +76,49 @@ func parsePrompt() string { if len(os.Args) < 2 { return "" } + if os.Args[1] == "-help" || os.Args[1] == "--help" || os.Args[1] == "-h" { + printHelp() + os.Exit(0) + } return strings.Join(os.Args[1:], " ") } +func printHelp() { + fmt.Println(`ainovel-cli - AI 小说生成工具 + +用法: + ainovel-cli [prompt] CLI 模式:直接生成小说 + ainovel-cli TUI 模式:启动交互界面 + +环境变量: + NOVEL_NAME 小说名称(默认:novel) + NOVEL_STYLE 小说风格(默认:default) + 可选值: + default 通用风格,叙事张弛有度,五感描写,对话自然 + fantasy 奇幻冒险,世界观自然展开,魔法体系有代价感 + romance 言情,情感递进有节奏,关系张力与内心描写并重 + suspense 悬疑推理,多线叙事,信息差悬念,线索管理严谨 + LLM_PROVIDER LLM 提供商:openrouter|anthropic|gemini(默认:openrouter) + LLM_MODEL 模型名称(默认:stepfun/step-3.5-flash:free) + + Z_OPENAI_API_KEY API 密钥 + Z_OPENAI_BASE_URL API 地址 + + ANTHROPIC_API_KEY Anthropic API 密钥 + ANTHROPIC_BASE_URL Anthropic API 地址 + + GEMINI_API_KEY Gemini API 密钥 + GEMINI_BASE_URL Gemini API 地址 + + OPENROUTER_API_KEY OpenRouter API 密钥 + OPENROUTER_BASE_URL OpenRouter API 地址 + +示例: + ainovel-cli "写一部科幻小说,主角是时间旅行者" + NOVEL_NAME=时空之旅 ainovel-cli "写一部科幻小说" + ainovel-cli # 启动 TUI 交互模式`) +} + func loadReferences(style string) tools.References { refs := tools.References{ ChapterGuide: mustRead(referencesFS, "references/chapter-guide.md"), diff --git a/prompts/architect-long.md b/prompts/architect-long.md index 1e2f985..8193056 100644 --- a/prompts/architect-long.md +++ b/prompts/architect-long.md @@ -58,7 +58,8 @@ 要求: -- 前 3 卷必须各自承担不同功能,而不是重复“升级打怪换地图” +- **若任务中包含明确章节数要求(如”【硬约束】用户明确要求写 N 章”),生成大纲时必须严格按照指定总章节数生成,不多不少,章节数为最高优先级约束,卷弧结构需服务于这个总章节数** +- 前 3 卷必须各自承担不同功能,而不是重复”升级打怪换地图” - 每卷都必须回答:新增了什么、失去了什么、关系如何变化、为何必须进入下一卷 - 每弧都必须有明确目标、阻力、转折和结果 - 每章都必须服务于当前弧目标 diff --git a/prompts/architect-mid.md b/prompts/architect-mid.md index dcf79d3..31e565a 100644 --- a/prompts/architect-mid.md +++ b/prompts/architect-mid.md @@ -55,6 +55,7 @@ 要求: +- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束** - 至少划分出 3 个阶段:建立、升级、收束 - 每个阶段的主问题要有区别 - 中段必须出现一次改变后续推进方式的转折 diff --git a/prompts/architect-short.md b/prompts/architect-short.md index d6647c2..e3f97f7 100644 --- a/prompts/architect-short.md +++ b/prompts/architect-short.md @@ -53,6 +53,7 @@ 要求: +- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束** - 每章都必须推动主冲突 - 不允许“中期再慢慢展开”的拖延式设计 - 配角数量控制在必要范围 diff --git a/prompts/coordinator.md b/prompts/coordinator.md index ea22861..b1207cd 100644 --- a/prompts/coordinator.md +++ b/prompts/coordinator.md @@ -51,11 +51,21 @@ 选择规则: +**第一优先级:用户明确指定的章节数。** 在判断题材之前,先从用户输入中提取明确的章节数。中文数字和阿拉伯数字均需识别(如"十章"="10章"=10章,"二十章"=20章,"一百章"=100章)。如果提取到了明确章节数 N,则: + +- N ≤ 25 → 使用 `architect_short` +- 26 ≤ N ≤ 60 → 使用 `architect_mid` +- N > 60 → 使用 `architect_long` + +此时章节数为硬约束,必须在传给架构师的 task 中追加:`\n\n【硬约束】用户明确要求写 N 章,请生成恰好 N 章的大纲,不多不少。` + +**第二优先级:题材判断。** 仅当用户没有指定明确章节数时,才按以下规则判断: + - 只要题材明显适合长期展开,优先使用 `architect_long` - 只有当需求明显更像单卷故事时,才使用 `architect_short` - 不确定时,优先 `architect_mid`,但对连载型商业题材宁可偏长,不要误压成短篇 -如果经过 `ask_user` 用户明确表达了篇幅或连载预期,优先遵从用户选择。 +如果经过 `ask_user` 用户明确表达了篇幅或连载预期(包括明确章节数),同样优先遵从用户选择,按上述章节数规则映射。 调用对应规划师完成基础设定: