init
This commit is contained in:
124
main.go
Normal file
124
main.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/voocel/ainovel-cli/app"
|
||||
"github.com/voocel/ainovel-cli/tools"
|
||||
)
|
||||
|
||||
//go:embed prompts/*.md
|
||||
var promptsFS embed.FS
|
||||
|
||||
//go:embed references
|
||||
var referencesFS embed.FS
|
||||
|
||||
//go:embed styles/*.md
|
||||
var stylesFS embed.FS
|
||||
|
||||
func main() {
|
||||
prompt := parsePrompt()
|
||||
if prompt == "" {
|
||||
fmt.Fprintf(os.Stderr, "用法: novel <小说需求描述>\n")
|
||||
fmt.Fprintf(os.Stderr, "示例: novel \"写一部3章的都市悬疑短篇,讲述一个程序员在深夜收到神秘代码后卷入一场阴谋\"\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
style := envOr("NOVEL_STYLE", "default")
|
||||
refs := loadReferences(style)
|
||||
prompts := loadPrompts()
|
||||
styles := loadStyles()
|
||||
|
||||
cfg := app.Config{
|
||||
Prompt: prompt,
|
||||
NovelName: "novel",
|
||||
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||
BaseURL: os.Getenv("OPENAI_BASE_URL"),
|
||||
ModelName: envOr("MODEL_NAME", ""),
|
||||
Style: style,
|
||||
}
|
||||
if v := os.Getenv("MAX_CHAPTERS"); v != "" {
|
||||
fmt.Sscanf(v, "%d", &cfg.MaxChapters)
|
||||
}
|
||||
|
||||
if err := app.Run(cfg, refs, prompts, styles); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func parsePrompt() string {
|
||||
if len(os.Args) < 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(os.Args[1:], " ")
|
||||
}
|
||||
|
||||
func loadReferences(style string) tools.References {
|
||||
refs := tools.References{
|
||||
ChapterGuide: mustRead(referencesFS, "references/chapter-guide.md"),
|
||||
HookTechniques: mustRead(referencesFS, "references/hook-techniques.md"),
|
||||
QualityChecklist: mustRead(referencesFS, "references/quality-checklist.md"),
|
||||
OutlineTemplate: mustRead(referencesFS, "references/outline-template.md"),
|
||||
CharacterTemplate: mustRead(referencesFS, "references/character-template.md"),
|
||||
ChapterTemplate: mustRead(referencesFS, "references/chapter-template.md"),
|
||||
Consistency: mustRead(referencesFS, "references/consistency.md"),
|
||||
ContentExpansion: mustRead(referencesFS, "references/content-expansion.md"),
|
||||
DialogueWriting: mustRead(referencesFS, "references/dialogue-writing.md"),
|
||||
}
|
||||
// 加载风格补充参考(可选)
|
||||
if style != "" && style != "default" {
|
||||
path := "references/" + style + "/style-references.md"
|
||||
if data, err := referencesFS.ReadFile(path); err == nil {
|
||||
refs.StyleReference = string(data)
|
||||
}
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func loadPrompts() app.Prompts {
|
||||
return app.Prompts{
|
||||
Coordinator: mustRead(promptsFS, "prompts/coordinator.md"),
|
||||
Architect: mustRead(promptsFS, "prompts/architect.md"),
|
||||
Writer: mustRead(promptsFS, "prompts/writer.md"),
|
||||
Editor: mustRead(promptsFS, "prompts/editor.md"),
|
||||
}
|
||||
}
|
||||
|
||||
func mustRead(fs embed.FS, path string) string {
|
||||
data, err := fs.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("embed read %s: %v", path, err))
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func loadStyles() map[string]string {
|
||||
styles := make(map[string]string)
|
||||
entries, err := stylesFS.ReadDir("styles")
|
||||
if err != nil {
|
||||
return styles
|
||||
}
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(e.Name(), ".md")
|
||||
data, err := stylesFS.ReadFile("styles/" + e.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
styles[name] = string(data)
|
||||
}
|
||||
return styles
|
||||
}
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
Reference in New Issue
Block a user