feat: 支持六维评审评分及别名管理

This commit is contained in:
voocel
2026-03-12 22:25:34 +08:00
parent bce0adeff1
commit 16e790a372
14 changed files with 344 additions and 40 deletions

View File

@@ -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=%derror=%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 {

View File

@@ -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
}

View File

@@ -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"`

11
domain/tracking.go Normal file
View File

@@ -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"` // 变化原因
}

View File

@@ -50,6 +50,7 @@
基于 premise 和 outline 生成角色档案JSON 格式),每个角色包含:
- name: 姓名
- aliases: 别名/称号/绰号列表(正文中可能使用的其他称呼,如"废物少年"、"炎哥"
- role: 角色定位(主角/配角/反派)
- description: 外貌与性格描写
- arc: 角色弧线从A到B的变化

View File

@@ -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
- score0-100 分
- verdictpass≥80/ warning60-79/ fail<60
- comment该维度的简要结论
- **issues**发现的具体问题列表每个问题包含
- type问题维度consistency/character/pacing/continuity/foreshadow/hook
- severityerror 或 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
## 弧级评审模式(长篇)

View File

@@ -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 中上报

View File

@@ -3,6 +3,7 @@
## 主角
### [角色一姓名]
- **别名/称号**:(如"废物少年"、"炎哥"、"不灭战神"等,正文中可能用到的各种称呼)
- **年龄/职业**
- **外貌特征**
- **性格核心**

42
state/state_changes.go Normal file
View File

@@ -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
}

View File

@@ -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检查本章对角色状态的描述是否与最近的状态变化记录一致
如果没有发现冲突,返回空数组 []。不要返回其他格式。`

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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(),

View File

@@ -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,
}
// 基础样式