|
|
@@ -24,7 +24,7 @@ from script.data_processing.path_config import PathConfig
|
|
|
|
|
|
# ============ 相似度阈值配置 ============
|
|
|
SIMILARITY_THRESHOLD_SAME = 0.8 # >= 此值为"相同"
|
|
|
-SIMILARITY_THRESHOLD_SIMILAR = 0.6 # >= 此值为"相似",< SAME阈值
|
|
|
+SIMILARITY_THRESHOLD_SIMILAR = 0.5 # >= 此值为"相似",< SAME阈值
|
|
|
# < SIMILAR阈值 为"无关"
|
|
|
|
|
|
# 相似度对应的颜色
|
|
|
@@ -209,68 +209,25 @@ def generate_post_detail_html(post_data: Dict, post_idx: int) -> str:
|
|
|
return html
|
|
|
|
|
|
|
|
|
-def generate_inspiration_detail_html(inspiration_point: Dict, feature_status_map: Dict[str, str] = None, point_type: str = "灵感点") -> str:
|
|
|
- """生成点详情HTML
|
|
|
+def generate_inspiration_detail_html(point: Dict, point_type: str = "灵感点") -> str:
|
|
|
+ """生成点详情HTML(简化版,适配新结构)
|
|
|
|
|
|
Args:
|
|
|
- inspiration_point: 点数据
|
|
|
- feature_status_map: 特征名称到状态的映射 {特征名称: "相同"|"相似"|"无关"}
|
|
|
+ point: 点数据(包含匹配人设结果)
|
|
|
point_type: 点类型(灵感点/关键点/目的点)
|
|
|
"""
|
|
|
- name = inspiration_point.get("名称", "")
|
|
|
- desc = inspiration_point.get("描述", "")
|
|
|
- features = inspiration_point.get("特征列表", [])
|
|
|
+ name = point.get("名称", "")
|
|
|
+ desc = point.get("描述", "")
|
|
|
+ match_results = point.get("匹配人设结果", [])
|
|
|
|
|
|
- if feature_status_map is None:
|
|
|
- feature_status_map = {}
|
|
|
+ # 根据最高相似度确定结论
|
|
|
+ max_similarity = 0.0
|
|
|
+ if match_results:
|
|
|
+ max_similarity = max(m.get("相似度", 0) for m in match_results)
|
|
|
|
|
|
- # 计算该灵感点的整体结论
|
|
|
- feature_statuses = []
|
|
|
- features_html_list = []
|
|
|
- for f in features:
|
|
|
- feature_name = f if isinstance(f, str) else f.get("特征名称", "")
|
|
|
- weight = f.get("权重", 1.0) if isinstance(f, dict) else 1.0
|
|
|
-
|
|
|
- # 获取该特征的状态
|
|
|
- status = feature_status_map.get(feature_name, "无关")
|
|
|
- feature_statuses.append(status)
|
|
|
-
|
|
|
- if status == "相同":
|
|
|
- status_class = "feature-same"
|
|
|
- status_label = "相同"
|
|
|
- elif status == "相似":
|
|
|
- status_class = "feature-similar"
|
|
|
- status_label = "相似"
|
|
|
- else:
|
|
|
- status_class = "feature-unrelated"
|
|
|
- status_label = "无关"
|
|
|
-
|
|
|
- features_html_list.append(
|
|
|
- f'<span class="feature-tag {status_class}">'
|
|
|
- f'<span class="feature-status-label">{status_label}</span> '
|
|
|
- f'{html_module.escape(feature_name)}'
|
|
|
- f'</span>'
|
|
|
- )
|
|
|
-
|
|
|
- features_html = "".join(features_html_list)
|
|
|
-
|
|
|
- # 计算灵感点结论
|
|
|
- has_same = "相同" in feature_statuses
|
|
|
- has_similar = "相似" in feature_statuses
|
|
|
- has_unrelated = "无关" in feature_statuses
|
|
|
-
|
|
|
- if not has_unrelated:
|
|
|
- # 没有无关的 -> 找到
|
|
|
- insp_conclusion = "找到"
|
|
|
- insp_conclusion_class = "insp-conclusion-found"
|
|
|
- elif has_same or has_similar:
|
|
|
- # 有相同或相似,但也有无关 -> 部分找到
|
|
|
- insp_conclusion = "部分找到"
|
|
|
- insp_conclusion_class = "insp-conclusion-partial"
|
|
|
- else:
|
|
|
- # 都是无关 -> 都找不到
|
|
|
- insp_conclusion = "都找不到"
|
|
|
- insp_conclusion_class = "insp-conclusion-not-found"
|
|
|
+ status_label, status_color, status_suffix = get_similarity_status(max_similarity)
|
|
|
+ insp_conclusion = status_label
|
|
|
+ insp_conclusion_class = f"insp-conclusion-{status_suffix}"
|
|
|
|
|
|
# 根据点类型设置图标
|
|
|
point_icons = {
|
|
|
@@ -291,9 +248,9 @@ def generate_inspiration_detail_html(inspiration_point: Dict, feature_status_map
|
|
|
<div class="desc-label">描述:</div>
|
|
|
<div class="desc-text">{html_module.escape(desc)}</div>
|
|
|
</div>
|
|
|
- <div class="inspiration-features">
|
|
|
- <div class="features-label">特征列表:</div>
|
|
|
- <div class="features-tags">{features_html}</div>
|
|
|
+ <div class="inspiration-stats">
|
|
|
+ <div class="stats-label">最高相似度: <span class="similarity-value">{max_similarity:.2f}</span></div>
|
|
|
+ <div class="stats-label">匹配人设数: <span class="match-count">{len(match_results)}</span></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
'''
|
|
|
@@ -333,34 +290,32 @@ def load_feature_source_mapping(config: PathConfig) -> Dict:
|
|
|
return {}
|
|
|
|
|
|
|
|
|
-def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, insp_idx: int, feature_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, current_point_type: str = "灵感点") -> str:
|
|
|
+def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, point_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, point_type: str = "灵感点") -> str:
|
|
|
"""生成单个匹配项的HTML
|
|
|
|
|
|
Args:
|
|
|
match: 单个匹配数据
|
|
|
match_idx: 匹配项索引
|
|
|
post_idx: 帖子索引
|
|
|
- insp_idx: 灵感点索引
|
|
|
- feature_idx: 特征索引
|
|
|
+ point_idx: 点索引
|
|
|
category_mapping: 特征分类映射
|
|
|
source_mapping: 特征来源映射
|
|
|
- current_point_type: 当前点的类型(灵感点/关键点/目的点)
|
|
|
+ point_type: 当前点的类型(灵感点/关键点/目的点)
|
|
|
"""
|
|
|
persona_name = match.get("人设特征名称", "")
|
|
|
feature_type = match.get("特征类型", "")
|
|
|
feature_categories = match.get("特征分类", [])
|
|
|
persona_level = match.get("人设特征层级", "")
|
|
|
- match_result = match.get("匹配结果", {})
|
|
|
- similarity = match_result.get("相似度", 0.0)
|
|
|
- explanation = match_result.get("说明", "")
|
|
|
+ similarity = match.get("相似度", 0.0)
|
|
|
+ explanation = match.get("说明", "")
|
|
|
|
|
|
# 根据相似度确定颜色和标签
|
|
|
label, color, _ = get_similarity_status(similarity)
|
|
|
|
|
|
- match_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-match-{match_idx}"
|
|
|
+ match_id = f"post-{post_idx}-{point_type}-{point_idx}-match-{match_idx}"
|
|
|
|
|
|
# 判断是否同层级匹配
|
|
|
- is_same_level = (persona_level == current_point_type)
|
|
|
+ is_same_level = (persona_level == point_type)
|
|
|
same_level_class = "match-same-level" if is_same_level else ""
|
|
|
|
|
|
# 生成合并的层级-类型标签
|
|
|
@@ -454,43 +409,36 @@ def generate_single_match_html(match: Dict, match_idx: int, post_idx: int, insp_
|
|
|
return html
|
|
|
|
|
|
|
|
|
-def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_idx: int, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, current_point_type: str = "灵感点", feature_number: int = 1) -> str:
|
|
|
- """生成可折叠的匹配结果HTML
|
|
|
+def generate_match_results_html(point: Dict, point_idx: int, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None, point_type: str = "灵感点") -> str:
|
|
|
+ """生成可折叠的匹配结果HTML(适配新结构)
|
|
|
|
|
|
Args:
|
|
|
- how_steps: how步骤列表
|
|
|
- feature_idx: 特征索引
|
|
|
- insp_idx: 灵感点索引
|
|
|
+ point: 点数据(包含匹配人设结果)
|
|
|
+ point_idx: 点索引
|
|
|
post_idx: 帖子索引
|
|
|
category_mapping: 特征分类映射
|
|
|
source_mapping: 特征来源映射
|
|
|
- current_point_type: 当前点的类型(灵感点/关键点/目的点)
|
|
|
- feature_number: 特征序号(从1开始)
|
|
|
+ point_type: 点类型(灵感点/关键点/目的点)
|
|
|
"""
|
|
|
- if not how_steps or len(how_steps) == 0:
|
|
|
- return ""
|
|
|
-
|
|
|
- step = how_steps[0]
|
|
|
- features = step.get("特征列表", [])
|
|
|
+ point_name = point.get("名称", "")
|
|
|
+ match_results = point.get("匹配人设结果", [])
|
|
|
|
|
|
- if feature_idx >= len(features):
|
|
|
+ if not match_results:
|
|
|
return ""
|
|
|
|
|
|
- feature_data = features[feature_idx]
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- feature_weight = feature_data.get("权重", 1.0)
|
|
|
- match_results = feature_data.get("匹配结果", [])
|
|
|
-
|
|
|
if category_mapping is None:
|
|
|
category_mapping = {}
|
|
|
|
|
|
- # 按相似度排序
|
|
|
- sorted_matches = sorted(match_results, key=lambda x: x.get("匹配结果", {}).get("相似度", 0), reverse=True)
|
|
|
+ # 用于显示的序号(点索引+1)
|
|
|
+ feature_number = point_idx + 1
|
|
|
+
|
|
|
+ # 匹配结果已经按相似度降序排列(在match脚本中处理过)
|
|
|
+ sorted_matches = match_results
|
|
|
|
|
|
# 找出最高相似度,确定状态
|
|
|
max_similarity = 0.0
|
|
|
if match_results:
|
|
|
- max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
|
|
|
+ max_similarity = max(match.get("相似度", 0) for match in match_results)
|
|
|
|
|
|
# 根据最高相似度确定状态
|
|
|
status, _, status_suffix = get_similarity_status(max_similarity)
|
|
|
@@ -504,7 +452,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
unrelated_label = f"无关 (<{SIMILARITY_THRESHOLD_SIMILAR})"
|
|
|
similarity_ranges = {same_label: 0, similar_label: 0, unrelated_label: 0}
|
|
|
for match in match_results:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
status_label, _, _ = get_similarity_status(similarity)
|
|
|
if status_label == "相同":
|
|
|
similarity_ranges[same_label] += 1
|
|
|
@@ -553,14 +501,14 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
continue
|
|
|
|
|
|
# 生成该层级的折叠区域
|
|
|
- level_section_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}"
|
|
|
+ level_section_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}"
|
|
|
|
|
|
# 找出该层级的最高分匹配
|
|
|
all_level_matches = level_data["标签"] + level_data["分类"]
|
|
|
top_match = None
|
|
|
max_similarity = 0
|
|
|
for _, match in all_level_matches:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
if similarity > max_similarity:
|
|
|
max_similarity = similarity
|
|
|
top_match = match
|
|
|
@@ -584,7 +532,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
# 计算该层级的相似度分布
|
|
|
level_stats = {"相同": 0, "相似": 0, "无关": 0}
|
|
|
for _, match in all_level_matches:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
stat_label, _, _ = get_similarity_status(similarity)
|
|
|
level_stats[stat_label] += 1
|
|
|
|
|
|
@@ -613,21 +561,21 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
# 该层级下的标签分组
|
|
|
subgroup_index = 1
|
|
|
if level_data["标签"]:
|
|
|
- group_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}-label"
|
|
|
+ group_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}-label"
|
|
|
group_matches_html = ""
|
|
|
|
|
|
# 找出标签中的最高分
|
|
|
tag_top_match = None
|
|
|
tag_max_similarity = 0
|
|
|
for i, match in level_data["标签"]:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
if similarity > tag_max_similarity:
|
|
|
tag_max_similarity = similarity
|
|
|
tag_top_match = match
|
|
|
|
|
|
match_html = generate_single_match_html(
|
|
|
- match, i, post_idx, insp_idx, feature_idx,
|
|
|
- category_mapping, source_mapping, current_point_type
|
|
|
+ match, i, post_idx, point_idx,
|
|
|
+ category_mapping, source_mapping, point_type
|
|
|
)
|
|
|
group_matches_html += match_html
|
|
|
|
|
|
@@ -669,21 +617,21 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
|
|
|
# 该层级下的分类分组
|
|
|
if level_data["分类"]:
|
|
|
- group_id = f"post-{post_idx}-{current_point_type}-{insp_idx}-feat-{feature_idx}-level-{level_name}-category"
|
|
|
+ group_id = f"post-{post_idx}-{point_type}-{point_idx}-level-{level_name}-category"
|
|
|
group_matches_html = ""
|
|
|
|
|
|
# 找出分类中的最高分
|
|
|
cat_top_match = None
|
|
|
cat_max_similarity = 0
|
|
|
for i, match in level_data["分类"]:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
if similarity > cat_max_similarity:
|
|
|
cat_max_similarity = similarity
|
|
|
cat_top_match = match
|
|
|
|
|
|
match_html = generate_single_match_html(
|
|
|
- match, i, post_idx, insp_idx, feature_idx,
|
|
|
- category_mapping, source_mapping, current_point_type
|
|
|
+ match, i, post_idx, point_idx,
|
|
|
+ category_mapping, source_mapping, point_type
|
|
|
)
|
|
|
group_matches_html += match_html
|
|
|
|
|
|
@@ -730,13 +678,13 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
|
|
|
level_index += 1
|
|
|
|
|
|
- section_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-section"
|
|
|
+ section_id = f"post-{post_idx}-{point_type}-{point_idx}-section"
|
|
|
|
|
|
# 找出所有匹配中的最高分
|
|
|
overall_top_match = None
|
|
|
overall_max_similarity = 0
|
|
|
for match in match_results:
|
|
|
- similarity = match.get("匹配结果", {}).get("相似度", 0)
|
|
|
+ similarity = match.get("相似度", 0)
|
|
|
if similarity > overall_max_similarity:
|
|
|
overall_max_similarity = similarity
|
|
|
overall_top_match = match
|
|
|
@@ -763,7 +711,7 @@ def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_id
|
|
|
<div class="match-section-header collapsible-header" onclick="toggleFeatureSection(event, '{section_id}')">
|
|
|
<div class="header-left">
|
|
|
<span class="expand-icon" id="{section_id}-icon">▼</span>
|
|
|
- <h4>{feature_number}. 匹配结果: {html_module.escape(feature_name)}</h4>
|
|
|
+ <h4>{feature_number}. 匹配结果: {html_module.escape(point_name)}</h4>
|
|
|
{found_status_html}
|
|
|
</div>
|
|
|
<div class="match-stats">{stats_html}</div>
|
|
|
@@ -786,7 +734,7 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
|
|
|
feature_status_map: 特征名称到状态的映射 {特征名称: "相同"|"相似"|"无关"}
|
|
|
overall_conclusion: 整体结论
|
|
|
"""
|
|
|
- how_result = post_data.get("how解构结果", {})
|
|
|
+ how_result = post_data.get("解构结果", {})
|
|
|
|
|
|
if feature_status_map is None:
|
|
|
feature_status_map = {}
|
|
|
@@ -816,68 +764,29 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
|
|
|
name = point.get("名称", f"{point_name} {point_idx + 1}")
|
|
|
name_short = name[:18] + "..." if len(name) > 18 else name
|
|
|
|
|
|
- # 计算该点的整体状态
|
|
|
- how_steps = point.get("how步骤列表", [])
|
|
|
- point_status = "无关"
|
|
|
- has_same = False
|
|
|
- has_similar = False
|
|
|
-
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feature_data in features:
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- status = feature_status_map.get(feature_name, "无关")
|
|
|
- if status == "相同":
|
|
|
- has_same = True
|
|
|
- break
|
|
|
- elif status == "相似":
|
|
|
- has_similar = True
|
|
|
-
|
|
|
- if has_same:
|
|
|
- point_status = "找到"
|
|
|
- point_status_class = "toc-point-found"
|
|
|
- elif has_similar:
|
|
|
- point_status = "部分找到"
|
|
|
- point_status_class = "toc-point-partial"
|
|
|
- else:
|
|
|
- point_status = "都找不到"
|
|
|
- point_status_class = "toc-point-notfound"
|
|
|
- else:
|
|
|
- point_status_class = "toc-point-notfound"
|
|
|
-
|
|
|
- toc_items.append(f'<div class="toc-item toc-level-2 {point_status_class}" onclick="scrollToSection(\'post-{post_idx}-point-{list_key}-{point_idx}\')"><span class="toc-badge {badge_class}">{point_name}</span> {html_module.escape(name_short)} <span class="toc-point-status">[{point_status}]</span></div>')
|
|
|
-
|
|
|
- # 特征列表
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feat_idx, feature_data in enumerate(features):
|
|
|
- feature_name = feature_data.get("特征名称", f"特征 {feat_idx + 1}")
|
|
|
-
|
|
|
- # 获取状态
|
|
|
- status = feature_status_map.get(feature_name, "无关")
|
|
|
- if status == "相同":
|
|
|
- status_class = "toc-feature-same"
|
|
|
- status_label = "相同"
|
|
|
- elif status == "相似":
|
|
|
- status_class = "toc-feature-similar"
|
|
|
- status_label = "相似"
|
|
|
- else:
|
|
|
- status_class = "toc-feature-unrelated"
|
|
|
- status_label = "无关"
|
|
|
-
|
|
|
- toc_items.append(f'<div class="toc-item toc-level-3 {status_class}" onclick="scrollToSection(\'post-{post_idx}-feat-{point_idx}-{feat_idx}\')"><span class="toc-badge toc-badge-feature">特征</span> {html_module.escape(feature_name)} <span class="toc-feature-status">[{status_label}]</span></div>')
|
|
|
+ # 计算该点的整体状态(根据匹配人设结果的最高相似度)
|
|
|
+ match_results = point.get("匹配人设结果", [])
|
|
|
+ max_similarity = 0.0
|
|
|
+ if match_results:
|
|
|
+ max_similarity = max(m.get("相似度", 0) for m in match_results)
|
|
|
+
|
|
|
+ status_label, _, status_suffix = get_similarity_status(max_similarity)
|
|
|
+ point_status = status_label
|
|
|
+ point_status_class = f"toc-point-{status_suffix}"
|
|
|
+
|
|
|
+ toc_items.append(f'<div class="toc-item toc-level-2 {point_status_class}" onclick="scrollToSection(\'post-{post_idx}-point-{list_key}-{point_idx}\')"><span class="toc-badge {badge_class}">{point_name}</span> {html_module.escape(name_short)} <span class="toc-point-status">[{point_status}]</span> <span class="toc-similarity">({max_similarity:.2f})</span></div>')
|
|
|
|
|
|
# 整体结论HTML
|
|
|
conclusion_html = ""
|
|
|
if overall_conclusion:
|
|
|
- if overall_conclusion == "找到":
|
|
|
- conclusion_class = "conclusion-found"
|
|
|
+ if overall_conclusion == "相同":
|
|
|
+ conclusion_class = "conclusion-same"
|
|
|
conclusion_icon = "✓"
|
|
|
- elif overall_conclusion == "部分找到":
|
|
|
- conclusion_class = "conclusion-partial"
|
|
|
+ elif overall_conclusion == "相似":
|
|
|
+ conclusion_class = "conclusion-similar"
|
|
|
conclusion_icon = "~"
|
|
|
- else: # 都找不到
|
|
|
- conclusion_class = "conclusion-not-found"
|
|
|
+ else: # 无关
|
|
|
+ conclusion_class = "conclusion-unrelated"
|
|
|
conclusion_icon = "✗"
|
|
|
|
|
|
conclusion_html = f'''
|
|
|
@@ -899,81 +808,49 @@ def generate_toc_html(post_data: Dict, post_idx: int, feature_status_map: Dict[s
|
|
|
|
|
|
|
|
|
def generate_post_content_html(post_data: Dict, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None) -> str:
|
|
|
- """生成单个帖子的完整内容HTML"""
|
|
|
- # 2. 灵感点详情和匹配结果
|
|
|
- how_result = post_data.get("how解构结果", {})
|
|
|
- inspiration_list = how_result.get("灵感点列表", [])
|
|
|
-
|
|
|
- # 先计算所有特征的状态(基于最高相似度)
|
|
|
- feature_status_map = {} # {特征名称: "相同"|"相似"|"无关"}
|
|
|
- for inspiration_point in inspiration_list:
|
|
|
- how_steps = inspiration_point.get("how步骤列表", [])
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feature_data in features:
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- match_results = feature_data.get("匹配结果", [])
|
|
|
-
|
|
|
- # 找出最高相似度
|
|
|
- max_similarity = 0.0
|
|
|
- if match_results:
|
|
|
- max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
|
|
|
-
|
|
|
- # 根据最高相似度确定状态
|
|
|
- status_label, _, _ = get_similarity_status(max_similarity)
|
|
|
- feature_status_map[feature_name] = status_label
|
|
|
+ """生成单个帖子的完整内容HTML(适配新结构:点直接包含匹配人设结果)"""
|
|
|
+ how_result = post_data.get("解构结果", {})
|
|
|
|
|
|
# 1. 帖子详情
|
|
|
post_detail_html = generate_post_detail_html(post_data, post_idx)
|
|
|
|
|
|
- # 生成所有灵感点的详情HTML(传入状态映射)
|
|
|
- inspirations_detail_html = ""
|
|
|
- for insp_idx, inspiration_point in enumerate(inspiration_list):
|
|
|
- inspiration_detail = generate_inspiration_detail_html(inspiration_point, feature_status_map)
|
|
|
- inspirations_detail_html += f'''
|
|
|
- <div id="post-{post_idx}-insp-{insp_idx}" class="inspiration-detail-item content-section">
|
|
|
- {inspiration_detail}
|
|
|
- </div>
|
|
|
- '''
|
|
|
+ # 2. 生成所有点的详情HTML(灵感点、关键点、目的点)
|
|
|
+ all_points_detail_html = ""
|
|
|
+ point_types = [
|
|
|
+ ("灵感点列表", "灵感点"),
|
|
|
+ ("关键点列表", "关键点"),
|
|
|
+ ("目的点列表", "目的点")
|
|
|
+ ]
|
|
|
+
|
|
|
+ for list_key, point_type in point_types:
|
|
|
+ point_list = how_result.get(list_key, [])
|
|
|
+ for point_idx, point in enumerate(point_list):
|
|
|
+ point_detail = generate_inspiration_detail_html(point, point_type)
|
|
|
+ all_points_detail_html += f'''
|
|
|
+ <div id="post-{post_idx}-{point_type}-{point_idx}" class="inspiration-detail-item content-section">
|
|
|
+ {point_detail}
|
|
|
+ </div>
|
|
|
+ '''
|
|
|
|
|
|
- # 生成所有匹配结果HTML,按照how步骤分组
|
|
|
+ # 3. 生成所有匹配结果HTML
|
|
|
all_matches_html = ""
|
|
|
- for insp_idx, inspiration_point in enumerate(inspiration_list):
|
|
|
- inspiration_name = inspiration_point.get("名称", f"灵感点 {insp_idx + 1}")
|
|
|
- how_steps = inspiration_point.get("how步骤列表", [])
|
|
|
-
|
|
|
- if how_steps:
|
|
|
- # 为每个灵感点创建一个区域
|
|
|
- for step_idx, step in enumerate(how_steps):
|
|
|
- step_name = step.get("步骤名称", f"步骤 {step_idx + 1}")
|
|
|
- features = step.get("特征列表", [])
|
|
|
-
|
|
|
- # 生成该步骤下所有特征的匹配结果
|
|
|
- features_html = ""
|
|
|
- for feat_idx, feature_data in enumerate(features):
|
|
|
- match_html = generate_match_results_html([step], feat_idx, insp_idx, post_idx, category_mapping, source_mapping, "灵感点", feat_idx + 1)
|
|
|
- features_html += f'<div id="post-{post_idx}-feat-{insp_idx}-{feat_idx}" class="feature-match-wrapper">{match_html}</div>'
|
|
|
-
|
|
|
- # 生成步骤区域(可折叠)
|
|
|
- step_section_id = f"post-{post_idx}-step-{insp_idx}-{step_idx}"
|
|
|
+ for list_key, point_type in point_types:
|
|
|
+ point_list = how_result.get(list_key, [])
|
|
|
+ for point_idx, point in enumerate(point_list):
|
|
|
+ match_html = generate_match_results_html(
|
|
|
+ point, point_idx, post_idx,
|
|
|
+ category_mapping, source_mapping, point_type
|
|
|
+ )
|
|
|
+ if match_html:
|
|
|
all_matches_html += f'''
|
|
|
- <div class="step-section">
|
|
|
- <div class="step-header collapsible-header" onclick="toggleStepSection('{step_section_id}')">
|
|
|
- <div class="header-left">
|
|
|
- <span class="expand-icon" id="{step_section_id}-icon">▼</span>
|
|
|
- <h3 class="step-name">{html_module.escape(step_name)}</h3>
|
|
|
- </div>
|
|
|
- <span class="step-inspiration-name">来自: {html_module.escape(inspiration_name)}</span>
|
|
|
- </div>
|
|
|
- <div class="step-features-list" id="{step_section_id}-content">
|
|
|
- {features_html}
|
|
|
- </div>
|
|
|
+ <div id="post-{post_idx}-match-{point_type}-{point_idx}" class="point-match-wrapper">
|
|
|
+ {match_html}
|
|
|
</div>
|
|
|
'''
|
|
|
|
|
|
html = f'''
|
|
|
<div class="post-content-wrapper">
|
|
|
- <!-- 第一个框:左右分栏(帖子详情 + 灵感点详情) -->
|
|
|
+ <!-- 第一个框:左右分栏(帖子详情 + 点详情) -->
|
|
|
<div class="top-section-box">
|
|
|
<div class="two-column-layout">
|
|
|
<div class="left-column">
|
|
|
@@ -983,7 +860,7 @@ def generate_post_content_html(post_data: Dict, post_idx: int, category_mapping:
|
|
|
</div>
|
|
|
<div class="right-column">
|
|
|
<div class="inspirations-detail-wrapper">
|
|
|
- {inspirations_detail_html}
|
|
|
+ {all_points_detail_html}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -1029,25 +906,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
<div class="toc-children hidden" id="toc-post-{post_idx}-children">
|
|
|
''')
|
|
|
|
|
|
- # 获取特征状态映射
|
|
|
- feature_status_map = {}
|
|
|
- how_result = post.get("how解构结果", {})
|
|
|
-
|
|
|
- # 先收集所有特征状态
|
|
|
- for point_list_key in ["灵感点列表", "目的点列表", "关键点列表"]:
|
|
|
- point_list = how_result.get(point_list_key, [])
|
|
|
- for point in point_list:
|
|
|
- how_steps = point.get("how步骤列表", [])
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feature_data in features:
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- match_results = feature_data.get("匹配结果", [])
|
|
|
- max_similarity = 0.0
|
|
|
- if match_results:
|
|
|
- max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
|
|
|
- status_label, _, _ = get_similarity_status(max_similarity)
|
|
|
- feature_status_map[feature_name] = status_label
|
|
|
+ how_result = post.get("解构结果", {})
|
|
|
|
|
|
# 生成点类型目录
|
|
|
point_types = [
|
|
|
@@ -1061,7 +920,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
if not point_list:
|
|
|
continue
|
|
|
|
|
|
- # 点类型分组标题(可折叠),去掉"列表"两字
|
|
|
+ # 点类型分组标题(可折叠)
|
|
|
group_id = f"post-{post_idx}-{list_key}"
|
|
|
all_toc_items.append(f'''
|
|
|
<div class="toc-item toc-level-1 toc-group-header collapsed" onclick="toggleTocGroup(event, '{group_id}')">
|
|
|
@@ -1077,87 +936,29 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
name = point.get("名称", f"{point_name} {point_idx + 1}")
|
|
|
name_short = name[:18] + "..." if len(name) > 18 else name
|
|
|
|
|
|
- # 计算该点的状态
|
|
|
- how_steps = point.get("how步骤列表", [])
|
|
|
- point_status = "无关"
|
|
|
- has_same = False
|
|
|
- has_similar = False
|
|
|
-
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- has_unrelated = False
|
|
|
- for feature_data in features:
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- status = feature_status_map.get(feature_name, "无关")
|
|
|
- if status == "相同":
|
|
|
- has_same = True
|
|
|
- elif status == "相似":
|
|
|
- has_similar = True
|
|
|
- elif status == "无关":
|
|
|
- has_unrelated = True
|
|
|
-
|
|
|
- # 统一使用与详情页相同的逻辑
|
|
|
- if not has_unrelated:
|
|
|
- # 没有无关的 -> 找到
|
|
|
- point_status = "找到"
|
|
|
- point_status_class = "toc-point-found"
|
|
|
- elif has_same or has_similar:
|
|
|
- # 有相同或相似,但也有无关 -> 部分找到
|
|
|
- point_status = "部分找到"
|
|
|
- point_status_class = "toc-point-partial"
|
|
|
- else:
|
|
|
- # 都是无关 -> 都找不到
|
|
|
- point_status = "都找不到"
|
|
|
- point_status_class = "toc-point-notfound"
|
|
|
- else:
|
|
|
- point_status = "都找不到"
|
|
|
- point_status_class = "toc-point-notfound"
|
|
|
-
|
|
|
- # 点项(可折叠,点击切换到该点的视图)
|
|
|
- # 确保ID唯一:包含list_key以区分不同类型的点
|
|
|
+ # 计算该点的状态(基于匹配人设结果的最高相似度)
|
|
|
+ match_results = point.get("匹配人设结果", [])
|
|
|
+ max_similarity = 0.0
|
|
|
+ if match_results:
|
|
|
+ max_similarity = max(m.get("相似度", 0) for m in match_results)
|
|
|
+
|
|
|
+ status_label, _, status_suffix = get_similarity_status(max_similarity)
|
|
|
+ point_status = status_label
|
|
|
+ point_status_class = f"toc-point-{status_suffix}"
|
|
|
+
|
|
|
+ # 点项(点击切换到该点的视图)
|
|
|
point_item_id = f"post-{post_idx}-{list_key}-point-{point_idx}"
|
|
|
point_view_id = f"view-post-{post_idx}-point-{list_key}-{point_idx}"
|
|
|
all_toc_items.append(f'''
|
|
|
- <div class="toc-item toc-level-2 {point_status_class} collapsed" onclick="toggleTocPoint(event, '{point_item_id}', '{point_view_id}')">
|
|
|
- <span class="toc-expand-icon">▼</span>
|
|
|
+ <div class="toc-item toc-level-2 {point_status_class}" onclick="showPointView(event, '{point_view_id}')">
|
|
|
<div class="toc-item-content">
|
|
|
[{point_name}] {html_module.escape(name_short)}
|
|
|
<span class="toc-point-status">[{point_status}]</span>
|
|
|
+ <span class="toc-similarity-score">{max_similarity:.2f}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="toc-children hidden" id="toc-{point_item_id}-children">
|
|
|
''')
|
|
|
|
|
|
- # 特征列表
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feat_idx, feature_data in enumerate(features):
|
|
|
- feature_name = feature_data.get("特征名称", f"特征 {feat_idx + 1}")
|
|
|
- status = feature_status_map.get(feature_name, "无关")
|
|
|
-
|
|
|
- if status == "相同":
|
|
|
- status_class = "toc-feature-same"
|
|
|
- status_label = "相同"
|
|
|
- elif status == "相似":
|
|
|
- status_class = "toc-feature-similar"
|
|
|
- status_label = "相似"
|
|
|
- else:
|
|
|
- status_class = "toc-feature-unrelated"
|
|
|
- status_label = "无关"
|
|
|
-
|
|
|
- # 特征项(点击直接跳转,不展开子项)
|
|
|
- all_toc_items.append(f'''
|
|
|
- <div class="toc-item toc-level-3 {status_class}" onclick="clickFeature(event, '{point_view_id}', 'post-{post_idx}-feat-{point_idx}-{feat_idx}')">
|
|
|
- <div class="toc-item-content">
|
|
|
- [特征] {html_module.escape(feature_name)}
|
|
|
- <span class="toc-feature-status">[{status_label}]</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ''')
|
|
|
-
|
|
|
- # 关闭点项的children
|
|
|
- all_toc_items.append('</div>')
|
|
|
-
|
|
|
# 关闭点类型分组的children
|
|
|
all_toc_items.append('</div>')
|
|
|
|
|
|
@@ -1169,7 +970,7 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
|
|
|
for post_idx, post in enumerate(posts_data):
|
|
|
post_detail = post.get("帖子详情", {})
|
|
|
- how_result = post.get("how解构结果", {})
|
|
|
+ how_result = post.get("解构结果", {})
|
|
|
|
|
|
# 1. 生成"仅帖子详情"的视图
|
|
|
post_detail_html = generate_post_detail_html(post, post_idx)
|
|
|
@@ -1193,51 +994,14 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
point_type = point_list_key.replace("列表", "")
|
|
|
|
|
|
for point_idx, point in enumerate(point_list):
|
|
|
- # 计算该点的特征状态映射
|
|
|
- point_feature_status_map = {}
|
|
|
- how_steps = point.get("how步骤列表", [])
|
|
|
- if how_steps:
|
|
|
- features = how_steps[0].get("特征列表", [])
|
|
|
- for feature_data in features:
|
|
|
- feature_name = feature_data.get("特征名称", "")
|
|
|
- match_results = feature_data.get("匹配结果", [])
|
|
|
- max_similarity = 0.0
|
|
|
- if match_results:
|
|
|
- max_similarity = max(match.get("匹配结果", {}).get("相似度", 0) for match in match_results)
|
|
|
- status_label, _, _ = get_similarity_status(max_similarity)
|
|
|
- point_feature_status_map[feature_name] = status_label
|
|
|
-
|
|
|
- # 生成点的详情HTML,传入特征状态映射和点类型
|
|
|
- point_detail_html = generate_inspiration_detail_html(point, point_feature_status_map, point_type)
|
|
|
-
|
|
|
- # 生成该点的所有特征匹配结果
|
|
|
- point_name = point.get("名称", f"点 {point_idx + 1}")
|
|
|
- matches_html = ""
|
|
|
- if how_steps:
|
|
|
- for step_idx, step in enumerate(how_steps):
|
|
|
- step_name = step.get("步骤名称", f"步骤 {step_idx + 1}")
|
|
|
- features = step.get("特征列表", [])
|
|
|
-
|
|
|
- features_html = ""
|
|
|
- for feat_idx, feature_data in enumerate(features):
|
|
|
- match_html = generate_match_results_html([step], feat_idx, point_idx, post_idx, category_mapping, source_mapping, point_list_key.replace("列表", ""), feat_idx + 1)
|
|
|
- features_html += f'<div id="post-{post_idx}-feat-{point_idx}-{feat_idx}" class="feature-match-wrapper">{match_html}</div>'
|
|
|
-
|
|
|
- step_section_id = f"post-{post_idx}-step-{point_idx}-{step_idx}"
|
|
|
- matches_html += f'''
|
|
|
- <div class="step-section">
|
|
|
- <div class="step-header collapsible-header" onclick="toggleStepSection('{step_section_id}')">
|
|
|
- <div class="header-left">
|
|
|
- <span class="expand-icon" id="{step_section_id}-icon">▼</span>
|
|
|
- <h3 class="step-name">{html_module.escape(step_name)}</h3>
|
|
|
- </div>
|
|
|
- <span class="step-inspiration-name">来自: {html_module.escape(point_name)}</span>
|
|
|
- </div>
|
|
|
- <div class="step-features-list" id="{step_section_id}-content">
|
|
|
- {features_html}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- '''
|
|
|
+ # 生成点的详情HTML
|
|
|
+ point_detail_html = generate_inspiration_detail_html(point, point_type)
|
|
|
+
|
|
|
+ # 生成该点的匹配结果
|
|
|
+ matches_html = generate_match_results_html(
|
|
|
+ point, point_idx, post_idx,
|
|
|
+ category_mapping, source_mapping, point_type
|
|
|
+ )
|
|
|
|
|
|
# 组合成完整视图
|
|
|
point_view_id = f"view-post-{post_idx}-point-{point_list_key}-{point_idx}"
|
|
|
@@ -3019,6 +2783,20 @@ def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None
|
|
|
children.classList.toggle('hidden');
|
|
|
}}
|
|
|
|
|
|
+ // 直接显示点的内容视图(新结构:点没有子级,直接跳转)
|
|
|
+ function showPointView(event, viewId) {{
|
|
|
+ event.stopPropagation();
|
|
|
+
|
|
|
+ // 切换到该点的视图
|
|
|
+ showContentView(viewId, event.currentTarget);
|
|
|
+
|
|
|
+ // 滚动到右侧内容区域的顶部
|
|
|
+ var rightContent = document.querySelector('.right-content');
|
|
|
+ if (rightContent) {{
|
|
|
+ rightContent.scrollTo({{ top: 0, behavior: 'smooth' }});
|
|
|
+ }}
|
|
|
+ }}
|
|
|
+
|
|
|
// 折叠/展开点项(同时切换到该点的内容视图)
|
|
|
function toggleTocPoint(event, pointItemId, viewId) {{
|
|
|
event.stopPropagation();
|
|
|
@@ -3724,7 +3502,7 @@ def main():
|
|
|
print(f"\n压缩HTML...")
|
|
|
minified_html = minify_html(html_content)
|
|
|
|
|
|
- minified_file = output_file.parent / "当前帖子_how解构结果_可视化.min.html"
|
|
|
+ minified_file = output_file.parent / "当前帖子_解构结果_可视化.min.html"
|
|
|
print(f"保存压缩HTML到: {minified_file}")
|
|
|
with open(minified_file, "w", encoding="utf-8") as f:
|
|
|
f.write(minified_html)
|
|
|
@@ -3735,7 +3513,7 @@ def main():
|
|
|
# Gzip压缩
|
|
|
import gzip
|
|
|
print(f"\n生成Gzip压缩版本...")
|
|
|
- gzip_file = output_file.parent / "当前帖子_how解构结果_可视化.html.gz"
|
|
|
+ gzip_file = output_file.parent / "当前帖子_解构结果_可视化.html.gz"
|
|
|
with gzip.open(gzip_file, "wb") as f:
|
|
|
f.write(minified_html.encode('utf-8'))
|
|
|
|