diff --git a/app/run.go b/app/run.go index cc8bf8b..cd46d52 100644 --- a/app/run.go +++ b/app/run.go @@ -174,6 +174,12 @@ func registerSubscription(coordinator *agentcore.Agent, store *state.Store, prov if emit != nil { emit(UIEvent{Time: time.Now(), Category: "TOOL", Summary: ev.Tool + ".done", Level: "info"}) } + // 上下文加载可视化:提取 novel_context 的加载摘要 + if ev.Tool == "novel_context" && emit != nil { + if summary := extractLoadingSummary(ev.Result); summary != "" { + emit(UIEvent{Time: time.Now(), Category: "CONTEXT", Summary: summary, Level: "info"}) + } + } if ev.Tool == "subagent" { handleSubAgentDone(coordinator, store, emit) @@ -448,7 +454,15 @@ func handleEditorDone(coordinator *agentcore.Agent, store *state.Store, emit emi log.Printf("[host] 清除审阅信号失败: %v", err) } - log.Printf("[host] 审阅信号:verdict=%s,%d 个问题", review.Verdict, len(review.Issues)) + criticalN := review.CriticalCount() + log.Printf("[host] 审阅信号:verdict=%s,%d 个问题(critical=%d,error=%d)", + review.Verdict, len(review.Issues), criticalN, review.ErrorCount()) + + // 宿主兜底:如果 LLM 给了 accept 但存在 critical 问题,强制升级为 rewrite + if review.Verdict == "accept" && criticalN > 0 { + log.Printf("[host] 检测到 %d 个 critical 问题但 verdict=accept,强制升级为 rewrite", criticalN) + review.Verdict = "rewrite" + } chaptersInfo := "" if len(review.AffectedChapters) > 0 { @@ -555,6 +569,20 @@ func parseProgressSummary(ev agentcore.Event) string { return truncateLog(string(ev.Result), 60) } +// extractLoadingSummary 从 novel_context 的返回 JSON 中提取 _loading_summary 字段。 +func extractLoadingSummary(result json.RawMessage) string { + if len(result) == 0 { + return "" + } + var data struct { + Summary string `json:"_loading_summary"` + } + if err := json.Unmarshal(result, &data); err != nil { + return "" + } + return data.Summary +} + func truncateLog(s string, maxRunes int) string { runes := []rune(s) if len(runes) <= maxRunes { diff --git a/domain/review.go b/domain/review.go index b7bedeb..7718245 100644 --- a/domain/review.go +++ b/domain/review.go @@ -34,18 +34,49 @@ type RelationshipEntry struct { // ConsistencyIssue 一致性问题。 type ConsistencyIssue struct { - Type string `json:"type"` // timeline / foreshadow / relationship / character - Severity string `json:"severity"` // error / warning + Type string `json:"type"` // consistency / character / pacing / continuity / foreshadow / hook + Severity string `json:"severity"` // critical / error / warning Description string `json:"description"` Suggestion string `json:"suggestion,omitempty"` } +// DimensionScore 单维度评审评分。 +type DimensionScore struct { + Dimension string `json:"dimension"` // consistency / character / pacing / continuity / foreshadow / hook + Score int `json:"score"` // 0-100 + Verdict string `json:"verdict"` // pass / warning / fail + Comment string `json:"comment,omitempty"` // 该维度的简要结论 +} + // ReviewEntry Editor 的审阅条目。 type ReviewEntry struct { Chapter int `json:"chapter"` - Scope string `json:"scope"` // chapter / global + Scope string `json:"scope"` // chapter / global / arc Issues []ConsistencyIssue `json:"issues"` - Verdict string `json:"verdict"` // accept / polish / rewrite + Dimensions []DimensionScore `json:"dimensions,omitempty"` // 分维度评分 + Verdict string `json:"verdict"` // accept / polish / rewrite Summary string `json:"summary"` AffectedChapters []int `json:"affected_chapters,omitempty"` // 需要重写/打磨的章节号 } + +// CriticalCount 返回 critical 级别问题数量。 +func (r *ReviewEntry) CriticalCount() int { + n := 0 + for _, issue := range r.Issues { + if issue.Severity == "critical" { + n++ + } + } + return n +} + +// ErrorCount 返回 error 级别问题数量。 +func (r *ReviewEntry) ErrorCount() int { + n := 0 + for _, issue := range r.Issues { + if issue.Severity == "error" { + n++ + } + } + return n +} diff --git a/domain/story.go b/domain/story.go index ef8f0c7..6f6e399 100644 --- a/domain/story.go +++ b/domain/story.go @@ -18,6 +18,7 @@ type OutlineEntry struct { // Character 角色档案。 type Character struct { Name string `json:"name"` + Aliases []string `json:"aliases,omitempty"` // 别名/称号/绰号(如"废物少年"、"炎哥") Role string `json:"role"` Description string `json:"description"` Arc string `json:"arc"` diff --git a/domain/tracking.go b/domain/tracking.go new file mode 100644 index 0000000..104c353 --- /dev/null +++ b/domain/tracking.go @@ -0,0 +1,11 @@ +package domain + +// StateChange 角色/实体状态变化记录。 +type StateChange struct { + Chapter int `json:"chapter"` + Entity string `json:"entity"` // 角色名或实体名 + Field string `json:"field"` // 变化属性:realm/location/status/power/relation 等 + OldValue string `json:"old_value,omitempty"` // 变化前(首次出现可空) + NewValue string `json:"new_value"` // 变化后 + Reason string `json:"reason,omitempty"` // 变化原因 +} diff --git a/prompts/architect.md b/prompts/architect.md index b9c1be0..fe171dc 100644 --- a/prompts/architect.md +++ b/prompts/architect.md @@ -50,6 +50,7 @@ 基于 premise 和 outline 生成角色档案(JSON 格式),每个角色包含: - name: 姓名 +- aliases: 别名/称号/绰号列表(正文中可能使用的其他称呼,如"废物少年"、"炎哥") - role: 角色定位(主角/配角/反派) - description: 外貌与性格描写 - arc: 角色弧线(从A到B的变化) diff --git a/prompts/editor.md b/prompts/editor.md index 5a6e2d2..56915ea 100644 --- a/prompts/editor.md +++ b/prompts/editor.md @@ -2,7 +2,7 @@ ## 你的工具 -- **novel_context**: 获取小说的完整状态(设定、大纲、角色、时间线、伏笔、关系) +- **novel_context**: 获取小说的完整状态(设定、大纲、角色、时间线、伏笔、关系、状态变化) - **save_review**: 保存审阅结果 ## 工作流程 @@ -12,67 +12,84 @@ ### 2. 六维结构化审阅 -逐维度检查,每个维度必须给出结论(通过/存在问题)和具体问题列表: +逐维度检查,每个维度必须给出**评分(0-100)**和结论(pass/warning/fail): -#### 维度一:设定一致性 +#### 维度一:设定一致性(consistency) - 事件发生顺序是否与时间线矛盾 - 时间跨度是否自洽 - 世界规则边界是否被违反 - 角色属性(能力、外貌、身份)是否前后矛盾 +- 如果有 recent_state_changes,检查角色状态描述是否与记录一致 +- 注意角色的别名/称号,同一人的不同称呼不要误判为不同角色 -#### 维度二:人设一致性 +#### 维度二:人设一致性(character) - 角色行为是否符合其性格设定和弧线 - 对话风格是否与角色身份匹配 - 角色动机是否合理连贯 +- 角色成长是否有合理铺垫 -#### 维度三:节奏平衡 +#### 维度三:节奏平衡(pacing) - 是否连续多章同一类型(纯打斗、纯对话、纯描写) - 主线是否持续推进,有无原地踏步 - 情感节奏是否有张有弛 - 如果有 strand_history 数据,检查 quest/fire/constellation 三线分布是否失衡 -#### 维度四:叙事连贯 +#### 维度四:叙事连贯(continuity) - 场景之间过渡是否自然 - 因果逻辑是否通顺 - 信息传递是否一致(角色A不应知道只有角色B知道的事) -#### 维度五:伏笔健康 +#### 维度五:伏笔健康(foreshadow) - 是否有超过 5 章未推进的伏笔(遗忘风险) - 新伏笔是否有回收方向 - 已回收伏笔的解决是否令人满意 -#### 维度六:钩子质量 +#### 维度六:钩子质量(hook) - 章末钩子是否有足够吸引力 - 如果有 hook_history 数据,检查是否连续使用同一类型的钩子 - 钩子是否与主线推进方向一致 ### 3. 输出审阅 + 调用 save_review,给出: -- issues:发现的具体问题列表,每个问题包含: + +- **dimensions**:六个维度的评分(每个维度一条) + - dimension:维度名(consistency/character/pacing/continuity/foreshadow/hook) + - score:0-100 分 + - verdict:pass(≥80)/ warning(60-79)/ fail(<60) + - comment:该维度的简要结论 + +- **issues**:发现的具体问题列表,每个问题包含: - type:问题维度(consistency/character/pacing/continuity/foreshadow/hook) - - severity:error 或 warning + - severity:问题严重程度 - description:具体问题描述 - suggestion:修改建议 -- verdict:审阅结论 - - `accept`:所有维度通过或仅有 warning 级问题,可以继续写 - - `polish`:存在细节问题,建议对特定章节做打磨 - - `rewrite`:存在 error 级结构性问题,建议重写特定章节 -- summary:审阅总结(200字以内),按维度概括 -- affected_chapters:需要重写或打磨的章节号列表(verdict 为 polish/rewrite 时必填) + +- **verdict**:审阅结论(accept/polish/rewrite) +- **summary**:审阅总结(200字以内),按维度概括 +- **affected_chapters**:需要重写或打磨的章节号列表(verdict 为 polish/rewrite 时必填) + +### severity 分级标准 + +| 级别 | 定义 | 示例 | +|------|------|------| +| **critical** | 逻辑硬伤,必须修复 | 角色已死但再次出场;违反世界规则核心边界;时间线严重错乱 | +| **error** | 明显矛盾,应当修复 | 角色行为与人设严重不符;伏笔遗忘超过10章;节奏严重失衡 | +| **warning** | 轻微瑕疵,可后续处理 | 细节不够精确;节奏略显平淡;钩子强度不足 | ### 判定标准 -- 任一维度出现 error 级问题 → verdict 至少为 polish -- 多个维度出现 error 级问题 → verdict 应为 rewrite -- 只有 warning 级问题 → verdict 为 accept -- 没有发现问题 → verdict 为 accept +- 存在任何 critical 问题 → verdict 必须为 rewrite +- 无 critical 但存在 error → verdict 至少为 polish +- 只有 warning 或无问题 → verdict 为 accept ## 注意事项 - 不要自己修改正文 - 不要输出空洞的表扬,只关注问题 -- severity=error 的问题必须修复,severity=warning 的可以后续处理 -- 如果没有发现问题,verdict 应为 accept +- critical 问题绝不放过,这是底线 +- warning 级问题如果是有意为之的过渡铺垫,可以不报 +- 如果没有发现问题,verdict 应为 accept,所有维度 score ≥ 80 ## 弧级评审模式(长篇) diff --git a/prompts/writer.md b/prompts/writer.md index 8b143c0..0fdc12f 100644 --- a/prompts/writer.md +++ b/prompts/writer.md @@ -49,11 +49,12 @@ ### 6. 提交章节 调用 commit_chapter,提供: - summary: 本章内容摘要(200字以内) -- characters: 本章出场角色名列表 +- characters: 本章出场角色名列表(使用正式名,不用别名) - key_events: 本章关键事件列表 - timeline_events: 本章发生的时间线事件 - foreshadow_updates: 伏笔操作(plant 埋设 / advance 推进 / resolve 回收) - relationship_changes: 人物关系变化 +- state_changes: 角色/实体状态变化(修为提升、位置转移、状态变化等),每条包含 entity/field/old_value/new_value/reason ## 重写模式 @@ -79,3 +80,6 @@ - 保持与前几章的连贯性 - 字数不够时用具体细节扩展,不用水话填充 - 注意时间线连贯和伏笔管理 +- 角色在正文中可以使用别名/称号/绰号,但 commit 时 characters 列表使用正式名 +- 如果上下文中有 recent_state_changes,注意本章对角色状态的描述必须与记录一致(如修为、位置、伤势等) +- 本章中角色发生任何状态变化(修为提升、位置转移、受伤/恢复、获得/失去物品等),必须在 commit 的 state_changes 中上报 diff --git a/references/character-template.md b/references/character-template.md index 62ac37c..42ade51 100644 --- a/references/character-template.md +++ b/references/character-template.md @@ -3,6 +3,7 @@ ## 主角 ### [角色一姓名] +- **别名/称号**:(如"废物少年"、"炎哥"、"不灭战神"等,正文中可能用到的各种称呼) - **年龄/职业**: - **外貌特征**: - **性格核心**: diff --git a/state/state_changes.go b/state/state_changes.go new file mode 100644 index 0000000..353dee9 --- /dev/null +++ b/state/state_changes.go @@ -0,0 +1,42 @@ +package state + +import ( + "os" + + "github.com/voocel/ainovel-cli/domain" +) + +// AppendStateChanges 追加角色状态变化到 meta/state_changes.json。 +func (s *Store) AppendStateChanges(changes []domain.StateChange) error { + existing, _ := s.LoadStateChanges() + existing = append(existing, changes...) + return s.writeJSON("meta/state_changes.json", existing) +} + +// LoadStateChanges 读取全部状态变化记录。 +func (s *Store) LoadStateChanges() ([]domain.StateChange, error) { + var changes []domain.StateChange + if err := s.readJSON("meta/state_changes.json", &changes); err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + return changes, nil +} + +// LoadRecentStateChanges 加载指定章节之前最近 count 章的状态变化。 +func (s *Store) LoadRecentStateChanges(currentChapter, count int) ([]domain.StateChange, error) { + all, err := s.LoadStateChanges() + if err != nil { + return nil, err + } + start := max(currentChapter-count, 1) + var result []domain.StateChange + for _, c := range all { + if c.Chapter >= start && c.Chapter < currentChapter { + result = append(result, c) + } + } + return result, nil +} diff --git a/tools/check_consistency.go b/tools/check_consistency.go index 6425c42..c365f21 100644 --- a/tools/check_consistency.go +++ b/tools/check_consistency.go @@ -71,6 +71,20 @@ func (t *CheckConsistencyTool) Execute(_ context.Context, args json.RawMessage) } if chars, _ := t.store.LoadCharacters(); len(chars) > 0 { result["characters"] = chars + // 构建别名映射表,供 LLM 识别角色的不同称呼 + aliasMap := make(map[string]string) + for _, c := range chars { + for _, alias := range c.Aliases { + aliasMap[alias] = c.Name + } + } + if len(aliasMap) > 0 { + result["alias_map"] = aliasMap + } + } + // 加载最近状态变化,供对照当前章节的状态描述 + if changes, _ := t.store.LoadRecentStateChanges(a.Chapter, 5); len(changes) > 0 { + result["recent_state_changes"] = changes } if rules, _ := t.store.LoadWorldRules(); len(rules) > 0 { @@ -95,19 +109,26 @@ func (t *CheckConsistencyTool) Execute(_ context.Context, args json.RawMessage) result["instruction"] = `请逐项对照以上状态数据检查本章内容,返回 JSON 数组格式的冲突项: [ { - "type": "timeline|foreshadow|relationship|character|world_rules", - "severity": "error|warning", + "type": "timeline|foreshadow|relationship|character|world_rules|state", + "severity": "critical|error|warning", "description": "具体冲突描述", "suggestion": "建议修正范围和方式" } ] +severity 分级: +- critical:严重逻辑硬伤,必须修复(如角色已死但再次出场、违反世界规则核心边界) +- error:明显矛盾,应当修复(如时间线冲突、角色行为与人设严重不符) +- warning:轻微瑕疵,可后续处理(如细节不够精确、可改进但不影响阅读) + 检查清单: 1. 时间线:本章事件时间是否与已有 timeline 矛盾 2. 伏笔:unresolved_foreshadow 中是否有本章应推进但遗漏的 3. 人物关系:角色互动是否与 relationships 当前状态矛盾 4. 角色一致性:行为是否符合 characters 中的性格和弧线 5. 世界规则:逐条检查 world_rules_boundaries 中的边界约束,本章内容是否违反任何一条 +6. 别名一致性:如果有 alias_map,检查同一角色的不同称呼是否指向正确的人 +7. 状态连续性:如果有 recent_state_changes,检查本章对角色状态的描述是否与最近的状态变化记录一致 如果没有发现冲突,返回空数组 []。不要返回其他格式。` diff --git a/tools/commit_chapter.go b/tools/commit_chapter.go index e8dfb3a..13ca305 100644 --- a/tools/commit_chapter.go +++ b/tools/commit_chapter.go @@ -43,6 +43,13 @@ func (t *CommitChapterTool) Schema() map[string]any { schema.Property("character_b", schema.String("角色 B")).Required(), schema.Property("relation", schema.String("当前关系描述")).Required(), ) + stateChangeSchema := schema.Object( + schema.Property("entity", schema.String("角色名或实体名")).Required(), + schema.Property("field", schema.String("变化属性:realm/location/status/power/relation 等")).Required(), + schema.Property("old_value", schema.String("变化前的值(首次出现可空)")), + schema.Property("new_value", schema.String("变化后的值")).Required(), + schema.Property("reason", schema.String("变化原因")), + ) return schema.Object( schema.Property("chapter", schema.Int("章节号")).Required(), schema.Property("summary", schema.String("本章内容摘要(200字以内)")).Required(), @@ -51,6 +58,7 @@ func (t *CommitChapterTool) Schema() map[string]any { schema.Property("timeline_events", schema.Array("本章时间线事件", timelineSchema)), schema.Property("foreshadow_updates", schema.Array("伏笔操作", foreshadowSchema)), schema.Property("relationship_changes", schema.Array("关系变化", relationshipSchema)), + schema.Property("state_changes", schema.Array("角色/实体状态变化(修为提升、位置转移、状态变化等)", stateChangeSchema)), schema.Property("hook_type", schema.Enum("章末钩子类型", "crisis", "mystery", "desire", "emotion", "choice")), schema.Property("dominant_strand", schema.Enum("本章主导叙事线", "quest", "fire", "constellation")), ) @@ -65,6 +73,7 @@ func (t *CommitChapterTool) Execute(_ context.Context, args json.RawMessage) (js TimelineEvents []domain.TimelineEvent `json:"timeline_events"` ForeshadowUpdates []domain.ForeshadowUpdate `json:"foreshadow_updates"` RelationshipChanges []domain.RelationshipEntry `json:"relationship_changes"` + StateChanges []domain.StateChange `json:"state_changes"` HookType string `json:"hook_type"` DominantStrand string `json:"dominant_strand"` } @@ -125,6 +134,14 @@ func (t *CommitChapterTool) Execute(_ context.Context, args json.RawMessage) (js return nil, fmt.Errorf("update relationships: %w", err) } } + if len(a.StateChanges) > 0 { + for i := range a.StateChanges { + a.StateChanges[i].Chapter = a.Chapter + } + if err := t.store.AppendStateChanges(a.StateChanges); err != nil { + return nil, fmt.Errorf("append state changes: %w", err) + } + } // 5. 更新进度 if err := t.store.MarkChapterComplete(a.Chapter, wordCount, a.HookType, a.DominantStrand); err != nil { diff --git a/tools/novel_context.go b/tools/novel_context.go index f42fa7f..85b2bda 100644 --- a/tools/novel_context.go +++ b/tools/novel_context.go @@ -133,6 +133,10 @@ func (t *ContextTool) Execute(_ context.Context, args json.RawMessage) (json.Raw if relationships, err := t.store.LoadRelationships(); err == nil && len(relationships) > 0 { result["relationship_state"] = relationships } + // 状态变化:最近 5 章的角色/实体状态变化 + if changes, err := t.store.LoadRecentStateChanges(a.Chapter, 5); err == nil && len(changes) > 0 { + result["recent_state_changes"] = changes + } // Layered 模式:注入当前卷弧位置 + 弧目标/卷主题 if profile.Layered && progress != nil { @@ -196,9 +200,113 @@ func (t *ContextTool) Execute(_ context.Context, args json.RawMessage) (json.Raw result["references"] = t.architectReferences() } + result["_loading_summary"] = buildLoadingSummary(result, a.Chapter) return json.Marshal(result) } +// buildLoadingSummary 从已组装的 result 中统计各项数据量,生成一行可读摘要。 +func buildLoadingSummary(result map[string]any, chapter int) string { + var parts []string + + if chapter > 0 { + parts = append(parts, fmt.Sprintf("ch=%d", chapter)) + } else { + parts = append(parts, "architect") + } + + // 卷弧位置 + if pos, ok := result["position"].(map[string]any); ok { + parts = append(parts, fmt.Sprintf("V%dA%d", pos["volume"], pos["arc"])) + } + + var items []string + countSlice := func(key string) int { + if v, ok := result[key]; ok { + if s, ok := v.([]domain.Character); ok { + return len(s) + } + // 通用 slice 反射 + return sliceLen(v) + } + return 0 + } + + // 角色 + if n := countSlice("character_snapshots"); n > 0 { + items = append(items, fmt.Sprintf("角色:%d(快照)", n)) + } else if n := countSlice("characters"); n > 0 { + items = append(items, fmt.Sprintf("角色:%d", n)) + } + + // 分层摘要 + if n := countSlice("volume_summaries"); n > 0 { + items = append(items, fmt.Sprintf("卷摘要:%d", n)) + } + if n := countSlice("arc_summaries"); n > 0 { + items = append(items, fmt.Sprintf("弧摘要:%d", n)) + } + if n := countSlice("recent_summaries"); n > 0 { + items = append(items, fmt.Sprintf("章摘要:%d", n)) + } + + // 分层大纲 + if n := countSlice("layered_outline"); n > 0 { + items = append(items, fmt.Sprintf("分层大纲:%d卷", n)) + } + + // 状态数据 + if n := countSlice("timeline"); n > 0 { + items = append(items, fmt.Sprintf("时间线:%d", n)) + } + if n := countSlice("foreshadow_ledger"); n > 0 { + items = append(items, fmt.Sprintf("伏笔:%d", n)) + } + if n := countSlice("relationship_state"); n > 0 { + items = append(items, fmt.Sprintf("关系:%d", n)) + } + if n := countSlice("recent_state_changes"); n > 0 { + items = append(items, fmt.Sprintf("状态变化:%d", n)) + } + + // 参考资料 + if refs, ok := result["references"].(map[string]string); ok && len(refs) > 0 { + items = append(items, fmt.Sprintf("参考:%d项", len(refs))) + } + + if len(items) > 0 { + parts = append(parts, strings.Join(items, " ")) + } + return strings.Join(parts, " | ") +} + +// sliceLen 对 any 类型尝试取 slice 长度。 +func sliceLen(v any) int { + switch s := v.(type) { + case []domain.ChapterSummary: + return len(s) + case []domain.ArcSummary: + return len(s) + case []domain.VolumeSummary: + return len(s) + case []domain.CharacterSnapshot: + return len(s) + case []domain.TimelineEvent: + return len(s) + case []domain.ForeshadowEntry: + return len(s) + case []domain.RelationshipEntry: + return len(s) + case []domain.StateChange: + return len(s) + case []domain.VolumeOutline: + return len(s) + case []domain.Character: + return len(s) + default: + return 0 + } +} + // loadFilteredCharacters 按 Tier 和场景出场过滤角色。 // core/important 始终返回;secondary/decorative 只在当前章节大纲提及时返回。 func (t *ContextTool) loadFilteredCharacters(result map[string]any, chapter int) { @@ -219,7 +327,7 @@ func (t *ContextTool) loadFilteredCharacters(result map[string]any, chapter int) for _, c := range chars { switch c.Tier { case "secondary", "decorative": - if strings.Contains(sceneText, c.Name) { + if matchCharacter(sceneText, c) { filtered = append(filtered, c) } default: // core, important, 或未设置 @@ -229,6 +337,19 @@ func (t *ContextTool) loadFilteredCharacters(result map[string]any, chapter int) result["characters"] = filtered } +// matchCharacter 检查场景文本中是否包含角色的正式名或任一别名。 +func matchCharacter(text string, c domain.Character) bool { + if strings.Contains(text, c.Name) { + return true + } + for _, alias := range c.Aliases { + if strings.Contains(text, alias) { + return true + } + } + return false +} + // loadLayeredSummaries 分层摘要加载:卷摘要 + 当前卷弧摘要 + 弧内章摘要。 func (t *ContextTool) loadLayeredSummaries(result map[string]any, chapter, summaryWindow int) { vol, arc, err := t.store.LocateChapter(chapter) diff --git a/tools/save_review.go b/tools/save_review.go index 8e9bc8c..8d1f02f 100644 --- a/tools/save_review.go +++ b/tools/save_review.go @@ -27,14 +27,21 @@ func (t *SaveReviewTool) Label() string { return "保存审阅" } func (t *SaveReviewTool) Schema() map[string]any { issueSchema := schema.Object( - schema.Property("type", schema.Enum("问题类型", "timeline", "foreshadow", "relationship", "character", "pacing", "logic")).Required(), - schema.Property("severity", schema.Enum("严重程度", "error", "warning")).Required(), + schema.Property("type", schema.Enum("问题维度", "consistency", "character", "pacing", "continuity", "foreshadow", "hook")).Required(), + schema.Property("severity", schema.Enum("严重程度", "critical", "error", "warning")).Required(), schema.Property("description", schema.String("问题描述")).Required(), schema.Property("suggestion", schema.String("修改建议")), ) + dimensionSchema := schema.Object( + schema.Property("dimension", schema.Enum("维度", "consistency", "character", "pacing", "continuity", "foreshadow", "hook")).Required(), + schema.Property("score", schema.Int("评分(0-100)")).Required(), + schema.Property("verdict", schema.Enum("维度结论", "pass", "warning", "fail")).Required(), + schema.Property("comment", schema.String("该维度的简要结论")), + ) return schema.Object( schema.Property("chapter", schema.Int("审阅的章节号(全局审阅填最新章节号)")).Required(), - schema.Property("scope", schema.Enum("审阅范围", "chapter", "global")).Required(), + schema.Property("scope", schema.Enum("审阅范围", "chapter", "global", "arc")).Required(), + schema.Property("dimensions", schema.Array("分维度评分(六个维度各一条)", dimensionSchema)).Required(), schema.Property("issues", schema.Array("发现的问题", issueSchema)).Required(), schema.Property("verdict", schema.Enum("审阅结论", "accept", "polish", "rewrite")).Required(), schema.Property("summary", schema.String("审阅总结")).Required(), diff --git a/tui/theme.go b/tui/theme.go index fd63425..d5c6cae 100644 --- a/tui/theme.go +++ b/tui/theme.go @@ -10,6 +10,7 @@ var ( colorSuccess = lipgloss.Color("#2ecc71") // 冷绿 colorError = lipgloss.Color("#e74c3c") // 朱红 colorReview = lipgloss.Color("#e67e22") // 橙色 + colorContext = lipgloss.Color("#9b59b6") // 紫色 ) // 状态标签颜色映射 @@ -24,12 +25,13 @@ var statusColors = map[string]lipgloss.Color{ // 事件分类颜色映射 var categoryColors = map[string]lipgloss.Color{ - "TOOL": colorText, - "SYSTEM": colorAccent, - "REVIEW": colorReview, - "CHECK": colorSuccess, - "ERROR": colorError, - "AGENT": colorDim, + "TOOL": colorText, + "SYSTEM": colorAccent, + "REVIEW": colorReview, + "CHECK": colorSuccess, + "ERROR": colorError, + "AGENT": colorDim, + "CONTEXT": colorContext, } // 基础样式