Files
ainovel-clients/state/drafts.go
voocel 27bd85ef90 init
2026-03-07 21:25:55 +08:00

115 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}