diff --git a/main.go b/main.go index 0792bf9..6a34821 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,12 @@ package main import ( + "bufio" "embed" "fmt" + "io/ioutil" "os" + "strconv" "strings" "github.com/voocel/ainovel-cli/app" @@ -21,6 +24,12 @@ var referencesFS embed.FS var stylesFS embed.FS func main() { + // 处理 start 子命令 + if len(os.Args) > 1 && os.Args[1] == "start" { + handleStartCommand() + return + } + style := envOr("NOVEL_STYLE", "default") refs := loadReferences(style) prompts := loadPrompts() @@ -87,6 +96,7 @@ func printHelp() { fmt.Println(`ainovel-cli - AI 小说生成工具 用法: + ainovel-cli start 交互式开始:选择小说 → 选择风格 → 启动 TUI ainovel-cli [prompt] CLI 模式:直接生成小说 ainovel-cli TUI 模式:启动交互界面 @@ -195,3 +205,142 @@ func envOr(key, fallback string) string { } return fallback } + +// handleStartCommand 处理 start 子命令:交互式选择小说和风格 +func handleStartCommand() { + // Step 1: 选择小说 + novelName := selectNovel() + if novelName == "" { + fmt.Println("已取消") + os.Exit(0) + } + + // Step 2: 选择风格 + style := selectStyle() + if style == "" { + fmt.Println("已取消") + os.Exit(0) + } + + // 设置环境变量并启动 TUI + os.Setenv("NOVEL_NAME", novelName) + os.Setenv("NOVEL_STYLE", style) + + refs := loadReferences(style) + prompts := loadPrompts() + styles := loadStyles() + cfg := buildConfig(style) + cfg.NovelName = novelName + + if err := tui.Run(cfg, refs, prompts, styles); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} + +// selectNovel 显示小说选择菜单 +func selectNovel() string { + options := []string{"📝 创建新小说"} + + // 扫描 output 目录下现有小说 + outputDir := "output" + entries, err := ioutil.ReadDir(outputDir) + novels := []string{} + if err == nil { + for _, e := range entries { + if e.IsDir() && e.Name() != "." && e.Name() != ".." { + novels = append(novels, e.Name()) + options = append(options, "📖 "+e.Name()) + } + } + } + + if len(options) == 1 { + // 只有创建选项,直接输入新名称 + return promptNovelName() + } + + // 显示菜单 + fmt.Println("\n=== 选择小说或创建新的 ===") + for i, opt := range options { + fmt.Printf("%d. %s\n", i+1, opt) + } + + choice := promptChoice(len(options)) + if choice < 0 { + return "" + } + + if choice == 0 { + // 创建新小说 + return promptNovelName() + } + + // 返回现有小说名称 + return novels[choice-1] +} + +// selectStyle 显示风格选择菜单 +func selectStyle() string { + styles := []struct { + name string + desc string + }{ + {"default", "通用风格,叙事张弛有度,五感描写,对话自然"}, + {"fantasy", "奇幻冒险,世界观自然展开,魔法体系有代价感"}, + {"romance", "言情,情感递进有节奏,关系张力与内心描写并重"}, + {"suspense", "悬疑推理,多线叙事,信息差悬念,线索管理严谨"}, + } + + fmt.Println("\n=== 选择小说风格 ===") + for i, s := range styles { + fmt.Printf("%d. %-10s %s\n", i+1, s.name, s.desc) + } + + choice := promptChoice(len(styles)) + if choice < 0 { + // EOF 或错误,使用默认风格 + fmt.Println("使用默认风格...") + return "default" + } + return styles[choice].name +} + +// promptNovelName 提示输入新小说名称 +func promptNovelName() string { + reader := bufio.NewReader(os.Stdin) + fmt.Print("\n请输入小说名称 (默认: novel): ") + name, err := reader.ReadString('\n') + if err != nil { + return "novel" + } + name = strings.TrimSpace(name) + if name == "" { + return "novel" + } + return name +} + +// promptChoice 提示用户选择菜单项 +func promptChoice(maxChoice int) int { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print("\n请选择 (输入数字): ") + input, err := reader.ReadString('\n') + if err != nil { + // EOF 或其他错误,返回 -1 表示取消 + return -1 + } + input = strings.TrimSpace(input) + if input == "" { + continue + } + + choice, err := strconv.Atoi(input) + if err != nil || choice < 1 || choice > maxChoice { + fmt.Printf("❌ 请输入 1 到 %d 之间的数字\n", maxChoice) + continue + } + return choice - 1 // 转换为 0-based 索引 + } +}