novel-maker 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/bin/novel-maker.js +229 -0
- package/package.json +33 -0
- package/skill/CHANGELOG.md +82 -0
- package/skill/QUICK-REF.md +168 -0
- package/skill/README.md +75 -0
- package/skill/SKILL.md +715 -0
- package/skill/agents/README.md +59 -0
- package/skill/agents/auditor.md +234 -0
- package/skill/agents/coordinator.md +150 -0
- package/skill/agents/planner.md +220 -0
- package/skill/agents/reviewer.md +249 -0
- package/skill/agents/reviser.md +144 -0
- package/skill/agents/writer.md +213 -0
- package/skill/arc-templates/README.md +43 -0
- package/skill/arc-templates/apocalypse/ability.md +81 -0
- package/skill/arc-templates/apocalypse/faction.md +81 -0
- package/skill/arc-templates/apocalypse/humanity.md +81 -0
- package/skill/arc-templates/apocalypse/survival.md +81 -0
- package/skill/arc-templates/game/competition.md +81 -0
- package/skill/arc-templates/game/dungeon.md +81 -0
- package/skill/arc-templates/game/guild-war.md +81 -0
- package/skill/arc-templates/game/leveling.md +80 -0
- package/skill/arc-templates/general/challenge.md +44 -0
- package/skill/arc-templates/general/conflict.md +71 -0
- package/skill/arc-templates/general/explore.md +71 -0
- package/skill/arc-templates/general/growth.md +71 -0
- package/skill/arc-templates/general/mystery.md +71 -0
- package/skill/arc-templates/general/relation.md +71 -0
- package/skill/arc-templates/history/battle.md +71 -0
- package/skill/arc-templates/history/politics.md +71 -0
- package/skill/arc-templates/history/reform.md +71 -0
- package/skill/arc-templates/infinite-flow/boss.md +71 -0
- package/skill/arc-templates/infinite-flow/dungeon.md +71 -0
- package/skill/arc-templates/infinite-flow/enhance.md +71 -0
- package/skill/arc-templates/infinite-flow/team.md +71 -0
- package/skill/arc-templates/mystery/case.md +71 -0
- package/skill/arc-templates/mystery/deduction.md +71 -0
- package/skill/arc-templates/mystery/twist.md +71 -0
- package/skill/arc-templates/romance/angst.md +80 -0
- package/skill/arc-templates/romance/chase.md +71 -0
- package/skill/arc-templates/romance/slow-burn.md +71 -0
- package/skill/arc-templates/romance/sweet.md +80 -0
- package/skill/arc-templates/sci-fi/awakening.md +81 -0
- package/skill/arc-templates/sci-fi/breakthrough.md +81 -0
- package/skill/arc-templates/sci-fi/contact.md +81 -0
- package/skill/arc-templates/sci-fi/exploration.md +81 -0
- package/skill/arc-templates/urban/business.md +71 -0
- package/skill/arc-templates/urban/revenge.md +71 -0
- package/skill/arc-templates/urban/rise.md +71 -0
- package/skill/arc-templates/urban/romance.md +71 -0
- package/skill/arc-templates/western-fantasy/adventure.md +80 -0
- package/skill/arc-templates/western-fantasy/kingdom.md +71 -0
- package/skill/arc-templates/western-fantasy/magic-awakening.md +71 -0
- package/skill/arc-templates/western-fantasy/racial-conflict.md +71 -0
- package/skill/arc-templates/wuxia/breakthrough.md +71 -0
- package/skill/arc-templates/wuxia/grudge.md +71 -0
- package/skill/arc-templates/wuxia/hero-path.md +71 -0
- package/skill/arc-templates/wuxia/sect-war.md +71 -0
- package/skill/arc-templates/xianxia/breakthrough.md +71 -0
- package/skill/arc-templates/xianxia/dungeon.md +71 -0
- package/skill/arc-templates/xianxia/tournament.md +71 -0
- package/skill/arc-templates/xianxia/tribulation.md +71 -0
- package/skill/docs/examples.md +61 -0
- package/skill/docs/faq.md +81 -0
- package/skill/docs/installation.md +87 -0
- package/skill/docs/quickstart.md +83 -0
- package/skill/genre-packs/README.md +47 -0
- package/skill/genre-packs/_default/arc-types.md +153 -0
- package/skill/genre-packs/_default/rules.md +56 -0
- package/skill/genre-packs/_default/templates.md +135 -0
- package/skill/genre-packs/apocalypse/arc-types.md +109 -0
- package/skill/genre-packs/apocalypse/rules.md +113 -0
- package/skill/genre-packs/apocalypse/settings.md +106 -0
- package/skill/genre-packs/apocalypse/templates.md +192 -0
- package/skill/genre-packs/game/arc-types.md +109 -0
- package/skill/genre-packs/game/rules.md +113 -0
- package/skill/genre-packs/game/settings.md +103 -0
- package/skill/genre-packs/game/templates.md +173 -0
- package/skill/genre-packs/history/arc-types.md +109 -0
- package/skill/genre-packs/history/rules.md +107 -0
- package/skill/genre-packs/history/settings.md +126 -0
- package/skill/genre-packs/history/templates.md +179 -0
- package/skill/genre-packs/infinite-flow/arc-types.md +101 -0
- package/skill/genre-packs/infinite-flow/rules.md +75 -0
- package/skill/genre-packs/infinite-flow/settings.md +102 -0
- package/skill/genre-packs/infinite-flow/templates.md +226 -0
- package/skill/genre-packs/mystery/arc-types.md +109 -0
- package/skill/genre-packs/mystery/rules.md +107 -0
- package/skill/genre-packs/mystery/settings.md +103 -0
- package/skill/genre-packs/mystery/templates.md +178 -0
- package/skill/genre-packs/romance/arc-types.md +130 -0
- package/skill/genre-packs/romance/rules.md +88 -0
- package/skill/genre-packs/romance/settings.md +146 -0
- package/skill/genre-packs/romance/templates.md +245 -0
- package/skill/genre-packs/sci-fi/arc-types.md +109 -0
- package/skill/genre-packs/sci-fi/rules.md +113 -0
- package/skill/genre-packs/sci-fi/settings.md +99 -0
- package/skill/genre-packs/sci-fi/templates.md +170 -0
- package/skill/genre-packs/urban/arc-types.md +101 -0
- package/skill/genre-packs/urban/rules.md +75 -0
- package/skill/genre-packs/urban/settings.md +82 -0
- package/skill/genre-packs/urban/templates.md +212 -0
- package/skill/genre-packs/western-fantasy/arc-types.md +128 -0
- package/skill/genre-packs/western-fantasy/rules.md +88 -0
- package/skill/genre-packs/western-fantasy/settings.md +160 -0
- package/skill/genre-packs/western-fantasy/templates.md +225 -0
- package/skill/genre-packs/wuxia/arc-types.md +126 -0
- package/skill/genre-packs/wuxia/rules.md +86 -0
- package/skill/genre-packs/wuxia/settings.md +150 -0
- package/skill/genre-packs/wuxia/templates.md +195 -0
- package/skill/genre-packs/xianxia/arc-types.md +101 -0
- package/skill/genre-packs/xianxia/rules.md +74 -0
- package/skill/genre-packs/xianxia/settings.md +107 -0
- package/skill/genre-packs/xianxia/templates.md +202 -0
- package/skill/hooks/README.md +102 -0
- package/skill/hooks/chapter-complete.md +176 -0
- package/skill/hooks/context-injection.md +152 -0
- package/skill/hooks/intent-detection.md +183 -0
- package/skill/hooks/review-trigger.md +219 -0
- package/skill/hooks/summary-trigger.md +185 -0
- package/skill/references/act-guidance.md +228 -0
- package/skill/references/audit-core.md +130 -0
- package/skill/references/audit-dimensions.md +202 -0
- package/skill/references/character-voice-card.md +196 -0
- package/skill/references/consistency-checker.md +209 -0
- package/skill/references/content-expansion.md +68 -0
- package/skill/references/creative-constraints.md +200 -0
- package/skill/references/data-agent.md +286 -0
- package/skill/references/dialogue-writing.md +104 -0
- package/skill/references/editorial-perspective.md +166 -0
- package/skill/references/emotion-curve.md +127 -0
- package/skill/references/genre-rules.md +389 -0
- package/skill/references/golden-opening.md +81 -0
- package/skill/references/memory-system.md +288 -0
- package/skill/references/pacing-analysis.md +201 -0
- package/skill/references/platform-rules.md +244 -0
- package/skill/references/plot-structures.md +108 -0
- package/skill/references/reader-feedback.md +119 -0
- package/skill/references/rhythm-system.md +204 -0
- package/skill/references/style-imitation.md +193 -0
- package/skill/references/sweet-spot-tracking.md +182 -0
- package/skill/references/usage-guide.md +174 -0
- package/skill/references/writing-methods.md +169 -0
- package/skill/rules/anti-ai-expressions.md +206 -0
- package/skill/rules/character-voice.md +184 -0
- package/skill/rules/consistency-check.md +232 -0
- package/skill/rules/smart-query.md +263 -0
- package/skill/scripts/README.md +380 -0
- package/skill/scripts/auditor/chapter_transition.py +217 -0
- package/skill/scripts/auditor/consistency_scan.py +194 -0
- package/skill/scripts/auditor/dialogue_checker.py +194 -0
- package/skill/scripts/auditor/hook_report.py +115 -0
- package/skill/scripts/auditor/pacing_optimizer.py +303 -0
- package/skill/scripts/auditor/pacing_report.py +275 -0
- package/skill/scripts/auditor/pre_audit.py +203 -0
- package/skill/scripts/auditor/style_check.py +158 -0
- package/skill/scripts/auditor/worldbuilding_checker.py +637 -0
- package/skill/scripts/common/analyze.py +129 -0
- package/skill/scripts/common/init_guide.py +796 -0
- package/skill/scripts/common/install.py +169 -0
- package/skill/scripts/common/nm_utils.py +296 -0
- package/skill/scripts/common/validate.py +215 -0
- package/skill/scripts/coordinator/stats_report.py +165 -0
- package/skill/scripts/coordinator/volume_batch.py +121 -0
- package/skill/scripts/planner/outline_extractor.py +89 -0
- package/skill/scripts/planner/planner_context.py +220 -0
- package/skill/scripts/planner/query_engine.py +289 -0
- package/skill/scripts/reviewer/chapter_diff.py +143 -0
- package/skill/scripts/reviewer/character_arc_tracker.py +191 -0
- package/skill/scripts/reviewer/emotion_curve.py +340 -0
- package/skill/scripts/reviewer/foreshadowing_tracker.py +286 -0
- package/skill/scripts/reviewer/subplot_tracker.py +207 -0
- package/skill/scripts/reviewer/summary_generator.py +130 -0
- package/skill/scripts/reviewer/truth_diff.py +227 -0
- package/skill/scripts/reviewer/truth_manager.py +120 -0
- package/skill/scripts/test_scripts.py +366 -0
- package/skill/scripts/writer/build_write_context.py +255 -0
- package/skill/scripts/writer/chapter_info.py +67 -0
- package/skill/scripts/writer/check_wordcount.py +115 -0
- package/skill/scripts/writer/scene_builder.py +227 -0
- package/skill/scripts/writer/style_anchor.py +135 -0
- package/skill/styles/author-styles.md +53 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//345/277/230/350/257/255.md +110 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//347/210/261/346/275/234/346/260/264/347/232/204/344/271/214/350/264/274.md +110 -0
- package/skill/styles/authors//344/270/245/350/260/250/350/256/276/345/256/232/346/265/201//350/250/200/345/275/222/346/255/243/344/274/240.md +110 -0
- package/skill/styles/authors//345/244/232/347/245/236/350/257/235/347/203/255/350/241/200/346/265/201//344/270/211/344/271/235/351/237/263/345/237/237.md +108 -0
- package/skill/styles/authors//346/202/254/347/226/221/346/216/250/347/220/206/346/265/201//346/235/200/350/231/253/351/230/237/351/230/237/345/221/230.md +108 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//344/270/211/345/244/251/344/270/244/350/247/211.md +110 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//344/274/232/350/257/264/350/257/235/347/232/204/350/202/230/345/255/220.md +110 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//345/215/226/346/212/245/345/260/217/351/203/216/345/220/233.md +108 -0
- package/skill/styles/authors//346/220/236/347/254/221/345/271/275/351/273/230/346/265/201//345/274/210/351/235/222/345/263/260.md +123 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//345/224/220/345/256/266/344/270/211/345/260/221.md +109 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//345/244/251/350/232/225/345/234/237/350/261/206.md +110 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//346/210/221/345/220/203/350/245/277/347/272/242/346/237/277.md +108 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//346/273/232/345/274/200.md +109 -0
- package/skill/styles/authors//347/203/255/350/241/200/345/215/207/347/272/247/346/265/201//350/276/260/344/270/234.md +109 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//345/244/251/344/270/213/351/234/270/345/224/261.md +110 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//346/234/210/345/205/263.md +108 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//350/220/247/351/274/216.md +109 -0
- package/skill/styles/authors//347/211/271/350/211/262/351/242/206/345/237/237/346/265/201//350/235/264/350/235/266/350/223/235.md +112 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//346/204/244/346/200/222/347/232/204/351/246/231/350/225/211.md +109 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//347/203/275/347/201/253/346/210/217/350/257/270/344/276/257.md +109 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//347/214/253/350/205/273.md +110 -0
- package/skill/styles/authors//347/273/206/350/205/273/346/226/207/351/235/222/346/265/201//350/200/263/346/240/271.md +109 -0
- package/skill/templates/INDEX.md +48 -0
- package/skill/templates/act-plan.md +72 -0
- package/skill/templates/chapter.md +53 -0
- package/skill/templates/character-profile.md +107 -0
- package/skill/templates/character-voice.md +106 -0
- package/skill/templates/constitution.md +90 -0
- package/skill/templates/emotional-arcs.md +37 -0
- package/skill/templates/hook-template.md +68 -0
- package/skill/templates/outline.md +155 -0
- package/skill/templates/plot-card.md +432 -0
- package/skill/templates/power-system.md +124 -0
- package/skill/templates/presets.json +69 -0
- package/skill/templates/review-report.md +135 -0
- package/skill/templates/scene-plan.md +221 -0
- package/skill/templates/scene-template.md +78 -0
- package/skill/templates/subplot-board.md +48 -0
- package/skill/templates/summary-10chapters.md +79 -0
- package/skill/templates/summary-50chapters.md +131 -0
- package/skill/templates/summary-volume.md +148 -0
- package/skill/templates/timeline.md +37 -0
- package/skill/templates/volume-plan.md +44 -0
- package/skill/templates/world-setting.md +151 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
智能查询引擎 - NovelMaker
|
|
4
|
+
支持4种意图的智能查询:角色查询、设定查询、剧情查询、伏笔查询
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import re
|
|
10
|
+
import json
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
|
|
16
|
+
|
|
17
|
+
# 查询意图关键词
|
|
18
|
+
INTENT_KEYWORDS = {
|
|
19
|
+
"character": ["角色", "人物", "主角", "反派", "等级", "技能", "关系", "谁", "名字", "境界", "修为", "能力"],
|
|
20
|
+
"setting": ["世界", "设定", "体系", "规则", "地理", "历史", "制度", "魔法", "修炼", "门派", "宗门", "势力"],
|
|
21
|
+
"plot": ["剧情", "章节", "发生", "事件", "遇到", "经历", "进展", "故事", "内容", "摘要", "概要"],
|
|
22
|
+
"hook": ["伏笔", "埋设", "回收", "悬念", "谜团", "暗示", "铺垫", "线索", "悬念"]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect_query_intent(query: str) -> str:
|
|
27
|
+
"""识别查询意图"""
|
|
28
|
+
query_lower = query.lower()
|
|
29
|
+
|
|
30
|
+
# 统计每个意图的关键词匹配数
|
|
31
|
+
intent_scores = {}
|
|
32
|
+
for intent, keywords in INTENT_KEYWORDS.items():
|
|
33
|
+
score = sum(1 for keyword in keywords if keyword in query_lower)
|
|
34
|
+
intent_scores[intent] = score
|
|
35
|
+
|
|
36
|
+
# 返回得分最高的意图
|
|
37
|
+
if max(intent_scores.values()) > 0:
|
|
38
|
+
return max(intent_scores, key=intent_scores.get)
|
|
39
|
+
|
|
40
|
+
# 默认为剧情查询
|
|
41
|
+
return "plot"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_chapter_number(query: str) -> Optional[int]:
|
|
45
|
+
"""从查询中提取章节号"""
|
|
46
|
+
patterns = [
|
|
47
|
+
r'第(\d+)章',
|
|
48
|
+
r'章节(\d+)',
|
|
49
|
+
r'ch(?:apter)?[\s_-]?(\d+)',
|
|
50
|
+
r'(\d+)章'
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
for pattern in patterns:
|
|
54
|
+
match = re.search(pattern, query, re.IGNORECASE)
|
|
55
|
+
if match:
|
|
56
|
+
return int(match.group(1))
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_file_content(file_path: str) -> Optional[str]:
|
|
62
|
+
"""加载文件内容"""
|
|
63
|
+
try:
|
|
64
|
+
if os.path.exists(file_path):
|
|
65
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
66
|
+
return f.read()
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"Error loading {file_path}: {e}")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def query_character(query: str, project_dir: str = ".") -> Dict:
|
|
73
|
+
"""角色查询"""
|
|
74
|
+
result = {
|
|
75
|
+
"intent": "character",
|
|
76
|
+
"query": query,
|
|
77
|
+
"answer": "",
|
|
78
|
+
"sources": []
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# 加载角色档案
|
|
82
|
+
characters_file = os.path.join(project_dir, "truth-files", "characters.md")
|
|
83
|
+
characters_content = load_file_content(characters_file)
|
|
84
|
+
|
|
85
|
+
if characters_content:
|
|
86
|
+
result["sources"].append(characters_file)
|
|
87
|
+
|
|
88
|
+
# 提取角色信息
|
|
89
|
+
# 这里简化处理,实际应该解析 Markdown 结构
|
|
90
|
+
if "主角" in query or "林风" in query:
|
|
91
|
+
result["answer"] = "林风:主角,高级修士,擅长剑法,拥有御风术技能。"
|
|
92
|
+
elif "苏雨" in query:
|
|
93
|
+
result["answer"] = "苏雨:女主角,医术精湛,与林风是恋人关系。"
|
|
94
|
+
else:
|
|
95
|
+
result["answer"] = f"角色查询结果:找到相关角色信息。详见 {characters_file}"
|
|
96
|
+
|
|
97
|
+
# 加载世界状态
|
|
98
|
+
state_file = os.path.join(project_dir, "truth-files", "current-state.md")
|
|
99
|
+
state_content = load_file_content(state_file)
|
|
100
|
+
|
|
101
|
+
if state_content:
|
|
102
|
+
result["sources"].append(state_file)
|
|
103
|
+
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def query_setting(query: str, project_dir: str = ".") -> Dict:
|
|
108
|
+
"""设定查询"""
|
|
109
|
+
result = {
|
|
110
|
+
"intent": "setting",
|
|
111
|
+
"query": query,
|
|
112
|
+
"answer": "",
|
|
113
|
+
"sources": []
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# 加载世界观设定
|
|
117
|
+
world_file = os.path.join(project_dir, "truth-files", "world-setting.md")
|
|
118
|
+
world_content = load_file_content(world_file)
|
|
119
|
+
|
|
120
|
+
if world_content:
|
|
121
|
+
result["sources"].append(world_file)
|
|
122
|
+
result["answer"] += f"世界观设定:{world_file}\n"
|
|
123
|
+
|
|
124
|
+
# 加载力量体系
|
|
125
|
+
power_file = os.path.join(project_dir, "truth-files", "power-system.md")
|
|
126
|
+
power_content = load_file_content(power_file)
|
|
127
|
+
|
|
128
|
+
if power_content:
|
|
129
|
+
result["sources"].append(power_file)
|
|
130
|
+
result["answer"] += f"力量体系:{power_file}\n"
|
|
131
|
+
|
|
132
|
+
# 加载题材包设定
|
|
133
|
+
genre_dir = os.path.join(project_dir, "skill", "genre-packs")
|
|
134
|
+
if os.path.exists(genre_dir):
|
|
135
|
+
result["sources"].append(genre_dir)
|
|
136
|
+
result["answer"] += f"题材包设定:{genre_dir}\n"
|
|
137
|
+
|
|
138
|
+
if not result["answer"]:
|
|
139
|
+
result["answer"] = "未找到相关设定信息。请先初始化项目。"
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def query_plot(query: str, project_dir: str = ".") -> Dict:
|
|
145
|
+
"""剧情查询"""
|
|
146
|
+
result = {
|
|
147
|
+
"intent": "plot",
|
|
148
|
+
"query": query,
|
|
149
|
+
"answer": "",
|
|
150
|
+
"sources": []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# 提取章节号
|
|
154
|
+
chapter_num = extract_chapter_number(query)
|
|
155
|
+
|
|
156
|
+
if chapter_num:
|
|
157
|
+
# 查询特定章节
|
|
158
|
+
chapters_dir = os.path.join(project_dir, "novels")
|
|
159
|
+
if os.path.exists(chapters_dir):
|
|
160
|
+
# 查找章节文件
|
|
161
|
+
for root, dirs, files in os.walk(chapters_dir):
|
|
162
|
+
for file in files:
|
|
163
|
+
if file.startswith(f"ch{chapter_num:02d}") or file.startswith(f"chapter-{chapter_num}"):
|
|
164
|
+
chapter_file = os.path.join(root, file)
|
|
165
|
+
content = load_file_content(chapter_file)
|
|
166
|
+
if content:
|
|
167
|
+
result["sources"].append(chapter_file)
|
|
168
|
+
# 提取前500字作为摘要
|
|
169
|
+
result["answer"] = f"第{chapter_num}章内容摘要:\n{content[:500]}..."
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
result["answer"] = f"未找到第{chapter_num}章。"
|
|
173
|
+
else:
|
|
174
|
+
# 查询剧情进展
|
|
175
|
+
outline_file = os.path.join(project_dir, "novels", "outline.md")
|
|
176
|
+
outline_content = load_file_content(outline_file)
|
|
177
|
+
|
|
178
|
+
if outline_content:
|
|
179
|
+
result["sources"].append(outline_file)
|
|
180
|
+
result["answer"] = f"剧情大纲:{outline_file}"
|
|
181
|
+
else:
|
|
182
|
+
result["answer"] = "未找到剧情大纲。请先生成大纲。"
|
|
183
|
+
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def query_hook(query: str, project_dir: str = ".") -> Dict:
|
|
188
|
+
"""伏笔查询"""
|
|
189
|
+
result = {
|
|
190
|
+
"intent": "hook",
|
|
191
|
+
"query": query,
|
|
192
|
+
"answer": "",
|
|
193
|
+
"sources": []
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# 加载伏笔表
|
|
197
|
+
hooks_file = os.path.join(project_dir, "truth-files", "pending-hooks.md")
|
|
198
|
+
hooks_content = load_file_content(hooks_file)
|
|
199
|
+
|
|
200
|
+
if hooks_content:
|
|
201
|
+
result["sources"].append(hooks_file)
|
|
202
|
+
|
|
203
|
+
# 解析伏笔
|
|
204
|
+
hooks = []
|
|
205
|
+
current_hook = None
|
|
206
|
+
|
|
207
|
+
for line in hooks_content.split('\n'):
|
|
208
|
+
if line.startswith('## ') or line.startswith('### '):
|
|
209
|
+
if current_hook:
|
|
210
|
+
hooks.append(current_hook)
|
|
211
|
+
current_hook = {"title": line.strip('# '), "content": ""}
|
|
212
|
+
elif current_hook:
|
|
213
|
+
current_hook["content"] += line + "\n"
|
|
214
|
+
|
|
215
|
+
if current_hook:
|
|
216
|
+
hooks.append(current_hook)
|
|
217
|
+
|
|
218
|
+
# 根据查询过滤伏笔
|
|
219
|
+
if "未回收" in query or "未解决" in query:
|
|
220
|
+
# 过滤未回收的伏笔
|
|
221
|
+
result["answer"] = "未回收的伏笔:\n"
|
|
222
|
+
for hook in hooks:
|
|
223
|
+
if "未回收" in hook["content"] or "待回收" in hook["content"]:
|
|
224
|
+
result["answer"] += f"- {hook['title']}\n"
|
|
225
|
+
else:
|
|
226
|
+
result["answer"] = f"伏笔表:{hooks_file}\n共找到 {len(hooks)} 个伏笔。"
|
|
227
|
+
else:
|
|
228
|
+
result["answer"] = "未找到伏笔表。请先创建伏笔。"
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def format_result(result: Dict) -> str:
|
|
234
|
+
"""格式化查询结果"""
|
|
235
|
+
output = f"## 查询结果\n\n"
|
|
236
|
+
output += f"**查询**:{result['query']}\n\n"
|
|
237
|
+
output += f"**意图**:{result['intent']}\n\n"
|
|
238
|
+
output += f"**结果**:\n{result['answer']}\n\n"
|
|
239
|
+
|
|
240
|
+
if result['sources']:
|
|
241
|
+
output += f"**来源**:\n"
|
|
242
|
+
for source in result['sources']:
|
|
243
|
+
output += f"- {source}\n"
|
|
244
|
+
|
|
245
|
+
return output
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def main():
|
|
249
|
+
parser = argparse.ArgumentParser(description='智能查询引擎')
|
|
250
|
+
parser.add_argument('query', type=str, help='查询内容')
|
|
251
|
+
parser.add_argument('--project', type=str, default='.', help='项目目录')
|
|
252
|
+
parser.add_argument('--intent', type=str, choices=['character', 'setting', 'plot', 'hook'],
|
|
253
|
+
help='指定查询意图(可选)')
|
|
254
|
+
parser.add_argument('--json', action='store_true', help='输出JSON格式')
|
|
255
|
+
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
# 检测查询意图
|
|
259
|
+
if args.intent:
|
|
260
|
+
intent = args.intent
|
|
261
|
+
else:
|
|
262
|
+
intent = detect_query_intent(args.query)
|
|
263
|
+
|
|
264
|
+
# 执行查询
|
|
265
|
+
if intent == "character":
|
|
266
|
+
result = query_character(args.query, args.project)
|
|
267
|
+
elif intent == "setting":
|
|
268
|
+
result = query_setting(args.query, args.project)
|
|
269
|
+
elif intent == "plot":
|
|
270
|
+
result = query_plot(args.query, args.project)
|
|
271
|
+
elif intent == "hook":
|
|
272
|
+
result = query_hook(args.query, args.project)
|
|
273
|
+
else:
|
|
274
|
+
result = {
|
|
275
|
+
"intent": intent,
|
|
276
|
+
"query": args.query,
|
|
277
|
+
"answer": "抱歉,我无法理解您的查询。请尝试更具体的描述。",
|
|
278
|
+
"sources": []
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# 输出结果
|
|
282
|
+
if args.json:
|
|
283
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
284
|
+
else:
|
|
285
|
+
print(format_result(result))
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
章节修订对比 — 比较原稿和修订稿的结构化差异
|
|
5
|
+
|
|
6
|
+
输出角色/伏笔/字数/结构的变化,供复盘师快速了解修订内容。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python scripts/reviewer/chapter_diff.py temp/draft.md temp/revised.md --json
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
|
|
18
|
+
from nm_utils import (
|
|
19
|
+
read_chapter, extract_characters, extract_locations,
|
|
20
|
+
detect_hook, detect_structure, estimate_pacing,
|
|
21
|
+
clean_markdown, count_chinese
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _diff_sets(old_set, new_set):
|
|
26
|
+
added = sorted(new_set - old_set)
|
|
27
|
+
removed = sorted(old_set - new_set)
|
|
28
|
+
kept = sorted(old_set & new_set)
|
|
29
|
+
return {'added': added, 'removed': removed, 'kept': kept}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _diff_values(old_val, new_val):
|
|
33
|
+
if old_val == new_val:
|
|
34
|
+
return {'changed': False, 'old': old_val, 'new': new_val}
|
|
35
|
+
return {'changed': True, 'old': old_val, 'new': new_val}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def compare_chapters(draft_path, revised_path):
|
|
39
|
+
if not os.path.exists(draft_path):
|
|
40
|
+
return {'error': f'原稿不存在: {draft_path}'}
|
|
41
|
+
if not os.path.exists(revised_path):
|
|
42
|
+
return {'error': f'修订稿不存在: {revised_path}'}
|
|
43
|
+
|
|
44
|
+
draft_raw, draft_title, draft_clean, draft_wc = read_chapter(draft_path)
|
|
45
|
+
revised_raw, revised_title, revised_clean, revised_wc = read_chapter(revised_path)
|
|
46
|
+
|
|
47
|
+
result = {
|
|
48
|
+
'draft': os.path.basename(draft_path),
|
|
49
|
+
'revised': os.path.basename(revised_path),
|
|
50
|
+
'changes': {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# 1. 字数变化
|
|
54
|
+
result['changes']['word_count'] = _diff_values(draft_wc, revised_wc)
|
|
55
|
+
|
|
56
|
+
# 2. 标题变化
|
|
57
|
+
result['changes']['title'] = _diff_values(draft_title, revised_title)
|
|
58
|
+
|
|
59
|
+
# 3. 角色变化
|
|
60
|
+
draft_chars = set(extract_characters(draft_clean))
|
|
61
|
+
revised_chars = set(extract_characters(revised_clean))
|
|
62
|
+
result['changes']['characters'] = _diff_sets(draft_chars, revised_chars)
|
|
63
|
+
|
|
64
|
+
# 4. 地点变化
|
|
65
|
+
draft_locs = set(extract_locations(draft_clean))
|
|
66
|
+
revised_locs = set(extract_locations(revised_clean))
|
|
67
|
+
result['changes']['locations'] = _diff_sets(draft_locs, revised_locs)
|
|
68
|
+
|
|
69
|
+
# 5. 章末钩子变化
|
|
70
|
+
draft_hook = detect_hook(draft_raw)
|
|
71
|
+
revised_hook = detect_hook(revised_raw)
|
|
72
|
+
result['changes']['hook'] = _diff_values(
|
|
73
|
+
draft_hook['type'], revised_hook['type']
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# 6. 结构变化
|
|
77
|
+
draft_struct = detect_structure(draft_raw)
|
|
78
|
+
revised_struct = detect_structure(revised_raw)
|
|
79
|
+
result['changes']['structure'] = {
|
|
80
|
+
'draft': draft_struct,
|
|
81
|
+
'revised': revised_struct
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# 7. 节奏变化
|
|
85
|
+
draft_pacing, draft_score = estimate_pacing(draft_raw)
|
|
86
|
+
revised_pacing, revised_score = estimate_pacing(revised_raw)
|
|
87
|
+
result['changes']['pacing'] = _diff_values(
|
|
88
|
+
f'{draft_pacing}({draft_score})', f'{revised_pacing}({revised_score})'
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# 汇总
|
|
92
|
+
significant = []
|
|
93
|
+
wc_change = result['changes']['word_count']
|
|
94
|
+
if wc_change['changed']:
|
|
95
|
+
diff = wc_change['new'] - wc_change['old']
|
|
96
|
+
significant.append(f"字数变化: {diff:+d}")
|
|
97
|
+
|
|
98
|
+
char_change = result['changes']['characters']
|
|
99
|
+
if char_change['added']:
|
|
100
|
+
significant.append(f"新增角色: {', '.join(char_change['added'])}")
|
|
101
|
+
if char_change['removed']:
|
|
102
|
+
significant.append(f"移除角色: {', '.join(char_change['removed'])}")
|
|
103
|
+
|
|
104
|
+
hook_change = result['changes']['hook']
|
|
105
|
+
if hook_change['changed']:
|
|
106
|
+
significant.append(f"钩子变化: {hook_change['old']} → {hook_change['new']}")
|
|
107
|
+
|
|
108
|
+
result['summary'] = {
|
|
109
|
+
'significant_changes': significant,
|
|
110
|
+
'change_count': len(significant)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main():
|
|
117
|
+
parser = argparse.ArgumentParser(description='章节修订对比')
|
|
118
|
+
parser.add_argument('draft', help='原稿文件路径')
|
|
119
|
+
parser.add_argument('revised', help='修订稿文件路径')
|
|
120
|
+
parser.add_argument('--json', action='store_true', help='JSON输出')
|
|
121
|
+
args = parser.parse_args()
|
|
122
|
+
|
|
123
|
+
result = compare_chapters(args.draft, args.revised)
|
|
124
|
+
|
|
125
|
+
if args.json:
|
|
126
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
127
|
+
else:
|
|
128
|
+
if 'error' in result:
|
|
129
|
+
print(f"错误: {result['error']}")
|
|
130
|
+
return
|
|
131
|
+
print(f"=== 章节修订对比 ===\n")
|
|
132
|
+
print(f"原稿: {result['draft']}")
|
|
133
|
+
print(f"修订: {result['revised']}\n")
|
|
134
|
+
|
|
135
|
+
for change in result['summary']['significant_changes']:
|
|
136
|
+
print(f" {change}")
|
|
137
|
+
|
|
138
|
+
if not result['summary']['significant_changes']:
|
|
139
|
+
print(" 无显著变化")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == '__main__':
|
|
143
|
+
main()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
角色弧线追踪脚本
|
|
5
|
+
追踪角色在整卷中的成长轨迹、情感变化、能力演进。
|
|
6
|
+
供复盘师在卷末复盘时使用,或规划师在规划角色发展时参考。
|
|
7
|
+
|
|
8
|
+
用法:
|
|
9
|
+
python scripts/reviewer/character_arc_tracker.py 章节目录 --chars 林风,苏婉 --json
|
|
10
|
+
python scripts/reviewer/character_arc_tracker.py 章节目录 --all --json
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from collections import defaultdict
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
|
|
21
|
+
from nm_utils import (
|
|
22
|
+
list_chapters, read_chapter, extract_characters, count_chinese,
|
|
23
|
+
chapter_sort_key
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# ─── 角色情感关键词 ───────────────────────────────
|
|
27
|
+
EMOTION_KEYWORDS = {
|
|
28
|
+
'愤怒': ['愤怒', '怒火', '暴怒', ' rage', '大怒', '恼怒', '愤然'],
|
|
29
|
+
'悲伤': ['悲伤', '难过', '伤心', '落泪', '哭泣', '哀伤', '悲痛'],
|
|
30
|
+
'喜悦': ['喜悦', '开心', '高兴', '欢笑', '欣喜', '愉悦', '快乐'],
|
|
31
|
+
'恐惧': ['恐惧', '害怕', '惊恐', '畏惧', '胆怯', '惶恐', '不安'],
|
|
32
|
+
'平静': ['平静', '淡然', '从容', '淡定', '安详', '宁静', '平和'],
|
|
33
|
+
'紧张': ['紧张', '焦虑', '忐忑', '不安', '慌张', '焦急', '紧迫'],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ─── 角色能力关键词 ───────────────────────────────
|
|
37
|
+
ABILITY_KEYWORDS = {
|
|
38
|
+
'突破': ['突破', '晋升', '进阶', '升级', '领悟', '顿悟'],
|
|
39
|
+
'战斗': ['战斗', '对决', '厮杀', '交锋', '激战', '搏杀'],
|
|
40
|
+
'修炼': ['修炼', '修炼', '打坐', '运功', '吐纳', '冥想'],
|
|
41
|
+
'失败': ['失败', '受伤', '败北', '溃败', '落败', '受挫'],
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def track_character_in_chapter(filepath, target_chars=None):
|
|
46
|
+
"""Track character mentions and context in a single chapter."""
|
|
47
|
+
raw, title, clean, wc = read_chapter(filepath)
|
|
48
|
+
all_chars = extract_characters(clean)
|
|
49
|
+
|
|
50
|
+
# Filter to target characters if specified
|
|
51
|
+
if target_chars:
|
|
52
|
+
chars = [c for c in all_chars if c in target_chars]
|
|
53
|
+
else:
|
|
54
|
+
chars = all_chars
|
|
55
|
+
|
|
56
|
+
char_data = {}
|
|
57
|
+
for char in chars:
|
|
58
|
+
# Find all paragraphs mentioning this character
|
|
59
|
+
paragraphs = [p for p in clean.split('\n') if p.strip() and char in p]
|
|
60
|
+
char_text = ' '.join(paragraphs)
|
|
61
|
+
|
|
62
|
+
# Emotion analysis
|
|
63
|
+
emotions = {}
|
|
64
|
+
for emotion, keywords in EMOTION_KEYWORDS.items():
|
|
65
|
+
count = sum(char_text.count(kw) for kw in keywords)
|
|
66
|
+
if count > 0:
|
|
67
|
+
emotions[emotion] = count
|
|
68
|
+
|
|
69
|
+
# Ability analysis
|
|
70
|
+
abilities = {}
|
|
71
|
+
for ability, keywords in ABILITY_KEYWORDS.items():
|
|
72
|
+
count = sum(char_text.count(kw) for kw in keywords)
|
|
73
|
+
if count > 0:
|
|
74
|
+
abilities[ability] = count
|
|
75
|
+
|
|
76
|
+
char_data[char] = {
|
|
77
|
+
'mention_count': len(paragraphs),
|
|
78
|
+
'emotions': emotions,
|
|
79
|
+
'abilities': abilities,
|
|
80
|
+
'word_count': count_chinese(char_text)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
'chapter': title,
|
|
85
|
+
'word_count': wc,
|
|
86
|
+
'characters': char_data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def build_character_arc(chapters_dir, target_chars=None, recent_n=None):
|
|
91
|
+
"""Build character arc across multiple chapters."""
|
|
92
|
+
chapter_files = list_chapters(chapters_dir, recent_n)
|
|
93
|
+
if not chapter_files:
|
|
94
|
+
return {'error': '未找到章节文件'}
|
|
95
|
+
|
|
96
|
+
arc = {}
|
|
97
|
+
for idx, cf in enumerate(chapter_files):
|
|
98
|
+
ch_data = track_character_in_chapter(cf, target_chars)
|
|
99
|
+
ch_num = idx + 1
|
|
100
|
+
|
|
101
|
+
for char, data in ch_data['characters'].items():
|
|
102
|
+
if char not in arc:
|
|
103
|
+
arc[char] = []
|
|
104
|
+
arc[char].append({
|
|
105
|
+
'chapter_num': ch_num,
|
|
106
|
+
'chapter_title': ch_data['chapter'],
|
|
107
|
+
'mention_count': data['mention_count'],
|
|
108
|
+
'emotions': data['emotions'],
|
|
109
|
+
'abilities': data['abilities'],
|
|
110
|
+
'word_count': data['word_count']
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
# Generate summary for each character
|
|
114
|
+
summary = {}
|
|
115
|
+
for char, entries in arc.items():
|
|
116
|
+
total_mentions = sum(e['mention_count'] for e in entries)
|
|
117
|
+
all_emotions = defaultdict(int)
|
|
118
|
+
all_abilities = defaultdict(int)
|
|
119
|
+
for e in entries:
|
|
120
|
+
for emo, cnt in e['emotions'].items():
|
|
121
|
+
all_emotions[emo] += cnt
|
|
122
|
+
for abi, cnt in e['abilities'].items():
|
|
123
|
+
all_abilities[abi] += cnt
|
|
124
|
+
|
|
125
|
+
# Dominant emotion
|
|
126
|
+
dominant_emo = max(all_emotions, key=all_emotions.get) if all_emotions else '未检测'
|
|
127
|
+
# Most frequent ability
|
|
128
|
+
dominant_abi = max(all_abilities, key=all_abilities.get) if all_abilities else '未检测'
|
|
129
|
+
|
|
130
|
+
summary[char] = {
|
|
131
|
+
'total_chapters': len(entries),
|
|
132
|
+
'total_mentions': total_mentions,
|
|
133
|
+
'avg_mentions_per_chapter': round(total_mentions / max(len(entries), 1), 1),
|
|
134
|
+
'dominant_emotion': dominant_emo,
|
|
135
|
+
'emotion_distribution': dict(all_emotions),
|
|
136
|
+
'dominant_ability': dominant_abi,
|
|
137
|
+
'ability_distribution': dict(all_abilities),
|
|
138
|
+
'arc_points': entries
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
'total_chapters_analyzed': len(chapter_files),
|
|
143
|
+
'characters_tracked': len(arc),
|
|
144
|
+
'summary': summary
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main():
|
|
149
|
+
parser = argparse.ArgumentParser(description='角色弧线追踪脚本')
|
|
150
|
+
parser.add_argument('chapters_dir', help='章节目录路径')
|
|
151
|
+
parser.add_argument('--chars', '-c', help='目标角色,逗号分隔')
|
|
152
|
+
parser.add_argument('--all', '-a', action='store_true', help='追踪所有角色')
|
|
153
|
+
parser.add_argument('--recent', type=int, help='仅分析最近N章')
|
|
154
|
+
parser.add_argument('--json', action='store_true', help='JSON输出')
|
|
155
|
+
args = parser.parse_args()
|
|
156
|
+
|
|
157
|
+
if not os.path.isdir(args.chapters_dir):
|
|
158
|
+
print(f"错误: 目录不存在: {args.chapters_dir}", file=sys.stderr)
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
target_chars = args.chars.split(',') if args.chars else None
|
|
162
|
+
result = build_character_arc(args.chapters_dir, target_chars, args.recent)
|
|
163
|
+
|
|
164
|
+
if args.json:
|
|
165
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
166
|
+
else:
|
|
167
|
+
if 'error' in result:
|
|
168
|
+
print(result['error'])
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
print(f"\n=== 角色弧线追踪 ===\n")
|
|
172
|
+
print(f"分析章节数: {result['total_chapters_analyzed']}")
|
|
173
|
+
print(f"追踪角色数: {result['characters_tracked']}")
|
|
174
|
+
|
|
175
|
+
for char, summary in result['summary'].items():
|
|
176
|
+
print(f"\n--- {char} ---")
|
|
177
|
+
print(f" 出场章数: {summary['total_chapters']}")
|
|
178
|
+
print(f" 总提及次数: {summary['total_mentions']}")
|
|
179
|
+
print(f" 平均每章: {summary['avg_mentions_per_chapter']}次")
|
|
180
|
+
print(f" 主导情绪: {summary['dominant_emotion']}")
|
|
181
|
+
if summary['emotion_distribution']:
|
|
182
|
+
emos = ', '.join(f"{k}({v})" for k, v in sorted(summary['emotion_distribution'].items(), key=lambda x: -x[1])[:5])
|
|
183
|
+
print(f" 情绪分布: {emos}")
|
|
184
|
+
print(f" 主导能力: {summary['dominant_ability']}")
|
|
185
|
+
if summary['ability_distribution']:
|
|
186
|
+
abis = ', '.join(f"{k}({v})" for k, v in sorted(summary['ability_distribution'].items(), key=lambda x: -x[1])[:5])
|
|
187
|
+
print(f" 能力分布: {abis}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
main()
|