feat: initial commit

This commit is contained in:
2026-06-23 20:29:02 +08:00
commit ea8f6066c4
217 changed files with 60754 additions and 0 deletions
@@ -0,0 +1,938 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Chapter Analyzer - 章节自动解构与模式提取
功能:
1. 章节内容解析(场景分割、人物提取)
2. 张力曲线分析(情绪压强跟踪)
3. 爽点模式识别(打脸、降维、闭环、僭越)
4. 套路结构提取(铺垫→压抑→爆发→反转)
5. 学习成果输出(可复用模式)
用法:
python chapter_analyzer.py --chapter-file "正文/第0003章-当众打脸.md" --project-root . --learn
"""
import re
import json
import hashlib
import sqlite3
from pathlib import Path
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Any, Optional, Tuple
from datetime import datetime
from enum import Enum
import asyncio
try:
from runtime_compat import enable_windows_utf8_stdio
except ImportError:
enable_windows_utf8_stdio = lambda: None
class PatternType(Enum):
"""模式类型"""
HOOK = "hook" # 钩子
PACING = "pacing" # 节奏
DIALOGUE = "dialogue" # 对话
PAYOFF = "payoff" # 兑现
EMOTION = "emotion" # 情绪
TENSION_BUILD = "tension_build" # 压强积累
RELEASE = "release" # 释放
TWIST = "twist" # 反转
class CatharsisModel(Enum):
"""爽感模型"""
TABOO_TRANSGRESSION = "taboo_transgression" # 禁忌僭越
OVERKILL_REVERSAL = "overkill_reversal" # 降维打击
COGNITIVE_CLOSURE = "cognitive_closure" # 认知闭环
@dataclass
class TensionPoint:
"""张力点"""
position: int # 位置(字符偏移)
tension: float # 张力值 0-1
event_type: str # 事件类型
description: str # 描述
@dataclass
class HotSpot:
"""爽点"""
position: int
span: int # 持续长度
catharsis_type: str # 爽感类型
intensity: float # 强度 0-1
description: str
@dataclass
class ChapterStructure:
"""章节结构"""
setup: int = 0 # 铺垫段落长度
suppress: int = 0 # 压抑段落长度
release: int = 0 # 释放段落长度
twist: int = 0 # 反转段落长度
@dataclass
class LearnedPattern:
"""学习到的模式"""
pattern_id: str
pattern_type: str
title: str
description: str
tension_curve: List[List[float]] # [[position, tension], ...]
catharsis_model: str
structure: Dict[str, int]
hot_spots: List[List[Any]]
style_tags: List[str]
source_project: str
source_chapter: int
learned_at: str
usage_count: int = 0
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class ChapterAnalysisResult:
"""章节分析结果"""
chapter: int
title: str
word_count: int
scene_count: int
character_count: int
# 张力分析
tension_curve: List[List[float]] # [[position, tension], ...]
tension_avg: float
tension_peak: float
tension_peak_position: int
# 爽点分析
hot_spots: List[HotSpot]
catharsis_model: str
# 结构分析
structure: ChapterStructure
# 模式
detected_patterns: List[str]
# 风格标签
style_tags: List[str]
# 人物出场
characters: List[str]
# 建议
suggestions: List[str]
class ChapterAnalyzer:
"""
章节分析器
自动解构章节,提取:
- 张力曲线
- 爽点位置和类型
- 套路结构
- 可复用模式
"""
# 爽点关键词
HOT_SPOT_KEYWORDS = {
CatharsisModel.OVERKILL_REVERSAL: [
"碾压", "秒杀", "不堪一击", "一招", "跪下", "颤抖", "惊恐",
"脸色大变", "难以置信", "怎么可能", "废物", "蝼蚁", "打脸",
"当众", "所有人", "目瞪口呆", "鸦雀无声", "死寂"
],
CatharsisModel.TABOO_TRANSGRESSION: [
"禁忌", "僭越", "突破底线", "危险", "禁忌", "邪魅", "诱惑",
"堕落", "黑化", "疯狂", "失控", "暴走", "越界", "禁忌"
],
CatharsisModel.COGNITIVE_CLOSURE: [
"原来", "竟然", "真相", "伏笔", "恍然大悟", "所有一切",
"早该", "早就在", "铺垫", "埋下", "算计", "布局"
]
}
# 情绪压强关键词
TENSION_KEYWORDS = {
# 压强上升
"rise": ["紧张", "危机", "危险", "困境", "难题", "冲突", "对峙",
"杀意", "阴谋", "威胁", "危机", "悬念", "未知"],
# 压强高峰
"peak": ["爆发", "突破", "反击", "反转", "真相", "高潮", "决战",
"绝杀", "逆转", "翻盘", "打脸", "碾压"],
# 压强下降
"fall": ["松了口气", "终于", "安心", "胜利", "结束", "平息", "安然"]
}
def __init__(self, project_root: Optional[Path] = None):
self.project_root = Path(project_root) if project_root else Path.cwd()
def analyze_chapter(
self,
chapter_file: Path,
chapter_num: Optional[int] = None
) -> ChapterAnalysisResult:
"""
分析章节
Args:
chapter_file: 章节文件路径
chapter_num: 章节号(从文件名推断或手动指定)
Returns:
章节分析结果
"""
content = chapter_file.read_text(encoding="utf-8")
# 解析章节号
if chapter_num is None:
chapter_num = self._extract_chapter_num(chapter_file.name)
# 解析标题
title = self._extract_title(chapter_file.name, content)
# 统计字数
word_count = len(content)
# 场景分割
scenes = self._split_scenes(content)
scene_count = len(scenes)
# 人物提取
characters = self._extract_characters(content, scenes)
# 张力曲线分析
tension_curve = self._analyze_tension_curve(content, scenes)
tension_avg = sum(p[1] for p in tension_curve) / len(tension_curve) if tension_curve else 0
peak = max(tension_curve, key=lambda x: x[1]) if tension_curve else (0, 0)
tension_peak = peak[1]
tension_peak_position = peak[0]
# 爽点分析
hot_spots = self._detect_hot_spots(content, scenes)
catharsis_model = self._detect_catharsis_model(hot_spots)
# 结构分析
structure = self._analyze_structure(content, scenes, hot_spots)
# 模式检测
detected_patterns = self._detect_patterns(
content, scenes, tension_curve, hot_spots, structure
)
# 风格标签
style_tags = self._extract_style_tags(content, tension_curve, hot_spots)
# 建议
suggestions = self._generate_suggestions(
tension_curve, hot_spots, structure, detected_patterns
)
return ChapterAnalysisResult(
chapter=chapter_num,
title=title,
word_count=word_count,
scene_count=scene_count,
character_count=len(characters),
tension_curve=tension_curve,
tension_avg=tension_avg,
tension_peak=tension_peak,
tension_peak_position=tension_peak_position,
hot_spots=hot_spots,
catharsis_model=catharsis_model,
structure=structure,
detected_patterns=detected_patterns,
style_tags=style_tags,
characters=characters,
suggestions=suggestions
)
def _extract_chapter_num(self, filename: str) -> int:
"""从文件名提取章节号"""
match = re.search(r'第(\d+)[章节]', filename)
if match:
return int(match.group(1))
return 0
def _extract_title(self, filename: str, content: str) -> str:
"""提取标题"""
# 尝试从文件名提取
match = re.search(r'\d+章[章节]-?(.+)', filename)
if match:
return match.group(1).replace('.md', '').strip()
# 尝试从内容第一行提取
lines = content.split('\n')
for line in lines[:5]:
line = line.strip()
if line.startswith('#'):
return line.lstrip('#').strip()
return filename.replace('.md', '')
def _split_scenes(self, content: str) -> List[Dict[str, Any]]:
"""
分割场景
Returns:
List[{"start": int, "end": int, "location": str, "characters": []}]
"""
scenes = []
lines = content.split('\n')
current_scene = {
"start": 0,
"end": 0,
"location": "未知",
"characters": [],
"lines": []
}
location_patterns = [
r'^---+$', # 分割线
r'^【(.+?)】', # 【场景名】
r'^(.+?)', # (场景名)
]
for i, line in enumerate(lines):
# 检测场景分隔
is_divider = any(re.match(p, line.strip()) for p in location_patterns)
if is_divider and current_scene["lines"]:
# 保存当前场景
current_scene["end"] = sum(len(l) + 1 for l in current_scene["lines"][:-1])
scenes.append(current_scene)
# 新场景
location_match = re.search(r'【(.+?)】|(.+?)', line)
current_scene = {
"start": sum(len(l) + 1 for l in lines[:i]) + 1,
"end": 0,
"location": location_match.group(1) if location_match else "未知",
"characters": [],
"lines": []
}
current_scene["lines"].append(line)
# 保存最后一个场景
if current_scene["lines"]:
current_scene["end"] = len(content)
scenes.append(current_scene)
return scenes if scenes else [{"start": 0, "end": len(content), "location": "未知", "characters": [], "lines": content.split('\n')}]
def _extract_characters(
self,
content: str,
scenes: List[Dict[str, Any]]
) -> List[str]:
"""提取人物列表"""
# 简单实现:提取引号内的对话人
characters = set()
# 匹配 "XXX说" 格式
dialogue_pattern = re.compile(r'^"?([^"说]{2,5})"?[说问道喊叫笑骂冷哼]')
for scene in scenes:
for line in scene["lines"]:
match = dialogue_pattern.match(line.strip())
if match:
name = match.group(1).strip()
if name and len(name) <= 5:
characters.add(name)
return list(characters)
def _analyze_tension_curve(
self,
content: str,
scenes: List[Dict[str, Any]]
) -> List[List[float]]:
"""
分析张力曲线
Returns:
[[position, tension], ...] - 位置(字数)和张力值(0-1)
"""
curve = []
total_len = len(content)
segment_size = 200 # 每200字一个采样点
content_lower = content # 网文不区分大小写
for pos in range(0, total_len, segment_size):
segment = content_lower[pos:pos + segment_size]
tension = 0.0
# 检查压强上升关键词
for kw in self.TENSION_KEYWORDS["rise"]:
if kw in segment:
tension += 0.2
# 检查压强高峰关键词
for kw in self.TENSION_KEYWORDS["peak"]:
if kw in segment:
tension += 0.4
# 检查压强下降关键词
for kw in self.TENSION_KEYWORDS["fall"]:
if kw in segment:
tension -= 0.15
# 限制范围
tension = max(0.0, min(1.0, tension))
curve.append([pos, tension])
# 平滑曲线
curve = self._smooth_curve(curve)
return curve
def _smooth_curve(self, curve: List[List[float]]) -> List[List[float]]:
"""平滑张力曲线"""
if len(curve) < 3:
return curve
smoothed = []
for i, point in enumerate(curve):
if i == 0 or i == len(curve) - 1:
smoothed.append(point)
else:
# 移动平均
avg_pos = (curve[i-1][0] + point[0] + curve[i+1][0]) / 3
avg_tension = (curve[i-1][1] + point[1] + curve[i+1][1]) / 3
smoothed.append([avg_pos, avg_tension])
return smoothed
def _detect_hot_spots(
self,
content: str,
scenes: List[Dict[str, Any]]
) -> List[HotSpot]:
"""检测爽点"""
hot_spots = []
for model_type, keywords in self.HOT_SPOT_KEYWORDS.items():
for keyword in keywords:
# 查找所有出现位置
start = 0
while True:
pos = content.find(keyword, start)
if pos == -1:
break
# 计算附近区域的强度
context_start = max(0, pos - 100)
context_end = min(len(content), pos + 100)
context = content[context_start:context_end]
# 统计上下文中的情绪词数量
intensity = 0.5 # 基础强度
for kw_set in self.HOT_SPOT_KEYWORDS.values():
for kw in kw_set:
if kw in context:
intensity += 0.1
intensity = min(1.0, intensity)
# 避免重叠
span = len(keyword)
is_overlap = any(
abs(pos - hs.position) < span
for hs in hot_spots
)
if not is_overlap:
hot_spots.append(HotSpot(
position=pos,
span=span,
catharsis_type=model_type.value,
intensity=intensity,
description=f"发现「{keyword}"
))
start = pos + 1
# 按位置排序
hot_spots.sort(key=lambda x: x.position)
return hot_spots
def _detect_catharsis_model(
self,
hot_spots: List[HotSpot]
) -> str:
"""检测主导爽感模型"""
if not hot_spots:
return "unknown"
model_counts = {}
model_intensity = {}
for hs in hot_spots:
model = hs.catharsis_type
model_counts[model] = model_counts.get(model, 0) + 1
model_intensity[model] = model_intensity.get(model, 0) + hs.intensity
# 综合评分:出现次数 * 0.4 + 强度 * 0.6
scores = {
m: model_counts[m] * 0.4 + model_intensity[m] * 0.6
for m in model_counts
}
return max(scores, key=scores.get) if scores else "unknown"
def _analyze_structure(
self,
content: str,
scenes: List[Dict[str, Any]],
hot_spots: List[HotSpot]
) -> ChapterStructure:
"""分析章节结构"""
total_len = len(content)
if not hot_spots:
# 无爽点:均匀分布
segment = total_len // 4
return ChapterStructure(
setup=segment,
suppress=segment,
release=segment,
twist=segment
)
# 找第一个爽点位置作为分界
first_hot = hot_spots[0].position
last_hot = hot_spots[-1].position
# 铺垫:第一个爽点之前
setup = first_hot
# 压抑+释放:根据爽点密集程度判断
# 简化:中间区域前40%压抑,后40%释放
middle_start = first_hot
middle_end = last_hot + 100
middle_len = middle_end - middle_start
suppress = int(middle_len * 0.4)
release = int(middle_len * 0.4)
twist = total_len - middle_end
return ChapterStructure(
setup=max(0, setup),
suppress=max(0, suppress),
release=max(0, release),
twist=max(0, twist)
)
def _detect_patterns(
self,
content: str,
scenes: List[Dict[str, Any]],
tension_curve: List[List[float]],
hot_spots: List[HotSpot],
structure: ChapterStructure
) -> List[str]:
"""检测章节中的模式"""
patterns = []
# 检测钩子模式
if tension_curve and tension_curve[0][1] > 0.3:
patterns.append("开篇高能钩")
elif "" in content[:200] or "" in content[:200]:
patterns.append("悬念钩子")
# 检测打脸模式
if any(hs.catharsis_type == "overkill_reversal" for hs in hot_spots):
patterns.append("打脸反转")
# 检测升级模式
if any(kw in content for kw in ["突破", "晋升", "升级", "进阶"]):
patterns.append("境界突破")
# 检测装逼模式
if any(kw in content for kw in ["冷笑", "不屑", "蝼蚁", "可笑"]):
patterns.append("装逼打脸")
# 张力曲线形状检测
if tension_curve:
# 持续上升 = 压抑型
tensions = [p[1] for p in tension_curve]
if tensions == sorted(tensions) and tensions[-1] - tensions[0] > 0.5:
patterns.append("单线压抑")
# 波动大 = 节奏快
elif max(tensions) - min(tensions) > 0.6:
patterns.append("爽点密集")
return patterns
def _extract_style_tags(
self,
content: str,
tension_curve: List[List[float]],
hot_spots: List[HotSpot]
) -> List[str]:
"""提取风格标签"""
tags = []
# 基于字数
word_count = len(content)
if word_count < 2000:
tags.append("短小精悍")
elif word_count > 5000:
tags.append("长篇巨制")
# 基于爽点密度
if tension_curve:
avg_tension = sum(p[1] for p in tension_curve) / len(tension_curve)
if avg_tension > 0.5:
tags.append("情绪压强高")
elif avg_tension < 0.2:
tags.append("节奏舒缓")
# 基于爽点类型
if hot_spots:
model = self._detect_catharsis_model(hot_spots)
if model == "overkill_reversal":
tags.append("爽点密集")
elif model == "taboo_transgression":
tags.append("边缘拉扯")
elif model == "cognitive_closure":
tags.append("多线收束")
# 基于对话比例
dialogue_count = content.count('"') + content.count('"')
dialogue_ratio = dialogue_count / max(word_count, 1)
if dialogue_ratio > 0.3:
tags.append("对话驱动")
elif dialogue_ratio < 0.1:
tags.append("叙事为主")
return tags
def _generate_suggestions(
self,
tension_curve: List[List[float]],
hot_spots: List[HotSpot],
structure: ChapterStructure,
patterns: List[str]
) -> List[str]:
"""生成改进建议"""
suggestions = []
# 张力曲线建议
if tension_curve:
avg = sum(p[1] for p in tension_curve) / len(tension_curve)
if avg < 0.2:
suggestions.append("张力整体偏低,建议增加危机感或悬念")
elif avg > 0.7:
suggestions.append("张力持续偏高,建议适当释放避免读者疲劳")
# 爽点建议
if not hot_spots:
suggestions.append("未检测到明显爽点,建议增加打脸/反转情节")
# 结构建议
if structure.setup < 500:
suggestions.append("铺垫不足,建议增加背景/动机描写")
# 节奏建议
if "单线压抑" in patterns:
suggestions.append("压抑较长,建议穿插小高潮保持节奏")
return suggestions
def learn_pattern(
self,
analysis: ChapterAnalysisResult,
layer: str = "project"
) -> LearnedPattern:
"""
从分析结果提取可学习模式
Args:
analysis: 章节分析结果
layer: 存储层级 ("project""system")
Returns:
学习到的模式
"""
pattern_id = hashlib.md5(
f"{self.project_root.name}_{analysis.chapter}_{datetime.now().isoformat()}".encode()
).hexdigest()[:16]
return LearnedPattern(
pattern_id=pattern_id,
pattern_type=self._infer_pattern_type(analysis),
title=f"{analysis.chapter}章模式: {analysis.title}",
description=self._generate_description(analysis),
tension_curve=analysis.tension_curve,
catharsis_model=analysis.catharsis_model,
structure=asdict(analysis.structure),
hot_spots=[[hs.position, hs.description] for hs in analysis.hot_spots],
style_tags=analysis.style_tags,
source_project=self.project_root.name,
source_chapter=analysis.chapter,
learned_at=datetime.now().isoformat(),
metadata={
"word_count": analysis.word_count,
"scene_count": analysis.scene_count,
"character_count": analysis.character_count,
"detected_patterns": analysis.detected_patterns,
"suggestions": analysis.suggestions
}
)
def _infer_pattern_type(self, analysis: ChapterAnalysisResult) -> str:
"""推断模式类型"""
if "打脸反转" in analysis.detected_patterns:
return PatternType.HOOK.value
elif "境界突破" in analysis.detected_patterns:
return PatternType.PAYOFF.value
elif analysis.tension_avg > 0.5:
return PatternType.TENSION_BUILD.value
else:
return PatternType.PACING.value
def _generate_description(self, analysis: ChapterAnalysisResult) -> str:
"""生成模式描述"""
parts = []
if analysis.detected_patterns:
parts.append(f"包含模式: {', '.join(analysis.detected_patterns[:3])}")
if analysis.style_tags:
parts.append(f"风格标签: {', '.join(analysis.style_tags[:3])}")
parts.append(f"字数: {analysis.word_count},场景: {analysis.scene_count}")
if analysis.hot_spots:
parts.append(f"检测到 {len(analysis.hot_spots)} 个爽点")
return "; ".join(parts)
def save_learned_pattern(
self,
pattern: LearnedPattern,
layer: str = "project"
) -> bool:
"""保存学习到的模式"""
try:
if layer == "project":
return self._save_to_project_db(pattern)
else:
return self._save_to_system(pattern)
except Exception as e:
print(f"Error saving pattern: {e}")
return False
def _init_project_learned_db(self) -> Path:
"""初始化项目学习库"""
db_dir = self.project_root / ".noma" / "rag"
db_dir.mkdir(parents=True, exist_ok=True)
db_path = db_dir / "learned.db"
conn = sqlite3.connect(str(db_path))
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS learned_patterns (
pattern_id TEXT PRIMARY KEY,
pattern_type TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
tension_curve TEXT,
catharsis_model TEXT,
structure TEXT,
hot_spots TEXT,
style_tags TEXT,
source_project TEXT,
source_chapter INTEGER,
learned_at TEXT,
usage_count INTEGER DEFAULT 0,
metadata TEXT
)
""")
conn.commit()
conn.close()
return db_path
def _save_to_project_db(self, pattern: LearnedPattern) -> bool:
"""保存到项目数据库"""
db_path = self._init_project_learned_db()
conn = sqlite3.connect(str(db_path))
cursor = conn.cursor()
cursor.execute("""
INSERT OR REPLACE INTO learned_patterns
(pattern_id, pattern_type, title, description, tension_curve,
catharsis_model, structure, hot_spots, style_tags,
source_project, source_chapter, learned_at, usage_count, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
pattern.pattern_id,
pattern.pattern_type,
pattern.title,
pattern.description,
json.dumps(pattern.tension_curve),
pattern.catharsis_model,
json.dumps(pattern.structure),
json.dumps(pattern.hot_spots),
json.dumps(pattern.style_tags),
pattern.source_project,
pattern.source_chapter,
pattern.learned_at,
pattern.usage_count,
json.dumps(pattern.metadata)
))
conn.commit()
conn.close()
return True
def _save_to_system(self, pattern: LearnedPattern) -> bool:
"""保存到系统共享库"""
# 需要系统根目录
system_root = self._resolve_system_root()
learned_dir = system_root / "rag" / "learned"
learned_dir.mkdir(parents=True, exist_ok=True)
pattern_file = learned_dir / f"{pattern.pattern_id}.json"
pattern_data = asdict(pattern)
pattern_file.write_text(
json.dumps(pattern_data, ensure_ascii=False, indent=2),
encoding="utf-8"
)
return True
def _resolve_system_root(self) -> Path:
"""解析系统根目录"""
# 与 project_root 同级的 .noma
sibling = self.project_root.parent / ".noma"
if sibling.exists():
return sibling
return self.project_root / ".noma"
# ==================== CLI 接口 ====================
def main():
import argparse
import sys
if sys.platform == "win32":
enable_windows_utf8_stdio()
parser = argparse.ArgumentParser(
description="Chapter Analyzer - 章节自动解构与模式提取"
)
parser.add_argument("--project-root", type=str, default=".",
help="项目根目录")
parser.add_argument("--chapter-file", type=str, required=True,
help="章节文件路径")
parser.add_argument("--chapter-num", type=int,
help="章节号(可选,从文件名推断)")
parser.add_argument("--learn", action="store_true",
help="学习并存储模式")
parser.add_argument("--learn-layer", choices=["project", "system"],
default="project",
help="学习成果存储层级")
parser.add_argument("--output", type=str,
help="输出文件(JSON格式)")
args = parser.parse_args()
project_root = Path(args.project_root).resolve()
chapter_file = project_root / args.chapter_file
if not chapter_file.exists():
print(f"Error: Chapter file not found: {chapter_file}")
sys.exit(1)
# 分析章节
analyzer = ChapterAnalyzer(project_root)
result = analyzer.analyze_chapter(chapter_file, args.chapter_num)
# 输出结果
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(
json.dumps(asdict(result), ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"Analysis saved to: {output_path}")
else:
# 打印摘要
print(f"\n{'='*60}")
print(f"章节分析报告: 第{result.chapter}章 - {result.title}")
print(f"{'='*60}")
print(f"\n基本信息:")
print(f" 字数: {result.word_count}")
print(f" 场景数: {result.scene_count}")
print(f" 人物数: {result.character_count}")
print(f" 人物列表: {', '.join(result.characters[:5]) or ''}")
print(f"\n张力分析:")
print(f" 平均张力: {result.tension_avg:.2f}")
print(f" 峰值张力: {result.tension_peak:.2f} (位置: {result.tension_peak_position})")
print(f"\n爽点分析:")
print(f" 爽点数量: {len(result.hot_spots)}")
print(f" 主导模型: {result.catharsis_model}")
if result.hot_spots:
print(f" 主要爽点:")
for hs in result.hot_spots[:3]:
print(f" - [{hs.position}] {hs.description} ({hs.intensity:.2f})")
print(f"\n结构分析:")
print(f" 铺垫: {result.structure.setup}")
print(f" 压抑: {result.structure.suppress}")
print(f" 释放: {result.structure.release}")
print(f" 反转: {result.structure.twist}")
print(f"\n检测到的模式:")
for p in result.detected_patterns:
print(f" - {p}")
print(f"\n风格标签:")
for t in result.style_tags:
print(f" - {t}")
if result.suggestions:
print(f"\n改进建议:")
for s in result.suggestions:
print(f" - {s}")
# 学习模式
if args.learn:
pattern = analyzer.learn_pattern(result, args.learn_layer)
success = analyzer.save_learned_pattern(pattern, args.learn_layer)
if success:
print(f"\n✓ 模式已学习并存储到 {args.learn_layer}")
print(f" Pattern ID: {pattern.pattern_id}")
else:
print(f"\n✗ 模式存储失败")
sys.exit(1)
if __name__ == "__main__":
main()