115 lines
3.2 KiB
Go
115 lines
3.2 KiB
Go
package state
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"unicode/utf8"
|
||
|
||
"github.com/voocel/ainovel-cli/domain"
|
||
)
|
||
|
||
// SaveChapterPlan 保存章节规划到 drafts/{ch}.plan.json。
|
||
func (s *Store) SaveChapterPlan(plan domain.ChapterPlan) error {
|
||
return s.writeJSON(fmt.Sprintf("drafts/%02d.plan.json", plan.Chapter), plan)
|
||
}
|
||
|
||
// LoadChapterPlan 读取章节规划。
|
||
func (s *Store) LoadChapterPlan(chapter int) (*domain.ChapterPlan, error) {
|
||
var plan domain.ChapterPlan
|
||
if err := s.readJSON(fmt.Sprintf("drafts/%02d.plan.json", chapter), &plan); err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
return &plan, nil
|
||
}
|
||
|
||
// SaveSceneDraft 保存场景草稿到 drafts/{ch}.scene-{n}.md。
|
||
func (s *Store) SaveSceneDraft(draft domain.SceneDraft) error {
|
||
rel := fmt.Sprintf("drafts/%02d.scene-%d.md", draft.Chapter, draft.Scene)
|
||
return s.writeMarkdown(rel, draft.Content)
|
||
}
|
||
|
||
// LoadSceneDrafts 加载指定章节的所有场景草稿,按场景编号排序。
|
||
func (s *Store) LoadSceneDrafts(chapter int) ([]domain.SceneDraft, error) {
|
||
pattern := filepath.Join(s.dir, "drafts", fmt.Sprintf("%02d.scene-*.md", chapter))
|
||
matches, err := filepath.Glob(pattern)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
sort.Strings(matches)
|
||
|
||
var drafts []domain.SceneDraft
|
||
for _, m := range matches {
|
||
base := filepath.Base(m)
|
||
sceneNum := parseSceneNum(base)
|
||
content, err := os.ReadFile(m)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("read scene draft %s: %w", base, err)
|
||
}
|
||
drafts = append(drafts, domain.SceneDraft{
|
||
Chapter: chapter,
|
||
Scene: sceneNum,
|
||
Content: string(content),
|
||
WordCount: utf8.RuneCountInString(string(content)),
|
||
})
|
||
}
|
||
return drafts, nil
|
||
}
|
||
|
||
// SavePolished 保存打磨后的章节正文到 drafts/{ch}.polished.md。
|
||
func (s *Store) SavePolished(chapter int, content string) error {
|
||
return s.writeMarkdown(fmt.Sprintf("drafts/%02d.polished.md", chapter), content)
|
||
}
|
||
|
||
// LoadPolished 读取打磨后的章节正文。不存在时返回空字符串。
|
||
func (s *Store) LoadPolished(chapter int) (string, error) {
|
||
data, err := s.readFile(fmt.Sprintf("drafts/%02d.polished.md", chapter))
|
||
if os.IsNotExist(err) {
|
||
return "", nil
|
||
}
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return string(data), nil
|
||
}
|
||
|
||
// LoadChapterContent 加载章节正文:优先 polished,否则 merge scenes。
|
||
func (s *Store) LoadChapterContent(chapter int) (string, int, error) {
|
||
polished, err := s.LoadPolished(chapter)
|
||
if err != nil {
|
||
return "", 0, err
|
||
}
|
||
if polished != "" {
|
||
return polished, utf8.RuneCountInString(polished), nil
|
||
}
|
||
drafts, err := s.LoadSceneDrafts(chapter)
|
||
if err != nil {
|
||
return "", 0, err
|
||
}
|
||
content, wc := domain.MergeScenes(drafts)
|
||
return content, wc, nil
|
||
}
|
||
|
||
// SaveFinalChapter 保存最终章节正文到 chapters/{ch}.md。
|
||
func (s *Store) SaveFinalChapter(chapter int, content string) error {
|
||
return s.writeMarkdown(fmt.Sprintf("chapters/%02d.md", chapter), content)
|
||
}
|
||
|
||
// parseSceneNum 从文件名如 "01.scene-2.md" 提取场景编号。
|
||
func parseSceneNum(filename string) int {
|
||
// 格式:{ch}.scene-{n}.md
|
||
parts := strings.Split(filename, "scene-")
|
||
if len(parts) < 2 {
|
||
return 0
|
||
}
|
||
numStr := strings.TrimSuffix(parts[1], ".md")
|
||
n, _ := strconv.Atoi(numStr)
|
||
return n
|
||
}
|