feat: 完善 help 说明及多项功能优化
This commit is contained in:
22
app/run.go
22
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{
|
||||
|
||||
@@ -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
4
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 (
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
44
main.go
44
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"),
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
|
||||
要求:
|
||||
|
||||
- 前 3 卷必须各自承担不同功能,而不是重复“升级打怪换地图”
|
||||
- **若任务中包含明确章节数要求(如”【硬约束】用户明确要求写 N 章”),生成大纲时必须严格按照指定总章节数生成,不多不少,章节数为最高优先级约束,卷弧结构需服务于这个总章节数**
|
||||
- 前 3 卷必须各自承担不同功能,而不是重复”升级打怪换地图”
|
||||
- 每卷都必须回答:新增了什么、失去了什么、关系如何变化、为何必须进入下一卷
|
||||
- 每弧都必须有明确目标、阻力、转折和结果
|
||||
- 每章都必须服务于当前弧目标
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
|
||||
要求:
|
||||
|
||||
- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束**
|
||||
- 至少划分出 3 个阶段:建立、升级、收束
|
||||
- 每个阶段的主问题要有区别
|
||||
- 中段必须出现一次改变后续推进方式的转折
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
|
||||
要求:
|
||||
|
||||
- **若任务中包含明确章节数要求(如"【硬约束】用户明确要求写 N 章"),生成大纲时必须严格按照指定章节数生成,不多不少,章节数为最高优先级约束**
|
||||
- 每章都必须推动主冲突
|
||||
- 不允许“中期再慢慢展开”的拖延式设计
|
||||
- 配角数量控制在必要范围
|
||||
|
||||
@@ -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` 用户明确表达了篇幅或连载预期(包括明确章节数),同样优先遵从用户选择,按上述章节数规则映射。
|
||||
|
||||
调用对应规划师完成基础设定:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user