feat: 完善 help 说明及多项功能优化

This commit is contained in:
shiyue
2026-03-18 00:20:12 +08:00
parent b23ac0fb6b
commit 40a3479e2a
9 changed files with 82 additions and 9 deletions

View File

@@ -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{

View File

@@ -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 {

4
go.mod
View File

@@ -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 (

2
go.sum
View File

@@ -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=

44
main.go
View File

@@ -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"),

View File

@@ -58,7 +58,8 @@
要求:
- 前 3 卷必须各自承担不同功能,而不是重复“升级打怪换地图”
- **若任务中包含明确章节数要求(如”【硬约束】用户明确要求写 N 章”),生成大纲时必须严格按照指定总章节数生成,不多不少,章节数为最高优先级约束,卷弧结构需服务于这个总章节数**
- 前 3 卷必须各自承担不同功能,而不是重复”升级打怪换地图”
- 每卷都必须回答:新增了什么、失去了什么、关系如何变化、为何必须进入下一卷
- 每弧都必须有明确目标、阻力、转折和结果
- 每章都必须服务于当前弧目标

View File

@@ -55,6 +55,7 @@
要求:
- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束**
- 至少划分出 3 个阶段:建立、升级、收束
- 每个阶段的主问题要有区别
- 中段必须出现一次改变后续推进方式的转折

View File

@@ -53,6 +53,7 @@
要求:
- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束**
- 每章都必须推动主冲突
- 不允许“中期再慢慢展开”的拖延式设计
- 配角数量控制在必要范围

View File

@@ -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` 用户明确表达了篇幅或连载预期(包括明确章节数),同样优先遵从用户选择,按上述章节数规则映射
调用对应规划师完成基础设定: