This commit is contained in:
voocel
2026-03-07 21:25:55 +08:00
commit 27bd85ef90
60 changed files with 5658 additions and 0 deletions

168
tools/commit_chapter.go Normal file
View File

@@ -0,0 +1,168 @@
package tools
import (
"context"
"encoding/json"
"fmt"
"github.com/voocel/agentcore/schema"
"github.com/voocel/ainovel-cli/domain"
"github.com/voocel/ainovel-cli/state"
)
// CommitChapterTool 提交章节:加载正文 → 保存终稿 → 生成摘要 → 更新状态 → 更新进度。
// 这是唯一允许写入 chapters/、summaries/、更新状态文件和进度的工具。
type CommitChapterTool struct {
store *state.Store
}
func NewCommitChapterTool(store *state.Store) *CommitChapterTool {
return &CommitChapterTool{store: store}
}
func (t *CommitChapterTool) Name() string { return "commit_chapter" }
func (t *CommitChapterTool) Description() string {
return "提交章节。优先使用打磨版正文,同时更新时间线、伏笔、关系状态。返回结构化信号"
}
func (t *CommitChapterTool) Label() string { return "提交章节" }
func (t *CommitChapterTool) Schema() map[string]any {
timelineSchema := schema.Object(
schema.Property("time", schema.String("故事内时间")).Required(),
schema.Property("event", schema.String("事件描述")).Required(),
schema.Property("characters", schema.Array("涉及角色", schema.String(""))),
)
foreshadowSchema := schema.Object(
schema.Property("id", schema.String("伏笔 ID新埋设时自定义推进/回收时使用已有 ID")).Required(),
schema.Property("action", schema.Enum("操作", "plant", "advance", "resolve")).Required(),
schema.Property("description", schema.String("伏笔描述(仅 plant 时必需)")),
)
relationshipSchema := schema.Object(
schema.Property("character_a", schema.String("角色 A")).Required(),
schema.Property("character_b", schema.String("角色 B")).Required(),
schema.Property("relation", schema.String("当前关系描述")).Required(),
)
return schema.Object(
schema.Property("chapter", schema.Int("章节号")).Required(),
schema.Property("summary", schema.String("本章内容摘要200字以内")).Required(),
schema.Property("characters", schema.Array("本章出场角色名", schema.String(""))).Required(),
schema.Property("key_events", schema.Array("本章关键事件", schema.String(""))).Required(),
schema.Property("timeline_events", schema.Array("本章时间线事件", timelineSchema)),
schema.Property("foreshadow_updates", schema.Array("伏笔操作", foreshadowSchema)),
schema.Property("relationship_changes", schema.Array("关系变化", relationshipSchema)),
)
}
func (t *CommitChapterTool) Execute(_ context.Context, args json.RawMessage) (json.RawMessage, error) {
var a struct {
Chapter int `json:"chapter"`
Summary string `json:"summary"`
Characters []string `json:"characters"`
KeyEvents []string `json:"key_events"`
TimelineEvents []domain.TimelineEvent `json:"timeline_events"`
ForeshadowUpdates []domain.ForeshadowUpdate `json:"foreshadow_updates"`
RelationshipChanges []domain.RelationshipEntry `json:"relationship_changes"`
}
if err := json.Unmarshal(args, &a); err != nil {
return nil, fmt.Errorf("invalid args: %w", err)
}
if a.Chapter <= 0 {
return nil, fmt.Errorf("chapter must be > 0")
}
if err := t.store.ValidateChapterCommit(a.Chapter); err != nil {
return nil, err
}
// 1. 加载章节正文polished 优先,否则 merge scenes
content, wordCount, err := t.store.LoadChapterContent(a.Chapter)
if err != nil {
return nil, fmt.Errorf("load chapter content: %w", err)
}
if content == "" {
return nil, fmt.Errorf("no content found for chapter %d", a.Chapter)
}
// 2. 保存终稿
if err := t.store.SaveFinalChapter(a.Chapter, content); err != nil {
return nil, fmt.Errorf("save final chapter: %w", err)
}
// 3. 保存摘要
summary := domain.ChapterSummary{
Chapter: a.Chapter,
Summary: a.Summary,
Characters: a.Characters,
KeyEvents: a.KeyEvents,
}
if err := t.store.SaveSummary(summary); err != nil {
return nil, fmt.Errorf("save summary: %w", err)
}
// 4. 更新状态增量
if len(a.TimelineEvents) > 0 {
for i := range a.TimelineEvents {
a.TimelineEvents[i].Chapter = a.Chapter
}
if err := t.store.AppendTimelineEvents(a.TimelineEvents); err != nil {
return nil, fmt.Errorf("append timeline: %w", err)
}
}
if len(a.ForeshadowUpdates) > 0 {
if err := t.store.UpdateForeshadow(a.Chapter, a.ForeshadowUpdates); err != nil {
return nil, fmt.Errorf("update foreshadow: %w", err)
}
}
if len(a.RelationshipChanges) > 0 {
for i := range a.RelationshipChanges {
a.RelationshipChanges[i].Chapter = a.Chapter
}
if err := t.store.UpdateRelationships(a.RelationshipChanges); err != nil {
return nil, fmt.Errorf("update relationships: %w", err)
}
}
// 5. 更新进度
if err := t.store.MarkChapterComplete(a.Chapter, wordCount); err != nil {
return nil, fmt.Errorf("mark chapter complete: %w", err)
}
// 6. 判断是否需要审阅
progress, err := t.store.LoadProgress()
if err != nil {
return nil, fmt.Errorf("load progress: %w", err)
}
completedCount := 0
if progress != nil {
completedCount = len(progress.CompletedChapters)
}
reviewRequired, reviewReason := domain.ShouldReview(completedCount)
// 7. 计算场景数
sceneCount := 0
if scenes, err := t.store.LoadSceneDrafts(a.Chapter); err == nil {
sceneCount = len(scenes)
}
// 8. 构造结构化信号
result := domain.CommitResult{
Chapter: a.Chapter,
Committed: true,
WordCount: wordCount,
SceneCount: sceneCount,
NextChapter: a.Chapter + 1,
ReviewRequired: reviewRequired,
ReviewReason: reviewReason,
}
// 9. 写入信号文件供宿主程序读取(优先于清理操作,确保信号不丢失)
if err := t.store.SaveLastCommit(result); err != nil {
return nil, fmt.Errorf("save commit signal: %w", err)
}
// 10. 清除场景级进度(章节已提交)
if err := t.store.ClearInProgress(); err != nil {
return nil, fmt.Errorf("clear in-progress: %w", err)
}
return json.Marshal(result)
}