刘立冬 hai 2 semanas
pai
achega
6b8e37092f
Modificáronse 1 ficheiros con 4911 adicións e 0 borrados
  1. 4911 0
      src/visualizers/deconstruction_visualizer.py

+ 4911 - 0
src/visualizers/deconstruction_visualizer.py

@@ -0,0 +1,4911 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+搜索评估和解构整合可视化工具
+在评估结果基础上,为完全匹配帖子增加解构和相似度展示
+"""
+
+import json
+import os
+from datetime import datetime
+from typing import List, Dict, Any
+
+
+def load_data(json_path: str) -> List[Dict[str, Any]]:
+    """加载JSON数据"""
+    with open(json_path, 'r', encoding='utf-8') as f:
+        return json.load(f)
+
+
+def load_deconstruction_data(json_path: str) -> Dict[str, Any]:
+    """加载解构数据"""
+    with open(json_path, 'r', encoding='utf-8') as f:
+        data = json.load(f)
+
+    # 创建note_id到解构数据的映射
+    mapping = {}
+    for result in data.get('results', []):
+        note_id = result.get('note_id')
+        if note_id:
+            mapping[note_id] = result
+
+    return mapping
+
+
+def load_similarity_data(json_path: str) -> Dict[str, Any]:
+    """加载相似度分析数据"""
+    with open(json_path, 'r', encoding='utf-8') as f:
+        data = json.load(f)
+
+    # 创建note_id到相似度数据的映射
+    mapping = {}
+    for result in data.get('results', []):
+        note_id = result.get('note_id')
+        if note_id:
+            mapping[note_id] = result
+
+    return mapping
+
+
+def load_persona_library(json_path: str) -> Dict[str, Any]:
+    """加载人设特征库"""
+    with open(json_path, 'r', encoding='utf-8') as f:
+        return json.load(f)
+
+
+def extract_all_features_from_how(how_json_path: str) -> Dict[str, Dict[str, Any]]:
+    """
+    从how解构结果中提取所有特征的完整信息(适配新数据结构)
+
+    新结构说明:
+    - 使用 '解构结果' 而不是 'how解构结果'
+    - 点的名称本身就是特征名,不再有特征列表
+    - 相似度从点的 '匹配人设结果' 获取
+
+    Returns:
+        Dict[特征名称, {
+            'similarity': float,    # 最高相似度
+            'weight': float,        # 权重(新结构默认1.0)
+            'dimension': str,       # 所属维度
+            'category': str         # 分类:'已搜索', '待搜索', '低相似度'
+        }]
+    """
+    with open(how_json_path, 'r', encoding='utf-8') as f:
+        how_data = json.load(f)
+
+    features = {}
+
+    for dimension_key in ['灵感点列表', '目的点列表', '关键点列表']:
+        # 适配新结构:使用 '解构结果' 而不是 'how解构结果'
+        dimension_list = how_data.get('解构结果', {}).get(dimension_key, [])
+        dimension_name = dimension_key.replace('列表', '')
+
+        for point in dimension_list:
+            # 适配新结构:点的名称本身就是特征名
+            feature_name = point.get('名称', '')
+            if not feature_name:
+                continue
+
+            # 新结构默认权重为1.0
+            weight = 1.0
+
+            # 适配新结构:从点的匹配人设结果获取最高相似度
+            max_similarity = 0
+            match_results = point.get('匹配人设结果', [])
+
+            for match in match_results:
+                similarity = match.get('相似度', 0)
+                max_similarity = max(max_similarity, similarity)
+
+            # 确定分类(使用0.5阈值,因为新流程筛选的是0.5-0.8)
+            if max_similarity < 0.5:
+                category = '低相似度'
+            elif 0.5 <= max_similarity < 0.8:
+                category = '待搜索'  # 后续会根据评估数据更新为'已搜索'
+            else:
+                category = '高相似度'  # >=0.8,孤立点
+
+            features[feature_name] = {
+                'similarity': max_similarity,
+                'weight': weight,
+                'dimension': dimension_name,
+                'category': category
+            }
+
+    return features
+
+
+def is_category_or_feature(persona_name: str, persona_data: Dict[str, Any]) -> str:
+    """
+    递归判断人设项是特征还是分类
+
+    Args:
+        persona_name: 人设项名称
+        persona_data: 人设库数据
+
+    Returns:
+        'feature' 或 'category'
+    """
+    def search_in_dict(data, name, path=""):
+        """递归搜索字典"""
+        if isinstance(data, dict):
+            # 检查是否有特征列表
+            if '特征列表' in data:
+                for feature in data['特征列表']:
+                    if feature.get('特征名称') == name:
+                        return 'feature'
+
+            # 递归搜索子节点
+            for key, value in data.items():
+                if key == '_meta':
+                    continue
+
+                # 如果键名匹配,且值是字典(说明是分类节点)
+                if key == name and isinstance(value, dict):
+                    return 'category'
+
+                # 递归搜索
+                result = search_in_dict(value, name, f"{path}/{key}")
+                if result:
+                    return result
+
+        return None
+
+    # 在三个主要维度中搜索
+    for dimension in ['灵感点列表', '目的点列表', '关键点列表']:
+        if dimension in persona_data:
+            result = search_in_dict(persona_data[dimension], persona_name)
+            if result:
+                return result
+
+    # 默认返回特征
+    return 'feature'
+
+
+def extract_relationship_data(how_json_path: str, persona_data: Dict[str, Any]) -> tuple:
+    """
+    从how解构结果中提取关系数据
+
+    Returns:
+        Tuple of (relationships, all_high_matches):
+        - relationships: 最高相似度匹配的关系列表
+        - all_high_matches: 所有相似度>0.8的匹配列表
+
+        Data format:
+        {
+            'post_feature': str,  # 帖子特征名称
+            'dimension': str,     # 所属维度(灵感点/目的点/关键点)
+            'weight': float,      # 权重
+            'persona_item': str,  # 人设特征/分类名称
+            'similarity': float,  # 相似度
+            'item_type': str,     # 'feature' or 'category'
+        }
+    """
+    with open(how_json_path, 'r', encoding='utf-8') as f:
+        how_data = json.load(f)
+
+    relationships = []
+    all_high_matches = []  # 所有相似度>0.8的匹配
+
+    # 遍历how解构结果
+    for dimension_key in ['灵感点列表', '目的点列表', '关键点列表']:
+        dimension_list = how_data.get('how解构结果', {}).get(dimension_key, [])
+        dimension_name = dimension_key.replace('列表', '')
+
+        for point in dimension_list:
+            features = point.get('特征列表', [])
+
+            for feature in features:
+                feature_name = feature.get('特征名称', '')
+                weight = feature.get('权重', 0)
+
+                # 查找匹配结果中相似度最高的
+                how_steps = point.get('how步骤列表', [])
+                max_match = None
+                max_similarity = -1
+
+                for step in how_steps:
+                    step_features = step.get('特征列表', [])
+                    for step_feature in step_features:
+                        if step_feature.get('特征名称') == feature_name:
+                            matches = step_feature.get('匹配结果', [])
+                            for match in matches:
+                                similarity = match.get('匹配结果', {}).get('相似度', 0)
+                                persona_name = match.get('人设特征名称', '')
+
+                                # 收集所有相似度>0.8的匹配
+                                if similarity > 0.8:
+                                    item_type = is_category_or_feature(persona_name, persona_data)
+                                    all_high_matches.append({
+                                        'post_feature': feature_name,
+                                        'dimension': dimension_name,
+                                        'weight': weight,
+                                        'persona_item': persona_name,
+                                        'similarity': similarity,
+                                        'item_type': item_type
+                                    })
+
+                                # 追踪最高相似度
+                                if similarity > max_similarity:
+                                    max_similarity = similarity
+                                    max_match = match
+
+                # 如果找到匹配
+                if max_match:
+                    persona_name = max_match.get('人设特征名称', '')
+                    item_type = is_category_or_feature(persona_name, persona_data)
+
+                    relationships.append({
+                        'post_feature': feature_name,
+                        'dimension': dimension_name,
+                        'weight': weight,
+                        'persona_item': persona_name,
+                        'similarity': max_similarity,
+                        'item_type': item_type
+                    })
+
+    return relationships, all_high_matches
+
+
+def generate_relationship_graph_html(relationships: List[Dict[str, Any]],
+                                    all_high_matches: List[Dict[str, Any]]) -> str:
+    """
+    生成关系图的SVG HTML代码
+
+    Args:
+        relationships: 最高相似度匹配的关系数据列表
+        all_high_matches: 所有相似度>0.8的匹配列表
+
+    Returns:
+        HTML字符串(包含SVG)
+    """
+    if not relationships:
+        return '<div style="padding: 40px; text-align: center; color: #6b7280;">暂无关系数据</div>'
+
+    # 合并所有需要显示的人设项(去重)
+    all_personas = set()
+
+    # 添加relationships中的人设项
+    for r in relationships:
+        all_personas.add((r['persona_item'], r['item_type'], r['similarity']))
+
+    # 添加所有>0.8的人设项
+    for match in all_high_matches:
+        all_personas.add((match['persona_item'], match['item_type'], match['similarity']))
+
+    persona_items = list(all_personas)
+
+    # 为每个帖子特征关联最高相似度
+    post_feature_map = {}
+    for r in relationships:
+        feature = r['post_feature']
+        if feature not in post_feature_map or r['similarity'] > post_feature_map[feature][3]:
+            post_feature_map[feature] = (feature, r['dimension'], r['weight'], r['similarity'])
+
+    post_features = list(post_feature_map.values())
+
+    # 排序:都按相似度降序
+    persona_items.sort(key=lambda x: x[2], reverse=True)  # 按相似度降序
+    post_features.sort(key=lambda x: x[3], reverse=True)  # 按相似度降序
+
+    # 布局参数
+    node_spacing = 60  # 节点间距
+    node_radius = 20   # 圆形半径
+    node_size = 40     # 方形大小
+    left_margin = 250  # 左边距
+    right_margin = 250 # 右边距
+    middle_space = 400 # 中间空间
+    top_margin = 50    # 顶部边距
+
+    # 计算SVG尺寸
+    max_nodes = max(len(persona_items), len(post_features))
+    svg_height = max_nodes * node_spacing + top_margin * 2
+    svg_width = left_margin + middle_space + right_margin
+
+    # 辅助函数:获取颜色
+    def get_color(similarity):
+        if similarity > 0.8:
+            return '#10b981'  # 绿色
+        elif similarity > 0.4:
+            return '#f59e0b'  # 黄色
+        else:
+            return '#ef4444'  # 红色
+
+    # 辅助函数:获取线条样式
+    def get_stroke_dasharray(similarity):
+        if similarity > 0.8:
+            return 'none'  # 实线
+        else:
+            return '5,5'  # 虚线
+
+    # 创建节点位置映射
+    persona_positions = {}
+    post_positions = {}
+
+    for idx, (item, item_type, similarity) in enumerate(persona_items):
+        y = top_margin + idx * node_spacing
+        persona_positions[item] = {
+            'x': left_margin,
+            'y': y,
+            'type': item_type,
+            'similarity': similarity,
+            'idx': idx  # 添加索引用于生成ID
+        }
+
+    for idx, (feature, dimension, weight, similarity) in enumerate(post_features):
+        y = top_margin + idx * node_spacing
+        post_positions[feature] = {
+            'x': left_margin + middle_space,
+            'y': y,
+            'dimension': dimension,
+            'weight': weight,
+            'similarity': similarity,
+            'idx': idx  # 添加索引用于生成ID
+        }
+
+    # 开始生成SVG
+    svg_parts = [f'''
+    <div class="relationship-graph-container">
+        <svg width="{svg_width}" height="{svg_height}" xmlns="http://www.w3.org/2000/svg">
+            <!-- 定义箭头标记 -->
+            <defs>
+                <marker id="arrowhead-green" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
+                    <polygon points="0 0, 10 3, 0 6" fill="#10b981" />
+                </marker>
+                <marker id="arrowhead-yellow" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
+                    <polygon points="0 0, 10 3, 0 6" fill="#f59e0b" />
+                </marker>
+                <marker id="arrowhead-red" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
+                    <polygon points="0 0, 10 3, 0 6" fill="#ef4444" />
+                </marker>
+            </defs>
+
+            <!-- 左右标记 -->
+            <g class="labels">
+                <!-- 人设标记 -->
+                <text x="{left_margin}" y="30"
+                      font-size="18" font-weight="600" fill="#6b7280"
+                      text-anchor="middle">人设</text>
+
+                <!-- 帖子标记 -->
+                <text x="{left_margin + middle_space}" y="30"
+                      font-size="18" font-weight="600" fill="#6b7280"
+                      text-anchor="middle">帖子</text>
+            </g>
+
+            <!-- 连接线 -->
+            <g class="connections">
+    ''']
+
+    # 合并所有需要绘制的连接(去重)
+    all_connections = []
+    connection_keys = set()
+
+    # 添加主要关系(最高相似度匹配)
+    for rel in relationships:
+        key = (rel['persona_item'], rel['post_feature'])
+        if key not in connection_keys:
+            all_connections.append(rel)
+            connection_keys.add(key)
+
+    # 添加额外的>0.8关系
+    for match in all_high_matches:
+        key = (match['persona_item'], match['post_feature'])
+        if key not in connection_keys:
+            all_connections.append(match)
+            connection_keys.add(key)
+
+    # 绘制连接线
+    line_idx = 0  # 计数器用于标签错开
+    for rel in all_connections:
+        persona_pos = persona_positions.get(rel['persona_item'])
+        post_pos = post_positions.get(rel['post_feature'])
+
+        if persona_pos and post_pos:
+            x1 = persona_pos['x'] + (node_size if persona_pos['type'] == 'category' else node_radius)
+            y1 = persona_pos['y']
+            x2 = post_pos['x'] - node_radius
+            y2 = post_pos['y']
+
+            # 贝塞尔曲线控制点
+            cx1 = x1 + (x2 - x1) * 0.3
+            cx2 = x1 + (x2 - x1) * 0.7
+
+            color = get_color(rel['similarity'])
+            dasharray = get_stroke_dasharray(rel['similarity'])
+
+            # 生成节点ID
+            persona_id = f"persona-{persona_pos['idx']}"
+            post_id = f"post-{post_pos['idx']}"
+
+            # 标签位置:左右错开
+            if line_idx % 2 == 0:
+                label_x = x1 + (x2 - x1) * 0.35  # 左侧位置
+            else:
+                label_x = x1 + (x2 - x1) * 0.65  # 右侧位置
+            line_idx += 1
+
+            svg_parts.append(f'''
+                <path class="connection-line"
+                      data-from="{persona_id}"
+                      data-to="{post_id}"
+                      d="M {x1},{y1} C {cx1},{y1} {cx2},{y2} {x2},{y2}"
+                      stroke="{color}"
+                      stroke-width="2"
+                      stroke-dasharray="{dasharray}"
+                      fill="none"
+                      opacity="0.7" />
+
+                <!-- 相似度标签 -->
+                <text x="{label_x}" y="{(y1 + y2) / 2 - 5}"
+                      font-size="12"
+                      fill="{color}"
+                      text-anchor="middle"
+                      font-weight="600"
+                      pointer-events="none">
+                    {rel['similarity']:.2f}
+                </text>
+            ''')
+
+    svg_parts.append('</g>\n\n<!-- 左侧:人设特征/分类节点 -->\n<g class="persona-nodes">')
+
+    # 绘制左侧人设节点
+    for item, pos in persona_positions.items():
+        color = get_color(pos['similarity'])
+        node_id = f"persona-{pos['idx']}"
+
+        if pos['type'] == 'category':
+            # 方形
+            half_size = node_size / 2
+            svg_parts.append(f'''
+                <g class="clickable" data-node-id="{node_id}" onclick="handleNodeClick('{node_id}', 'persona')">
+                    <rect x="{pos['x'] - half_size}" y="{pos['y'] - half_size}"
+                          width="{node_size}" height="{node_size}"
+                          fill="{color}"
+                          stroke="white"
+                          stroke-width="2"
+                          rx="4" />
+                    <text x="{pos['x'] - half_size - 10}" y="{pos['y'] + 5}"
+                          font-size="14"
+                          fill="#374151"
+                          text-anchor="end"
+                          font-weight="500"
+                          pointer-events="none">⬜ {item}</text>
+                </g>
+            ''')
+        else:
+            # 圆形
+            svg_parts.append(f'''
+                <g class="clickable" data-node-id="{node_id}" onclick="handleNodeClick('{node_id}', 'persona')">
+                    <circle cx="{pos['x']}" cy="{pos['y']}"
+                            r="{node_radius}"
+                            fill="{color}"
+                            stroke="white"
+                            stroke-width="2" />
+                    <text x="{pos['x'] - node_radius - 10}" y="{pos['y'] + 5}"
+                          font-size="14"
+                          fill="#374151"
+                          text-anchor="end"
+                          font-weight="500"
+                          pointer-events="none">⚪ {item}</text>
+                </g>
+            ''')
+
+    svg_parts.append('</g>\n\n<!-- 右侧:帖子特征节点 -->\n<g class="post-nodes">')
+
+    # 绘制右侧帖子节点
+    for feature, pos in post_positions.items():
+        # 帖子特征都是圆形,蓝色
+        node_id = f"post-{pos['idx']}"
+        svg_parts.append(f'''
+            <g class="clickable" data-node-id="{node_id}" onclick="handleNodeClick('{node_id}', 'post')">
+                <circle cx="{pos['x']}" cy="{pos['y']}"
+                        r="{node_radius}"
+                        fill="#3b82f6"
+                        stroke="white"
+                        stroke-width="2" />
+                <text x="{pos['x'] + node_radius + 10}" y="{pos['y'] + 5}"
+                      font-size="14"
+                      fill="#374151"
+                      text-anchor="start"
+                      font-weight="500"
+                      pointer-events="none">📝 {feature}</text>
+                <text x="{pos['x'] + node_radius + 10}" y="{pos['y'] + 20}"
+                      font-size="11"
+                      fill="#6b7280"
+                      text-anchor="start"
+                      pointer-events="none">({pos['dimension']} · 权重{pos['weight']:.1f})</text>
+            </g>
+        ''')
+
+    svg_parts.append('''
+            </g>
+        </svg>
+    </div>
+    ''')
+
+    return ''.join(svg_parts)
+
+
+def calculate_statistics(data: List[Dict[str, Any]]) -> Dict[str, Any]:
+    """计算统计数据(包括评估结果)"""
+    total_features = len(data)
+    total_search_words = 0
+    searched_count = 0
+    not_searched_count = 0
+    total_notes = 0
+    video_count = 0
+    normal_count = 0
+
+    # 评估统计
+    total_evaluated_notes = 0
+    total_filtered = 0
+    match_complete = 0
+    match_similar = 0
+    match_weak = 0
+    match_none = 0
+
+    for feature in data:
+        grouped_results = feature.get('组合评估结果_分组', [])
+
+        for group in grouped_results:
+            search_items = group.get('top10_searches', [])
+            total_search_words += len(search_items)
+
+            for search_item in search_items:
+                search_result = search_item.get('search_result', {})
+
+                if search_result:
+                    searched_count += 1
+                    notes = search_result.get('data', {}).get('data', [])
+                    total_notes += len(notes)
+
+                    for note in notes:
+                        note_type = note.get('note_card', {}).get('type', '')
+                        if note_type == 'video':
+                            video_count += 1
+                        else:
+                            normal_count += 1
+
+                    evaluation = search_item.get('evaluation_with_filter')
+                    if evaluation:
+                        total_evaluated_notes += evaluation.get('total_notes', 0)
+                        total_filtered += evaluation.get('filtered_count', 0)
+
+                        stats = evaluation.get('statistics', {})
+                        match_complete += stats.get('完全匹配(0.8-1.0)', 0)
+                        match_similar += stats.get('相似匹配(0.6-0.79)', 0)
+                        match_weak += stats.get('弱相似(0.5-0.59)', 0)
+                        match_none += stats.get('无匹配(≤0.4)', 0)
+                else:
+                    not_searched_count += 1
+
+    total_remaining = total_evaluated_notes - total_filtered if total_evaluated_notes > 0 else 0
+
+    return {
+        'total_features': total_features,
+        'total_search_words': total_search_words,
+        'searched_count': searched_count,
+        'not_searched_count': not_searched_count,
+        'searched_percentage': round(searched_count / total_search_words * 100, 1) if total_search_words > 0 else 0,
+        'total_notes': total_notes,
+        'video_count': video_count,
+        'normal_count': normal_count,
+        'video_percentage': round(video_count / total_notes * 100, 1) if total_notes > 0 else 0,
+        'normal_percentage': round(normal_count / total_notes * 100, 1) if total_notes > 0 else 0,
+        'total_evaluated': total_evaluated_notes,
+        'total_filtered': total_filtered,
+        'total_remaining': total_remaining,
+        'filter_rate': round(total_filtered / total_evaluated_notes * 100, 1) if total_evaluated_notes > 0 else 0,
+        'match_complete': match_complete,
+        'match_similar': match_similar,
+        'match_weak': match_weak,
+        'match_none': match_none,
+        'complete_rate': round(match_complete / total_remaining * 100, 1) if total_remaining > 0 else 0,
+        'similar_rate': round(match_similar / total_remaining * 100, 1) if total_remaining > 0 else 0,
+    }
+
+
+def generate_html(data: List[Dict[str, Any]], stats: Dict[str, Any],
+                  deconstruction_mapping: Dict[str, Any], similarity_mapping: Dict[str, Any],
+                  relationship_graph_html: str,
+                  all_features: Dict[str, Dict[str, Any]],
+                  high_similarity_features: List[Dict[str, Any]],
+                  low_similarity_features: List[Dict[str, Any]],
+                  output_path: str):
+    """生成HTML可视化页面"""
+
+    # 准备数据JSON
+    data_json = json.dumps(data, ensure_ascii=False, indent=2)
+    deconstruction_json = json.dumps(deconstruction_mapping, ensure_ascii=False, indent=2)
+    similarity_json = json.dumps(similarity_mapping, ensure_ascii=False, indent=2)
+    all_features_json = json.dumps(all_features, ensure_ascii=False, indent=2)
+    high_similarity_json = json.dumps(high_similarity_features, ensure_ascii=False, indent=2)
+    low_similarity_json = json.dumps(low_similarity_features, ensure_ascii=False, indent=2)
+
+    html_content = f'''<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>搜索评估和解构整合可视化</title>
+    <style>
+        * {{
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }}
+
+        body {{
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+            background: #f5f7fa;
+            color: #333;
+            overflow-x: hidden;
+        }}
+
+        /* 顶部统计面板 */
+        .stats-panel {{
+            position: relative;
+            z-index: 50;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 20px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+        }}
+
+        .stats-container {{
+            max-width: 1400px;
+            margin: 0 auto;
+        }}
+
+        .stats-row {{
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 15px;
+            margin-bottom: 15px;
+        }}
+
+        .stats-row:last-child {{
+            margin-bottom: 0;
+            padding-top: 15px;
+            border-top: 1px solid rgba(255,255,255,0.2);
+        }}
+
+        .stat-item {{
+            text-align: center;
+        }}
+
+        .stat-value {{
+            font-size: 28px;
+            font-weight: bold;
+            margin-bottom: 5px;
+        }}
+
+        .stat-label {{
+            font-size: 12px;
+            opacity: 0.9;
+        }}
+
+        .stat-item.small .stat-value {{
+            font-size: 22px;
+        }}
+
+        /* 过滤控制面板 */
+        .filter-panel {{
+            position: relative;
+            z-index: 40;
+            background: white;
+            max-width: 1400px;
+            margin: 20px auto;
+            padding: 15px 20px;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            display: flex;
+            align-items: center;
+            gap: 20px;
+            flex-wrap: wrap;
+        }}
+
+        .filter-label {{
+            font-weight: 600;
+            color: #374151;
+        }}
+
+        .filter-buttons {{
+            display: flex;
+            gap: 10px;
+            flex-wrap: wrap;
+        }}
+
+        .filter-btn {{
+            padding: 6px 12px;
+            border: 2px solid #e5e7eb;
+            background: white;
+            border-radius: 6px;
+            cursor: pointer;
+            font-size: 13px;
+            font-weight: 500;
+            transition: all 0.2s;
+        }}
+
+        .filter-btn:hover {{
+            border-color: #667eea;
+            background: #f9fafb;
+        }}
+
+        .filter-btn.active {{
+            border-color: #667eea;
+            background: #667eea;
+            color: white;
+        }}
+
+        .filter-btn.complete {{
+            border-color: #10b981;
+        }}
+        .filter-btn.complete.active {{
+            background: #10b981;
+            border-color: #10b981;
+        }}
+
+        .filter-btn.similar {{
+            border-color: #f59e0b;
+        }}
+        .filter-btn.similar.active {{
+            background: #f59e0b;
+            border-color: #f59e0b;
+        }}
+
+        .filter-btn.weak {{
+            border-color: #f97316;
+        }}
+        .filter-btn.weak.active {{
+            background: #f97316;
+            border-color: #f97316;
+        }}
+
+        .filter-btn.none {{
+            border-color: #ef4444;
+        }}
+        .filter-btn.none.active {{
+            background: #ef4444;
+            border-color: #ef4444;
+        }}
+
+        .filter-btn.filtered {{
+            border-color: #6b7280;
+        }}
+        .filter-btn.filtered.active {{
+            background: #6b7280;
+            border-color: #6b7280;
+        }}
+
+        /* 主容器 - 级联布局 */
+        .main-container {{
+            display: flex;
+            max-width: 1800px;
+            margin: 0 auto 20px;
+            gap: 25px;
+            padding: 0 20px;
+            height: calc(100vh - 260px);
+            position: relative;
+        }}
+
+        /* 左侧栏 - 原始特征列表 */
+        .left-sidebar {{
+            width: 20%;
+            min-width: 220px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            overflow-y: auto;
+            height: fit-content;
+            max-height: calc(100vh - 280px);
+        }}
+
+        /* 中间栏 - base_word列表 */
+        .middle-sidebar {{
+            width: 18%;
+            min-width: 240px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            overflow-y: auto;
+            height: fit-content;
+            max-height: calc(100vh - 280px);
+            display: none;
+        }}
+
+        .middle-sidebar.active {{
+            display: block;
+        }}
+
+        /* 右侧栏 - 搜索词列表 */
+        .right-sidebar {{
+            width: 22%;
+            min-width: 280px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            overflow-y: auto;
+            height: fit-content;
+            max-height: calc(100vh - 280px);
+            display: none;
+        }}
+
+        .right-sidebar.active {{
+            display: block;
+        }}
+
+        /* 详情区域 */
+        .detail-area {{
+            flex: 1;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            overflow-y: auto;
+            max-height: calc(100vh - 280px);
+        }}
+
+        /* 侧边栏标题 */
+        .sidebar-header {{
+            position: sticky;
+            top: 0;
+            z-index: 10;
+            padding: 15px 20px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            font-size: 16px;
+            font-weight: 600;
+            border-radius: 8px 8px 0 0;
+        }}
+
+        .sidebar-content {{
+            padding: 10px;
+        }}
+
+        /* 详情区占位符 */
+        .detail-placeholder {{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            height: 300px;
+            font-size: 16px;
+            color: #9ca3af;
+        }}
+
+        .detail-content {{
+            padding: 20px;
+        }}
+
+        /* 候选词库固定区域 */
+        .candidate-library {{
+            position: sticky;
+            top: 0;
+            z-index: 100;
+            background: white;
+            border-bottom: 2px solid #e5e7eb;
+            padding: 15px;
+            margin-bottom: 10px;
+        }}
+
+        .candidate-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            cursor: pointer;
+            padding: 10px;
+            background: linear-gradient(135deg, #f3e8ff 0%, #fae8ff 100%);
+            border-radius: 6px;
+            border: 2px solid #d8b4fe;
+            transition: all 0.2s;
+        }}
+
+        .candidate-header:hover {{
+            background: linear-gradient(135deg, #e9d5ff 0%, #f3e8ff 100%);
+        }}
+
+        .candidate-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #7c3aed;
+        }}
+
+        .candidate-toggle {{
+            font-size: 12px;
+            color: #7c3aed;
+            transition: transform 0.3s;
+        }}
+
+        .candidate-toggle.rotated {{
+            transform: rotate(180deg);
+        }}
+
+        .candidate-content {{
+            margin-top: 10px;
+            max-height: 0;
+            overflow: hidden;
+            transition: max-height 0.3s ease;
+        }}
+
+        .candidate-content.expanded {{
+            max-height: 400px;
+            overflow-y: auto;
+        }}
+
+        .candidate-section {{
+            margin-bottom: 12px;
+        }}
+
+        .candidate-section-title {{
+            font-size: 12px;
+            font-weight: 600;
+            color: #6b7280;
+            padding: 8px 10px;
+            background: #f9fafb;
+            border-radius: 4px;
+            margin-bottom: 6px;
+        }}
+
+        .candidate-list {{
+            display: flex;
+            flex-wrap: wrap;
+            gap: 6px;
+        }}
+
+        .candidate-item {{
+            padding: 4px 10px;
+            font-size: 11px;
+            border-radius: 12px;
+            cursor: pointer;
+            transition: all 0.2s;
+            border: 1px solid transparent;
+        }}
+
+        .candidate-item.persona {{
+            background: #d1fae5;
+            color: #065f46;
+            border-color: #a7f3d0;
+        }}
+
+        .candidate-item.persona:hover {{
+            background: #a7f3d0;
+            border-color: #6ee7b7;
+        }}
+
+        .candidate-item.post {{
+            background: #dbeafe;
+            color: #1e40af;
+            border-color: #bfdbfe;
+        }}
+
+        .candidate-item.post:hover {{
+            background: #bfdbfe;
+            border-color: #93c5fd;
+        }}
+
+        .candidate-item.highlighted {{
+            border-width: 2px;
+            font-weight: 700;
+            transform: scale(1.05);
+            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+        }}
+
+        .candidate-item.persona.highlighted {{
+            background: #6ee7b7;
+            border-color: #10b981;
+        }}
+
+        .candidate-item.post.highlighted {{
+            background: #93c5fd;
+            border-color: #3b82f6;
+        }}
+
+        /* 来源分类路径 */
+        .source-path {{
+            font-size: 11px;
+            color: #6b7280;
+            padding: 6px 12px;
+            background: #fefce8;
+            border-left: 3px solid #fbbf24;
+            border-radius: 4px;
+            margin-top: 6px;
+            line-height: 1.5;
+        }}
+
+        .source-path-label {{
+            font-weight: 600;
+            color: #92400e;
+        }}
+
+        /* 搜索词推理区域 */
+        .search-word-reasoning {{
+            margin-top: 8px;
+            padding: 10px;
+            background: #f0f9ff;
+            border-left: 3px solid #3b82f6;
+            border-radius: 4px;
+            font-size: 11px;
+            color: #1e3a8a;
+            line-height: 1.6;
+        }}
+
+        .reasoning-label {{
+            font-weight: 600;
+            color: #2563eb;
+            margin-bottom: 4px;
+            display: block;
+        }}
+
+        .reasoning-text {{
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }}
+
+        .reasoning-text.expanded {{
+            display: block;
+            -webkit-line-clamp: unset;
+        }}
+
+        .reasoning-toggle {{
+            display: inline-block;
+            margin-top: 6px;
+            padding: 4px 10px;
+            font-size: 11px;
+            font-weight: 600;
+            color: #3b82f6;
+            background: transparent;
+            border: none;
+            cursor: pointer;
+            transition: all 0.2s;
+            user-select: none;
+        }}
+
+        .reasoning-toggle:hover {{
+            color: #2563eb;
+            text-decoration: underline;
+        }}
+
+        /* 搜索词组合词和匹配分 */
+        .search-word-combo {{
+            font-size: 11px;
+            color: #6b7280;
+            margin-top: 4px;
+            padding: 6px 10px;
+            background: #f9fafb;
+            border-radius: 4px;
+            line-height: 1.5;
+        }}
+
+        .combo-label {{
+            font-weight: 600;
+            color: #374151;
+        }}
+
+        .match-score {{
+            display: inline-block;
+            padding: 2px 6px;
+            margin-left: 6px;
+            background: #dcfce7;
+            color: #166534;
+            font-weight: 700;
+            border-radius: 4px;
+        }}
+
+        .search-word-item.highlighted {{
+            background: #fef3c7 !important;
+            border-left-color: #f59e0b !important;
+            box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
+        }}
+
+        /* 级联列表项通用样式 */
+        .cascade-item {{
+            padding: 12px 15px;
+            border-bottom: 1px solid #e5e7eb;
+            cursor: pointer;
+            user-select: none;
+            transition: all 0.2s;
+            border-left: 3px solid transparent;
+        }}
+
+        .cascade-item:hover {{
+            background: #f9fafb;
+            border-left-color: #667eea;
+        }}
+
+        .cascade-item.active {{
+            background: #ede9fe;
+            border-left-color: #7c3aed;
+        }}
+
+        .cascade-item-title {{
+            font-size: 14px;
+            font-weight: 600;
+            margin-bottom: 4px;
+        }}
+
+        .cascade-item-meta {{
+            font-size: 11px;
+            color: #6b7280;
+        }}
+
+        /* 左侧栏特征项 */
+        .feature-item-left {{
+            padding: 15px;
+            border-bottom: 1px solid #e5e7eb;
+            cursor: pointer;
+            transition: all 0.2s;
+            border-left: 4px solid transparent;
+        }}
+
+        .feature-item-left:hover {{
+            background: #fef3c7;
+            border-left-color: #f59e0b;
+        }}
+
+        .feature-item-left.active {{
+            background: linear-gradient(90deg, #fef3c7 0%, #fefce8 100%);
+            border-left-color: #f59e0b;
+            position: relative;
+        }}
+
+        .feature-item-left.active::after {{
+            content: '';
+            position: absolute;
+            right: -25px;
+            top: 50%;
+            width: 25px;
+            height: 2px;
+            background: #f59e0b;
+        }}
+
+        .feature-item-left.active::before {{
+            content: '';
+            position: absolute;
+            right: -28px;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 0;
+            height: 0;
+            border-left: 6px solid #f59e0b;
+            border-top: 4px solid transparent;
+            border-bottom: 4px solid transparent;
+        }}
+
+        .feature-name {{
+            font-size: 15px;
+            font-weight: 600;
+            color: #1f2937;
+            margin-bottom: 6px;
+        }}
+
+        .feature-meta-small {{
+            font-size: 11px;
+            color: #6b7280;
+            margin-bottom: 4px;
+        }}
+
+        .feature-source {{
+            font-size: 10px;
+            color: #92400e;
+            background: #fef3c7;
+            padding: 2px 6px;
+            border-radius: 3px;
+            margin-top: 4px;
+            display: inline-block;
+        }}
+
+        .feature-stats {{
+            font-size: 11px;
+            color: #7c3aed;
+            margin-top: 6px;
+            font-weight: 500;
+        }}
+
+        /* 中间栏base_word项 */
+        .baseword-item {{
+            padding: 12px 15px;
+            border-bottom: 1px solid #e5e7eb;
+            cursor: pointer;
+            transition: all 0.2s;
+            border-left: 3px solid transparent;
+        }}
+
+        .baseword-item:hover {{
+            background: #f0fdf4;
+            border-left-color: #22c55e;
+        }}
+
+        .baseword-item.active {{
+            background: linear-gradient(90deg, #dcfce7 0%, #f0fdf4 100%);
+            border-left-color: #22c55e;
+            position: relative;
+        }}
+
+        .baseword-item.active::after {{
+            content: '';
+            position: absolute;
+            right: -25px;
+            top: 50%;
+            width: 25px;
+            height: 2px;
+            background: #22c55e;
+        }}
+
+        .baseword-item.active::before {{
+            content: '';
+            position: absolute;
+            right: -28px;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 0;
+            height: 0;
+            border-left: 6px solid #22c55e;
+            border-top: 4px solid transparent;
+            border-bottom: 4px solid transparent;
+        }}
+
+        /* 右侧栏搜索词项 */
+        .searchword-item {{
+            padding: 12px 15px;
+            border-bottom: 1px solid #e5e7eb;
+            cursor: pointer;
+            transition: all 0.2s;
+            border-left: 3px solid transparent;
+        }}
+
+        .searchword-item:hover {{
+            background: #dbeafe;
+            border-left-color: #3b82f6;
+        }}
+
+        .searchword-item.active {{
+            background: linear-gradient(90deg, #dbeafe 0%, #eff6ff 100%);
+            border-left-color: #3b82f6;
+            position: relative;
+        }}
+
+        .searchword-item.active::after {{
+            content: '';
+            position: absolute;
+            right: -25px;
+            top: 50%;
+            width: 25px;
+            height: 2px;
+            background: #3b82f6;
+        }}
+
+        .searchword-item.active::before {{
+            content: '';
+            position: absolute;
+            right: -28px;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 0;
+            height: 0;
+            border-left: 6px solid #3b82f6;
+            border-top: 4px solid transparent;
+            border-bottom: 4px solid transparent;
+        }}
+
+        .feature-group {{
+            border-bottom: 1px solid #e5e7eb;
+        }}
+
+        .feature-header {{
+            padding: 15px 20px;
+            background: #f9fafb;
+            cursor: pointer;
+            user-select: none;
+            transition: background 0.2s;
+        }}
+
+        .feature-header:hover {{
+            background: #f3f4f6;
+        }}
+
+        .feature-header.active {{
+            background: #667eea;
+            color: white;
+        }}
+
+        .feature-title {{
+            font-size: 16px;
+            font-weight: 600;
+            margin-bottom: 5px;
+        }}
+
+        .post-target-word {{
+            color: #2563eb;
+            background: linear-gradient(90deg, #dbeafe 0%, #f0f9ff 100%);
+            padding: 8px 12px;
+            border-radius: 6px;
+            border-left: 4px solid #2563eb;
+        }}
+
+        .feature-meta {{
+            font-size: 12px;
+            color: #6b7280;
+        }}
+
+        .feature-header.active .feature-meta {{
+            color: rgba(255,255,255,0.8);
+        }}
+
+        .search-words-list {{
+            display: none;
+            padding: 0;
+        }}
+
+        .search-words-list.expanded {{
+            display: block;
+        }}
+
+        .base-word-group {{
+            border-bottom: 1px solid #f3f4f6;
+        }}
+
+        .base-word-header {{
+            padding: 12px 20px 12px 30px;
+            background: #fafbfc;
+            cursor: pointer;
+            user-select: none;
+            transition: all 0.2s;
+            border-left: 3px solid transparent;
+        }}
+
+        .base-word-header:hover {{
+            background: #f3f4f6;
+            border-left-color: #a78bfa;
+        }}
+
+        .base-word-header.active {{
+            background: #f3f4f6;
+            border-left-color: #7c3aed;
+        }}
+
+        .base-word-title {{
+            font-size: 15px;
+            font-weight: 600;
+            color: #7c3aed;
+            margin-bottom: 4px;
+        }}
+
+        .persona-feature {{
+            color: #059669;
+            background: linear-gradient(90deg, #d1fae5 0%, #ecfdf5 100%);
+            padding: 6px 10px;
+            border-radius: 5px;
+            border-left: 3px solid #059669;
+        }}
+
+        .base-word-meta {{
+            font-size: 11px;
+            color: #6b7280;
+        }}
+
+        .base-word-desc {{
+            padding: 8px 20px 8px 30px;
+            background: #fefce8;
+            font-size: 12px;
+            color: #854d0e;
+            line-height: 1.5;
+            border-left: 3px solid #fbbf24;
+            display: none;
+        }}
+
+        .base-word-desc.expanded {{
+            display: block;
+        }}
+
+        .search-words-sublist {{
+            display: none;
+        }}
+
+        .search-words-sublist.expanded {{
+            display: block;
+        }}
+
+        .search-word-item {{
+            padding: 12px 20px 12px 50px;
+            cursor: pointer;
+            border-left: 3px solid transparent;
+            transition: all 0.2s;
+        }}
+
+        .search-word-item:hover {{
+            background: #f9fafb;
+            border-left-color: #667eea;
+        }}
+
+        .search-word-item.active {{
+            background: #ede9fe;
+            border-left-color: #7c3aed;
+        }}
+
+        .search-word-text {{
+            font-size: 14px;
+            font-weight: 500;
+            color: #374151;
+            margin-bottom: 4px;
+        }}
+
+        .search-word-score {{
+            display: inline-block;
+            padding: 2px 8px;
+            border-radius: 12px;
+            font-size: 11px;
+            font-weight: 600;
+            margin-left: 8px;
+        }}
+
+        .score-high {{
+            background: #d1fae5;
+            color: #065f46;
+        }}
+
+        .score-medium {{
+            background: #fef3c7;
+            color: #92400e;
+        }}
+
+        .score-low {{
+            background: #fee2e2;
+            color: #991b1b;
+        }}
+
+        .eval-badge {{
+            display: inline-block;
+            padding: 2px 6px;
+            border-radius: 10px;
+            font-size: 11px;
+            font-weight: 600;
+            margin-left: 6px;
+        }}
+
+        .eval-complete {{
+            background: #d1fae5;
+            color: #065f46;
+            border: 1px solid #10b981;
+        }}
+
+        .eval-similar {{
+            background: #fef3c7;
+            color: #92400e;
+            border: 1px solid #f59e0b;
+        }}
+
+        .eval-weak {{
+            background: #fed7aa;
+            color: #9a3412;
+            border: 1px solid #f97316;
+        }}
+
+        .eval-none {{
+            background: #fee2e2;
+            color: #991b1b;
+            border: 1px solid #ef4444;
+        }}
+
+        .eval-filtered {{
+            background: #e5e7eb;
+            color: #4b5563;
+            border: 1px solid #6b7280;
+        }}
+
+        .search-word-eval {{
+            font-size: 11px;
+            color: #6b7280;
+            margin-top: 4px;
+        }}
+
+        /* P值显示样式 */
+        .p-score-container {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-top: 6px;
+            padding: 6px 8px;
+            background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+            border-radius: 6px;
+            border-left: 3px solid #0ea5e9;
+        }}
+
+        .p-score-label {{
+            font-size: 11px;
+            font-weight: 600;
+            color: #0369a1;
+        }}
+
+        .p-score-value {{
+            font-size: 13px;
+            font-weight: 700;
+            color: #0284c7;
+        }}
+
+        .p-score-info-icon {{
+            cursor: pointer;
+            font-size: 14px;
+            color: #0ea5e9;
+            transition: all 0.2s;
+            padding: 2px 6px;
+            border-radius: 50%;
+            background: white;
+        }}
+
+        .p-score-info-icon:hover {{
+            background: #0ea5e9;
+            color: white;
+            transform: scale(1.1);
+        }}
+
+        .p-score-arrow {{
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            font-size: 11px;
+            color: #64748b;
+            margin-top: 4px;
+        }}
+
+        .p-score-arrow-symbol {{
+            color: #0ea5e9;
+            font-weight: bold;
+        }}
+
+        /* P值详情模态窗口 */
+        .p-detail-modal {{
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0, 0, 0, 0.7);
+            z-index: 10000;
+            animation: fadeIn 0.3s ease;
+        }}
+
+        .p-detail-modal.active {{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }}
+
+        .p-detail-window {{
+            background: white;
+            border-radius: 12px;
+            width: 90%;
+            max-width: 800px;
+            max-height: 85vh;
+            overflow: hidden;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            animation: slideUp 0.3s ease;
+            display: flex;
+            flex-direction: column;
+        }}
+
+        .p-detail-header {{
+            padding: 20px 25px;
+            background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
+            color: white;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            flex-shrink: 0;
+        }}
+
+        .p-detail-title {{
+            font-size: 18px;
+            font-weight: 600;
+        }}
+
+        .p-detail-close-btn {{
+            background: rgba(255, 255, 255, 0.2);
+            border: none;
+            color: white;
+            width: 36px;
+            height: 36px;
+            border-radius: 50%;
+            cursor: pointer;
+            font-size: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s;
+        }}
+
+        .p-detail-close-btn:hover {{
+            background: rgba(255, 255, 255, 0.3);
+            transform: scale(1.1);
+        }}
+
+        .p-detail-body {{
+            flex: 1;
+            overflow-y: auto;
+            padding: 25px;
+        }}
+
+        .p-formula-section {{
+            background: #f8fafc;
+            border: 2px solid #e2e8f0;
+            border-radius: 8px;
+            padding: 16px;
+            margin-bottom: 20px;
+        }}
+
+        .p-formula-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #0f172a;
+            margin-bottom: 12px;
+        }}
+
+        .p-formula {{
+            font-family: 'Courier New', monospace;
+            font-size: 16px;
+            color: #0284c7;
+            background: white;
+            padding: 12px;
+            border-radius: 6px;
+            text-align: center;
+            margin-bottom: 8px;
+        }}
+
+        .p-summary {{
+            display: grid;
+            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+            gap: 12px;
+            margin-bottom: 20px;
+        }}
+
+        .p-summary-item {{
+            background: white;
+            border: 2px solid #e2e8f0;
+            border-radius: 8px;
+            padding: 12px;
+            text-align: center;
+        }}
+
+        .p-summary-label {{
+            font-size: 12px;
+            color: #64748b;
+            margin-bottom: 4px;
+        }}
+
+        .p-summary-value {{
+            font-size: 20px;
+            font-weight: 700;
+            color: #0f172a;
+        }}
+
+        .p-summary-value.highlight {{
+            color: #0284c7;
+        }}
+
+        .p-matches-section {{
+            margin-top: 20px;
+        }}
+
+        .p-matches-title {{
+            font-size: 15px;
+            font-weight: 600;
+            color: #0f172a;
+            margin-bottom: 12px;
+        }}
+
+        .p-match-item {{
+            background: white;
+            border: 2px solid #e2e8f0;
+            border-radius: 8px;
+            padding: 12px;
+            margin-bottom: 10px;
+            transition: all 0.2s;
+        }}
+
+        .p-match-item:hover {{
+            border-color: #0ea5e9;
+            box-shadow: 0 4px 12px rgba(14, 165, 233, 0.1);
+        }}
+
+        .p-match-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 8px;
+        }}
+
+        .p-match-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #0f172a;
+            flex: 1;
+        }}
+
+        .p-match-contribution {{
+            font-size: 16px;
+            font-weight: 700;
+            color: #0284c7;
+        }}
+
+        .p-match-details {{
+            display: flex;
+            gap: 16px;
+            font-size: 12px;
+            color: #64748b;
+        }}
+
+        .p-match-detail-item {{
+            display: flex;
+            gap: 4px;
+        }}
+
+        .p-match-detail-label {{
+            font-weight: 600;
+        }}
+
+        /* ========== 新增: 顶部统计面板折叠样式 ========== */
+        .stats-panel {{
+            position: relative;
+            transition: all 0.3s ease;
+            cursor: pointer;
+        }}
+
+        .stats-panel.collapsed {{
+            max-height: 90px;
+            overflow: hidden;
+        }}
+
+        .stats-panel.collapsed .stats-row:not(:first-child) {{
+            display: none;
+        }}
+
+        .stats-toggle-icon {{
+            position: absolute;
+            right: 30px;
+            top: 50%;
+            transform: translateY(-50%);
+            font-size: 24px;
+            color: rgba(255, 255, 255, 0.9);
+            transition: transform 0.3s ease;
+            cursor: pointer;
+            z-index: 100;
+        }}
+
+        .stats-panel.collapsed .stats-toggle-icon {{
+            transform: translateY(-50%) rotate(180deg);
+        }}
+
+        /* ========== 新增: 帖子浮层模态窗口样式 ========== */
+        .notes-modal {{
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0, 0, 0, 0.8);
+            z-index: 10001;
+            animation: fadeIn 0.3s ease;
+        }}
+
+        .notes-modal.active {{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }}
+
+        .notes-modal-window {{
+            background: white;
+            border-radius: 12px;
+            width: 95%;
+            max-width: 1400px;
+            height: 90vh;
+            overflow: hidden;
+            display: flex;
+            flex-direction: column;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            animation: slideUp 0.3s ease;
+        }}
+
+        .notes-modal-header {{
+            padding: 20px 25px;
+            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+            color: white;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            flex-shrink: 0;
+        }}
+
+        .notes-modal-title {{
+            font-size: 18px;
+            font-weight: 600;
+        }}
+
+        .notes-modal-subtitle {{
+            font-size: 14px;
+            opacity: 0.9;
+            margin-top: 5px;
+        }}
+
+        .notes-modal-close-btn {{
+            background: rgba(255, 255, 255, 0.2);
+            border: none;
+            color: white;
+            width: 36px;
+            height: 36px;
+            border-radius: 50%;
+            cursor: pointer;
+            font-size: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s;
+        }}
+
+        .notes-modal-close-btn:hover {{
+            background: rgba(255, 255, 255, 0.3);
+            transform: scale(1.1);
+        }}
+
+        .notes-modal-body {{
+            flex: 1;
+            overflow-y: auto;
+            padding: 25px;
+            background: #fafbfc;
+        }}
+
+        .notes-grid-modal {{
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+            gap: 20px;
+        }}
+
+        .note-card-modal {{
+            background: white;
+            border-radius: 12px;
+            overflow: hidden;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            flex-direction: column;
+        }}
+
+        .note-card-modal:hover {{
+            transform: translateY(-4px);
+            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
+        }}
+
+        .note-image-wrapper {{
+            position: relative;
+            width: 100%;
+            height: 200px;
+            background: #f3f4f6;
+            overflow: hidden;
+        }}
+
+        .note-image {{
+            width: 100%;
+            height: 100%;
+            object-fit: cover;
+        }}
+
+        .note-image-placeholder {{
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 48px;
+            background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
+        }}
+
+        .note-content {{
+            padding: 15px;
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            gap: 10px;
+        }}
+
+        .note-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #111827;
+            line-height: 1.4;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+        }}
+
+        .note-score {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            font-size: 13px;
+        }}
+
+        .score-label {{
+            color: #6b7280;
+        }}
+
+        .score-value {{
+            font-weight: 700;
+            padding: 2px 8px;
+            border-radius: 4px;
+        }}
+
+        .score-value.high {{
+            color: #10b981;
+            background: #d1fae5;
+        }}
+
+        .score-value.medium {{
+            color: #f59e0b;
+            background: #fef3c7;
+        }}
+
+        .score-value.low {{
+            color: #6b7280;
+            background: #e5e7eb;
+        }}
+
+        @keyframes fadeIn {{
+            from {{ opacity: 0; }}
+            to {{ opacity: 1; }}
+        }}
+
+        @keyframes slideUp {{
+            from {{
+                opacity: 0;
+                transform: translateY(50px);
+            }}
+            to {{
+                opacity: 1;
+                transform: translateY(0);
+            }}
+        }}
+
+        /* 右侧结果区 */
+        .right-content {{
+            flex: 1;
+            overflow-y: auto;
+            padding-bottom: 40px;
+        }}
+
+        .result-block {{
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+            margin-bottom: 30px;
+            padding: 20px;
+            scroll-margin-top: 20px;
+        }}
+
+        .result-header {{
+            margin-bottom: 20px;
+            padding-bottom: 15px;
+            border-bottom: 2px solid #e5e7eb;
+        }}
+
+        .result-title {{
+            font-size: 20px;
+            font-weight: 600;
+            color: #111827;
+            margin-bottom: 10px;
+        }}
+
+        .result-stats {{
+            display: flex;
+            gap: 10px;
+            font-size: 12px;
+            color: #6b7280;
+            flex-wrap: wrap;
+        }}
+
+        .stat-badge {{
+            background: #f3f4f6;
+            padding: 4px 10px;
+            border-radius: 4px;
+        }}
+
+        .stat-badge.eval {{
+            font-weight: 600;
+        }}
+
+        .stat-badge.eval.complete {{
+            background: #d1fae5;
+            color: #065f46;
+        }}
+
+        .stat-badge.eval.similar {{
+            background: #fef3c7;
+            color: #92400e;
+        }}
+
+        .stat-badge.eval.weak {{
+            background: #fed7aa;
+            color: #9a3412;
+        }}
+
+        .stat-badge.eval.none {{
+            background: #fee2e2;
+            color: #991b1b;
+        }}
+
+        .stat-badge.eval.filtered {{
+            background: #e5e7eb;
+            color: #4b5563;
+        }}
+
+        .notes-grid {{
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+            gap: 20px;
+        }}
+
+        .empty-state {{
+            text-align: center;
+            padding: 60px 40px;
+            color: #6b7280;
+        }}
+
+        .empty-icon {{
+            font-size: 48px;
+            margin-bottom: 16px;
+        }}
+
+        .empty-title {{
+            font-size: 16px;
+            font-weight: 600;
+            color: #374151;
+            margin-bottom: 8px;
+        }}
+
+        .empty-desc {{
+            font-size: 14px;
+            line-height: 1.6;
+            color: #9ca3af;
+            max-width: 400px;
+            margin: 0 auto;
+        }}
+
+        .note-card {{
+            border: 3px solid #e5e7eb;
+            border-radius: 8px;
+            overflow: hidden;
+            cursor: pointer;
+            transition: all 0.3s;
+            background: white;
+        }}
+
+        .note-card:hover {{
+            transform: translateY(-4px);
+            box-shadow: 0 10px 25px rgba(0,0,0,0.15);
+        }}
+
+        .note-card.eval-complete {{
+            border-color: #10b981;
+        }}
+
+        .note-card.eval-similar {{
+            border-color: #f59e0b;
+        }}
+
+        .note-card.eval-weak {{
+            border-color: #f97316;
+        }}
+
+        .note-card.eval-none {{
+            border-color: #ef4444;
+        }}
+
+        .note-card.eval-filtered {{
+            border-color: #6b7280;
+            opacity: 0.6;
+        }}
+
+        /* 图片轮播 */
+        .image-carousel {{
+            position: relative;
+            width: 100%;
+            height: 280px;
+            background: #f3f4f6;
+            overflow: hidden;
+        }}
+
+        .carousel-images {{
+            display: flex;
+            height: 100%;
+            transition: transform 0.3s ease;
+        }}
+
+        .carousel-image {{
+            min-width: 100%;
+            height: 100%;
+            object-fit: cover;
+        }}
+
+        .carousel-btn {{
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            background: rgba(0,0,0,0.5);
+            color: white;
+            border: none;
+            width: 32px;
+            height: 32px;
+            border-radius: 50%;
+            cursor: pointer;
+            font-size: 16px;
+            display: none;
+            align-items: center;
+            justify-content: center;
+            transition: background 0.2s;
+            z-index: 10;
+        }}
+
+        .carousel-btn:hover {{
+            background: rgba(0,0,0,0.7);
+        }}
+
+        .carousel-btn.prev {{
+            left: 8px;
+        }}
+
+        .carousel-btn.next {{
+            right: 8px;
+        }}
+
+        .note-card:hover .carousel-btn {{
+            display: flex;
+        }}
+
+        .carousel-indicators {{
+            position: absolute;
+            bottom: 10px;
+            left: 50%;
+            transform: translateX(-50%);
+            display: flex;
+            gap: 6px;
+            z-index: 10;
+        }}
+
+        .dot {{
+            width: 8px;
+            height: 8px;
+            border-radius: 50%;
+            background: rgba(255,255,255,0.5);
+            cursor: pointer;
+            transition: all 0.2s;
+        }}
+
+        .dot.active {{
+            background: white;
+            width: 24px;
+            border-radius: 4px;
+        }}
+
+        .image-counter {{
+            position: absolute;
+            top: 10px;
+            right: 10px;
+            background: rgba(0,0,0,0.6);
+            color: white;
+            padding: 4px 8px;
+            border-radius: 4px;
+            font-size: 12px;
+            z-index: 10;
+        }}
+
+        .note-info {{
+            padding: 12px;
+        }}
+
+        .note-title {{
+            font-size: 14px;
+            font-weight: 500;
+            color: #111827;
+            margin-bottom: 8px;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+            overflow: hidden;
+            line-height: 1.4;
+        }}
+
+        .note-meta {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            font-size: 12px;
+            color: #6b7280;
+            margin-bottom: 8px;
+        }}
+
+        .note-type {{
+            padding: 3px 8px;
+            border-radius: 4px;
+            font-weight: 500;
+        }}
+
+        .type-video {{
+            background: #dbeafe;
+            color: #1e40af;
+        }}
+
+        .type-normal {{
+            background: #d1fae5;
+            color: #065f46;
+        }}
+
+        .note-author {{
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }}
+
+        .author-avatar {{
+            width: 24px;
+            height: 24px;
+            border-radius: 50%;
+        }}
+
+        .note-eval {{
+            padding: 8px 12px;
+            background: #f9fafb;
+            border-top: 1px solid #e5e7eb;
+            font-size: 12px;
+        }}
+
+        .note-eval-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            cursor: pointer;
+            user-select: none;
+        }}
+
+        .note-eval-score {{
+            font-weight: 600;
+        }}
+
+        .note-eval-toggle {{
+            color: #6b7280;
+            font-size: 10px;
+        }}
+
+        .note-eval-details {{
+            margin-top: 8px;
+            padding-top: 8px;
+            border-top: 1px solid #e5e7eb;
+            display: none;
+            line-height: 1.5;
+        }}
+
+        .note-eval-details.expanded {{
+            display: block;
+        }}
+
+        .eval-detail-label {{
+            font-weight: 600;
+            color: #374151;
+            margin-top: 6px;
+            margin-bottom: 2px;
+        }}
+
+        .eval-detail-label:first-child {{
+            margin-top: 0;
+        }}
+
+        .eval-detail-text {{
+            color: #6b7280;
+        }}
+
+        /* ========== 新增: 解构面板样式 ========== */
+
+        .deconstruction-toggle-btn {{
+            width: 100%;
+            padding: 10px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            border-top: 1px solid #e5e7eb;
+            cursor: pointer;
+            font-size: 13px;
+            font-weight: 600;
+            transition: all 0.3s;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 6px;
+            position: relative;
+            z-index: 1;
+        }}
+
+        .deconstruction-toggle-btn:hover {{
+            background: linear-gradient(135deg, #5568d3 0%, #6a3f8f 100%);
+            transform: scale(1.02);
+        }}
+
+        /* 浮层遮罩 */
+        .modal-overlay {{
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0, 0, 0, 0.7);
+            z-index: 9998;
+            animation: fadeIn 0.3s ease;
+        }}
+
+        .modal-overlay.active {{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }}
+
+        /* 浮层窗口 */
+        .modal-window {{
+            background: white;
+            border-radius: 12px;
+            width: 90%;
+            max-width: 1200px;
+            max-height: 90vh;
+            overflow: hidden;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+            animation: slideUp 0.3s ease;
+            display: flex;
+            flex-direction: column;
+        }}
+
+        @keyframes fadeIn {{
+            from {{ opacity: 0; }}
+            to {{ opacity: 1; }}
+        }}
+
+        @keyframes slideUp {{
+            from {{
+                opacity: 0;
+                transform: translateY(50px);
+            }}
+            to {{
+                opacity: 1;
+                transform: translateY(0);
+            }}
+        }}
+
+        /* 浮层头部 */
+        .modal-header {{
+            padding: 20px 25px;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            flex-shrink: 0;
+        }}
+
+        .modal-title {{
+            font-size: 18px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }}
+
+        .modal-note-title {{
+            font-size: 14px;
+            opacity: 0.9;
+            margin-top: 5px;
+        }}
+
+        .modal-close-btn {{
+            background: rgba(255, 255, 255, 0.2);
+            border: none;
+            color: white;
+            width: 36px;
+            height: 36px;
+            border-radius: 50%;
+            cursor: pointer;
+            font-size: 20px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            transition: all 0.2s;
+        }}
+
+        .modal-close-btn:hover {{
+            background: rgba(255, 255, 255, 0.3);
+            transform: scale(1.1);
+        }}
+
+        /* 浮层内容区 */
+        .modal-body {{
+            flex: 1;
+            overflow-y: auto;
+            padding: 25px;
+            background: #fafbfc;
+        }}
+
+        .deconstruction-content {{
+            max-width: 1000px;
+            margin: 0 auto;
+        }}
+
+        .deconstruction-header {{
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 12px 15px;
+            border-radius: 6px;
+            margin-bottom: 15px;
+            font-size: 14px;
+            font-weight: 600;
+        }}
+
+        .original-feature {{
+            font-size: 16px;
+            margin-top: 4px;
+        }}
+
+        .dimension-card {{
+            background: white;
+            border: 2px solid #e5e7eb;
+            border-radius: 8px;
+            margin-bottom: 12px;
+            overflow: hidden;
+        }}
+
+        .dimension-header {{
+            padding: 10px 15px;
+            background: #667eea;
+            color: white;
+            cursor: pointer;
+            user-select: none;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            font-weight: 600;
+            font-size: 14px;
+        }}
+
+        .dimension-header:hover {{
+            background: #5568d3;
+        }}
+
+        .dimension-title {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }}
+
+        .dimension-count {{
+            font-size: 12px;
+            opacity: 0.9;
+        }}
+
+        .dimension-toggle {{
+            font-size: 12px;
+        }}
+
+        .dimension-body {{
+            max-height: 0;
+            overflow: hidden;
+            transition: max-height 0.3s ease-in-out;
+        }}
+
+        .dimension-body.expanded {{
+            max-height: 1000px;
+        }}
+
+        .feature-list {{
+            padding: 10px;
+        }}
+
+        .feature-item {{
+            padding: 10px;
+            margin-bottom: 8px;
+            background: #f9fafb;
+            border-left: 3px solid #e5e7eb;
+            border-radius: 4px;
+            transition: all 0.2s;
+        }}
+
+        .feature-item:hover {{
+            background: #f3f4f6;
+            border-left-color: #667eea;
+        }}
+
+        .feature-item.top-score {{
+            background: #fff9e6;
+            border-left: 3px solid #FFD700;
+            box-shadow: 0 2px 8px rgba(255, 215, 0, 0.2);
+        }}
+
+        .feature-item.top-score .feature-name {{
+            color: #b8860b;
+            font-weight: 700;
+        }}
+
+        .feature-name {{
+            font-size: 13px;
+            font-weight: 600;
+            color: #111827;
+            margin-bottom: 6px;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }}
+
+        .top-badge {{
+            background: #FFD700;
+            color: #000;
+            padding: 2px 6px;
+            border-radius: 4px;
+            font-size: 11px;
+            font-weight: 700;
+        }}
+
+        .feature-meta-row {{
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-bottom: 6px;
+            font-size: 11px;
+            color: #6b7280;
+        }}
+
+        .feature-dimension-detail {{
+            background: #e5e7eb;
+            padding: 2px 6px;
+            border-radius: 3px;
+        }}
+
+        .feature-weight {{
+            background: #dbeafe;
+            padding: 2px 6px;
+            border-radius: 3px;
+        }}
+
+        .similarity-row {{
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }}
+
+        .similarity-score {{
+            font-size: 14px;
+            font-weight: 700;
+            min-width: 50px;
+        }}
+
+        .similarity-score.high {{
+            color: #10b981;
+        }}
+
+        .similarity-score.medium {{
+            color: #f59e0b;
+        }}
+
+        .similarity-score.low {{
+            color: #6b7280;
+        }}
+
+        .similarity-bar-container {{
+            flex: 1;
+            height: 8px;
+            background: #e5e7eb;
+            border-radius: 4px;
+            overflow: hidden;
+        }}
+
+        .similarity-bar {{
+            height: 100%;
+            border-radius: 4px;
+            transition: width 0.3s ease;
+        }}
+
+        .similarity-bar.high {{
+            background: linear-gradient(90deg, #10b981 0%, #059669 100%);
+        }}
+
+        .similarity-bar.medium {{
+            background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
+        }}
+
+        .similarity-bar.low {{
+            background: linear-gradient(90deg, #9ca3af 0%, #6b7280 100%);
+        }}
+
+        .similarity-explanation {{
+            margin-top: 8px;
+            padding: 8px;
+            background: white;
+            border-radius: 4px;
+            font-size: 11px;
+            color: #6b7280;
+            line-height: 1.5;
+            display: none;
+        }}
+
+        .feature-item:hover .similarity-explanation {{
+            display: block;
+        }}
+
+        /* 滚动条样式 */
+        ::-webkit-scrollbar {{
+            width: 8px;
+            height: 8px;
+        }}
+
+        ::-webkit-scrollbar-track {{
+            background: #f1f1f1;
+        }}
+
+        ::-webkit-scrollbar-thumb {{
+            background: #888;
+            border-radius: 4px;
+        }}
+
+        ::-webkit-scrollbar-thumb:hover {{
+            background: #555;
+        }}
+
+        .hidden {{
+            display: none !important;
+        }}
+
+        /* 关系图样式 */
+        .relationship-section {{
+            margin: 20px;
+            padding: 20px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+        }}
+
+        .relationship-section .section-header {{
+            font-size: 18px;
+            font-weight: 600;
+            color: #1f2937;
+            margin-bottom: 20px;
+            padding-bottom: 10px;
+            border-bottom: 2px solid #e5e7eb;
+        }}
+
+        .relationship-graph-container {{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            padding: 20px;
+            background: #f9fafb;
+            border-radius: 8px;
+            overflow-x: auto;
+        }}
+
+        .relationship-graph-container svg {{
+            max-width: 100%;
+            height: auto;
+        }}
+
+        /* Tab导航样式 */
+        .tab-navigation {{
+            display: flex;
+            gap: 4px;
+            background: white;
+            padding: 10px 20px 0;
+            margin: 0 20px;
+            border-bottom: 2px solid #e5e7eb;
+        }}
+
+        .tab-button {{
+            padding: 12px 24px;
+            background: none;
+            border: none;
+            border-bottom: 3px solid transparent;
+            cursor: pointer;
+            font-size: 15px;
+            font-weight: 500;
+            color: #6b7280;
+            transition: all 0.2s;
+            position: relative;
+            top: 2px;
+        }}
+
+        .tab-button:hover {{
+            color: #374151;
+            background: #f9fafb;
+            border-radius: 6px 6px 0 0;
+        }}
+
+        .tab-button.active {{
+            color: #2563eb;
+            border-bottom-color: #2563eb;
+            font-weight: 600;
+        }}
+
+        /* Tab内容区域 */
+        .tab-content {{
+            min-height: 600px;
+        }}
+
+        .tab-pane {{
+            display: none;
+        }}
+
+        .tab-pane.active {{
+            display: block;
+        }}
+
+        /* 交互高亮样式 */
+        .clickable {{
+            cursor: pointer;
+            transition: opacity 0.3s ease, filter 0.3s ease;
+        }}
+
+        .clickable:hover {{
+            filter: brightness(1.15);
+        }}
+
+        .highlighted {{
+            filter: drop-shadow(0 0 8px currentColor);
+        }}
+
+        .highlighted circle,
+        .highlighted rect {{
+            stroke-width: 3;
+        }}
+
+        .dimmed {{
+            opacity: 0.15;
+        }}
+
+        .connection-line {{
+            transition: opacity 0.3s ease, stroke-width 0.3s ease;
+        }}
+
+        .connection-line.highlighted {{
+            stroke-width: 4;
+            opacity: 1 !important;
+        }}
+
+        .connection-line.dimmed {{
+            opacity: 0.1 !important;
+        }}
+
+        /* ========== 高相似度特征样式(绿色)========== */
+        .high-similarity-section {{
+            margin-top: 20px;
+            border-top: 2px solid #86efac;
+            padding-top: 15px;
+        }}
+
+        .high-similarity-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 12px 15px;
+            background: linear-gradient(135deg, #dcfce7 0%, #f0fdf4 100%);
+            border-radius: 8px;
+            border: 2px solid #86efac;
+        }}
+
+        .high-similarity-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #16a34a;
+        }}
+
+        .high-similarity-count {{
+            font-size: 12px;
+            font-weight: 600;
+            color: #15803d;
+            background: white;
+            padding: 2px 8px;
+            border-radius: 12px;
+        }}
+
+        .high-similarity-list {{
+            margin-top: 10px;
+        }}
+
+        .high-similarity-item {{
+            padding: 10px 12px;
+            margin: 8px 0;
+            background: white;
+            border-left: 3px solid #22c55e;
+            border-radius: 4px;
+            transition: all 0.2s ease;
+        }}
+
+        .high-similarity-item:hover {{
+            background: #f0fdf4;
+        }}
+
+        .high-feature-name {{
+            font-weight: 600;
+            color: #166534;
+            margin-bottom: 4px;
+        }}
+
+        .high-feature-score {{
+            display: inline-block;
+            font-size: 13px;
+            font-weight: 600;
+            color: #16a34a;
+            background: #dcfce7;
+            padding: 2px 8px;
+            border-radius: 4px;
+        }}
+
+        .high-feature-meta {{
+            font-size: 11px;
+            color: #9ca3af;
+            margin-top: 4px;
+        }}
+
+        /* ========== 部分匹配特征样式(橘黄色)========== */
+        .partial-similarity-section {{
+            margin-top: 20px;
+            border-top: 2px solid #fdba74;
+            padding-top: 15px;
+        }}
+
+        .partial-similarity-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 12px 15px;
+            background: linear-gradient(135deg, #fed7aa 0%, #fef3c7 100%);
+            border-radius: 8px;
+            border: 2px solid #fdba74;
+        }}
+
+        .partial-similarity-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #c2410c;
+        }}
+
+        .partial-similarity-count {{
+            font-size: 12px;
+            font-weight: 600;
+            color: #c2410c;
+            background: white;
+            padding: 2px 8px;
+            border-radius: 12px;
+        }}
+
+        .feature-header {{
+            border-left: 4px solid #f97316;
+        }}
+
+        .feature-title {{
+            color: #c2410c;
+        }}
+
+        /* ========== 右侧内容特征标题区域样式 ========== */
+        .feature-section {{
+            margin-bottom: 30px;
+        }}
+
+        .feature-section-header {{
+            padding: 16px 20px;
+            margin-bottom: 20px;
+            background: white;
+            border-radius: 8px;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+        }}
+
+        .feature-section-title {{
+            font-size: 18px;
+            font-weight: 700;
+            margin-bottom: 8px;
+        }}
+
+        .feature-section-meta {{
+            font-size: 13px;
+            color: #6b7280;
+            font-weight: 500;
+        }}
+
+        /* 高相似度特征 */
+        .feature-section-high .feature-section-header {{
+            border-left: 4px solid #22c55e;
+            background: linear-gradient(135deg, #dcfce7 0%, #f0fdf4 100%);
+        }}
+
+        .feature-section-high .feature-section-title {{
+            color: #22c55e;
+        }}
+
+        /* 部分匹配特征 */
+        .feature-section-partial .feature-section-header {{
+            border-left: 4px solid #f97316;
+            background: linear-gradient(135deg, #fed7aa 0%, #fef3c7 100%);
+        }}
+
+        .feature-section-partial .feature-section-title {{
+            color: #f97316;
+        }}
+
+        /* 低相似度特征 */
+        .feature-section-low .feature-section-header {{
+            border-left: 4px solid #dc2626;
+            background: linear-gradient(135deg, #fee2e2 0%, #fef2f2 100%);
+        }}
+
+        .feature-section-low .feature-section-title {{
+            color: #dc2626;
+        }}
+
+        /* ========== 低相似度特征样式(红色)========== */
+        .low-similarity-section {{
+            margin-top: 20px;
+            border-top: 2px solid #fca5a5;
+            padding-top: 15px;
+        }}
+
+        .low-similarity-header {{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 12px 15px;
+            background: linear-gradient(135deg, #fee2e2 0%, #fef2f2 100%);
+            border-radius: 8px;
+            cursor: pointer;
+            border: 2px solid #fca5a5;
+            transition: all 0.3s ease;
+        }}
+
+        .low-similarity-header:hover {{
+            background: linear-gradient(135deg, #fecaca 0%, #fee2e2 100%);
+            border-color: #f87171;
+        }}
+
+        .low-similarity-title {{
+            font-size: 14px;
+            font-weight: 600;
+            color: #dc2626;
+        }}
+
+        .low-similarity-count {{
+            font-size: 12px;
+            font-weight: 600;
+            color: #991b1b;
+            background: white;
+            padding: 2px 8px;
+            border-radius: 12px;
+        }}
+
+        .toggle-icon {{
+            font-size: 12px;
+            color: #dc2626;
+            transition: transform 0.3s ease;
+        }}
+
+        .toggle-icon.rotated {{
+            transform: rotate(180deg);
+        }}
+
+        .low-similarity-list {{
+            margin-top: 10px;
+        }}
+
+        .low-similarity-item {{
+            padding: 10px 12px;
+            margin: 8px 0;
+            background: white;
+            border-left: 3px solid #dc2626;
+            border-radius: 4px;
+            transition: all 0.2s ease;
+        }}
+
+        .low-similarity-item:hover {{
+            background: #fef2f2;
+        }}
+
+        .low-feature-name {{
+            font-weight: 600;
+            color: #991b1b;
+            margin-bottom: 4px;
+        }}
+
+        .low-feature-score {{
+            display: inline-block;
+            font-size: 13px;
+            font-weight: 600;
+            color: #dc2626;
+            background: #fee2e2;
+            padding: 2px 8px;
+            border-radius: 4px;
+        }}
+
+        .low-feature-meta {{
+            font-size: 11px;
+            color: #9ca3af;
+            margin-top: 4px;
+        }}
+    </style>
+</head>
+<body>
+    <!-- 统计面板 -->
+    <div class="stats-panel collapsed">
+        <div class="stats-toggle-icon">▼</div>
+        <div class="stats-container">
+            <div class="stats-row">
+                <div class="stat-item">
+                    <div class="stat-value">📊 {stats['total_features']}</div>
+                    <div class="stat-label">原始特征数</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">🔍 {stats['total_search_words']}</div>
+                    <div class="stat-label">搜索词总数</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">✅ {stats['searched_count']}</div>
+                    <div class="stat-label">已搜索 ({stats['searched_percentage']}%)</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">⏸️ {stats['not_searched_count']}</div>
+                    <div class="stat-label">未搜索</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">📝 {stats['total_notes']}</div>
+                    <div class="stat-label">帖子总数</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">🎬 {stats['video_count']}</div>
+                    <div class="stat-label">视频 ({stats['video_percentage']}%)</div>
+                </div>
+                <div class="stat-item">
+                    <div class="stat-value">📷 {stats['normal_count']}</div>
+                    <div class="stat-label">图文 ({stats['normal_percentage']}%)</div>
+                </div>
+            </div>
+            <div class="stats-row">
+                <div class="stat-item small">
+                    <div class="stat-value">⚡ {stats['total_evaluated']}</div>
+                    <div class="stat-label">已评估</div>
+                </div>
+                <div class="stat-item small">
+                    <div class="stat-value">⚫ {stats['total_filtered']}</div>
+                    <div class="stat-label">已过滤 ({stats['filter_rate']}%)</div>
+                </div>
+                <div class="stat-item small">
+                    <div class="stat-value">🟢 {stats['match_complete']}</div>
+                    <div class="stat-label">完全匹配 ({stats['complete_rate']}%)</div>
+                </div>
+                <div class="stat-item small">
+                    <div class="stat-value">🟡 {stats['match_similar']}</div>
+                    <div class="stat-label">相似匹配 ({stats['similar_rate']}%)</div>
+                </div>
+                <div class="stat-item small">
+                    <div class="stat-value">🟠 {stats['match_weak']}</div>
+                    <div class="stat-label">弱相似</div>
+                </div>
+                <div class="stat-item small">
+                    <div class="stat-value">🔴 {stats['match_none']}</div>
+                    <div class="stat-label">无匹配</div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 过滤控制面板 -->
+    <div class="filter-panel">
+        <span class="filter-label">🔍 筛选显示:</span>
+        <div class="filter-buttons">
+            <button class="filter-btn active" onclick="filterNotes('all')">全部</button>
+            <button class="filter-btn complete" onclick="filterNotes('complete')">🟢 完全匹配</button>
+            <button class="filter-btn similar" onclick="filterNotes('similar')">🟡 相似匹配</button>
+            <button class="filter-btn weak" onclick="filterNotes('weak')">🟠 弱相似</button>
+            <button class="filter-btn none" onclick="filterNotes('none')">🔴 无匹配</button>
+            <button class="filter-btn filtered" onclick="filterNotes('filtered')">⚫ 已过滤</button>
+        </div>
+    </div>
+
+    <!-- 主容器 - 级联布局 -->
+    <div class="main-container">
+        <!-- 左侧栏:原始特征列表 -->
+        <div class="left-sidebar" id="leftSidebar"></div>
+
+        <!-- 中间栏:base_word列表 -->
+        <div class="middle-sidebar" id="middleSidebar">
+            <div class="sidebar-header">匹配的人设特征</div>
+            <div class="sidebar-content" id="middleContent"></div>
+        </div>
+
+        <!-- 右侧栏:搜索词列表 -->
+        <div class="right-sidebar" id="rightSidebar">
+            <div class="sidebar-header">相关搜索词</div>
+            <div class="sidebar-content" id="rightSidebarContent"></div>
+        </div>
+
+        <!-- 详情区域:搜索结果 -->
+        <div class="detail-area" id="detailArea">
+            <div class="detail-placeholder">👈 请从左侧选择特征查看详情</div>
+            <div class="detail-content" id="detailContent"></div>
+        </div>
+    </div>
+
+    <!-- 解构结果模态窗口 -->
+    <div class="modal-overlay" id="deconstructionModal">
+        <div class="modal-window">
+            <div class="modal-header">
+                <div>
+                    <div class="modal-title">🎯 解构特征相似度分析</div>
+                    <div class="modal-note-title" id="modalNoteTitle"></div>
+                </div>
+                <button class="modal-close-btn" onclick="closeModal()">×</button>
+            </div>
+            <div class="modal-body">
+                <div class="deconstruction-content" id="modalContent"></div>
+            </div>
+        </div>
+    </div>
+
+    <!-- P值详情模态窗口 -->
+    <div class="p-detail-modal" id="pDetailModal">
+        <div class="p-detail-window">
+            <div class="p-detail-header">
+                <div class="p-detail-title">📊 综合得分P 计算详情</div>
+                <button class="p-detail-close-btn" onclick="closePDetailModal()">×</button>
+            </div>
+            <div class="p-detail-body" id="pDetailContent"></div>
+        </div>
+    </div>
+
+    <!-- 帖子浮层模态窗口 -->
+    <div class="notes-modal" id="notesModal">
+        <div class="notes-modal-window">
+            <div class="notes-modal-header">
+                <div class="notes-modal-title">📝 搜索词帖子列表</div>
+                <button class="notes-modal-close-btn" onclick="closeNotesModal()">×</button>
+            </div>
+            <div class="notes-modal-body" id="notesModalContent"></div>
+        </div>
+    </div>
+
+    <script>
+        // 数据
+        const data = {data_json};
+        const deconstructionData = {deconstruction_json};
+        const similarityData = {similarity_json};
+        const allFeatures = {all_features_json};
+        const highSimilarityFeatures = {high_similarity_json};
+        const lowSimilarityFeatures = {low_similarity_json};
+        let currentFilter = 'all';
+
+        // Tab切换功能
+        function switchTab(tabName) {{
+            // 更新Tab按钮状态
+            document.querySelectorAll('.tab-button').forEach(btn => {{
+                btn.classList.remove('active');
+            }});
+            event.target.classList.add('active');
+
+            // 更新Tab内容
+            document.querySelectorAll('.tab-pane').forEach(pane => {{
+                pane.classList.remove('active');
+            }});
+            document.getElementById('tab-' + tabName).classList.add('active');
+        }}
+
+        // 关系图节点点击高亮功能
+        let selectedNodeId = null;
+
+        function handleNodeClick(nodeId, nodeType) {{
+            const svg = document.querySelector('.relationship-graph-container svg');
+            if (!svg) return;
+
+            // 如果点击的是已选中节点,取消高亮
+            if (selectedNodeId === nodeId) {{
+                clearHighlight();
+                selectedNodeId = null;
+                return;
+            }}
+
+            selectedNodeId = nodeId;
+
+            // 找到所有相关的连接线
+            const connections = svg.querySelectorAll('.connection-line');
+            const relatedNodes = new Set([nodeId]);
+
+            connections.forEach(conn => {{
+                const from = conn.getAttribute('data-from');
+                const to = conn.getAttribute('data-to');
+
+                if (from === nodeId || to === nodeId) {{
+                    // 这是相关的连接线
+                    conn.classList.add('highlighted');
+                    conn.classList.remove('dimmed');
+
+                    // 收集相关节点
+                    relatedNodes.add(from);
+                    relatedNodes.add(to);
+                }} else {{
+                    // 非相关连接线变暗
+                    conn.classList.add('dimmed');
+                    conn.classList.remove('highlighted');
+                }}
+            }});
+
+            // 处理所有节点
+            const allNodes = svg.querySelectorAll('.clickable');
+            allNodes.forEach(node => {{
+                const nId = node.getAttribute('data-node-id');
+                if (relatedNodes.has(nId)) {{
+                    node.classList.add('highlighted');
+                    node.classList.remove('dimmed');
+                }} else {{
+                    node.classList.add('dimmed');
+                    node.classList.remove('highlighted');
+                }}
+            }});
+        }}
+
+        function clearHighlight() {{
+            const svg = document.querySelector('.relationship-graph-container svg');
+            if (!svg) return;
+
+            svg.querySelectorAll('.highlighted, .dimmed').forEach(el => {{
+                el.classList.remove('highlighted', 'dimmed');
+            }});
+        }}
+
+        // 创建评估映射
+        const noteEvaluations = {{}};
+        data.forEach((feature, fIdx) => {{
+            const groups = feature['组合评估结果_分组'] || [];
+            groups.forEach((group, gIdx) => {{
+                const searches = group['top10_searches'] || [];
+                searches.forEach((search, sIdx) => {{
+                    const evaluation = search['evaluation_with_filter'];
+                    if (evaluation && evaluation.notes_evaluation) {{
+                        evaluation.notes_evaluation.forEach(noteEval => {{
+                            const key = `${{fIdx}}-${{gIdx}}-${{sIdx}}-${{noteEval.note_index}}`;
+                            noteEvaluations[key] = noteEval;
+                        }});
+                    }}
+                }});
+            }});
+        }});
+
+        // 获取评估类别
+        function getEvalCategory(noteEval) {{
+            if (!noteEval || noteEval['Query相关性'] !== '相关') {{
+                return 'filtered';
+            }}
+            const score = noteEval['综合得分'];
+            if (score >= 0.8) return 'complete';
+            if (score >= 0.6) return 'similar';
+            if (score >= 0.5) return 'weak';
+            return 'none';
+        }}
+
+        // 提取所有候选词
+        function extractAllCandidates() {{
+            const personaCandidates = [];
+            const postCandidates = [];
+            const candidateMap = {{}};
+
+            data.forEach(feature => {{
+                const groups = feature['高相似度候选_按base_word'] || {{}};
+
+                Object.values(groups).forEach(candidateList => {{
+                    if (!Array.isArray(candidateList)) return;
+
+                    candidateList.forEach(candidate => {{
+                        const name = candidate['候选词'] || '';
+                        const type = candidate['候选词类型'] || 'unknown';
+                        const similarity = candidate['相似度'] || 0;
+
+                        if (!name || candidateMap[name]) return;
+
+                        candidateMap[name] = true;
+
+                        const candidateObj = {{
+                            name: name,
+                            type: type,
+                            similarity: similarity
+                        }};
+
+                        if (type === 'persona') {{
+                            personaCandidates.push(candidateObj);
+                        }} else if (type === 'post') {{
+                            postCandidates.push(candidateObj);
+                        }}
+                    }});
+                }});
+            }});
+
+            // 按相似度降序排序
+            personaCandidates.sort((a, b) => b.similarity - a.similarity);
+            postCandidates.sort((a, b) => b.similarity - a.similarity);
+
+            return {{
+                persona: personaCandidates,
+                post: postCandidates
+            }};
+        }}
+
+        // 渲染候选词库
+        function renderCandidateLibrary(candidates) {{
+            const personaCount = candidates.persona.length;
+            const postCount = candidates.post.length;
+
+            let html = `
+                <div class="candidate-library">
+                    <div class="candidate-header" onclick="toggleCandidateLibrary()">
+                        <div class="candidate-title">📋 候选词库(区分人设/帖子来源)</div>
+                        <div class="candidate-toggle" id="candidate-toggle">▼</div>
+                    </div>
+                    <div class="candidate-content" id="candidate-content">
+            `;
+
+            // 人设候选词
+            if (personaCount > 0) {{
+                html += `
+                    <div class="candidate-section">
+                        <div class="candidate-section-title">【人设候选词】(${{personaCount}}个)</div>
+                        <div class="candidate-list">
+                `;
+
+                candidates.persona.forEach(candidate => {{
+                    html += `
+                        <div class="candidate-item persona"
+                             data-candidate="${{candidate.name}}"
+                             onclick="highlightCandidateWord('${{candidate.name.replace(/'/g, "\\'")}}')"
+                             title="相似度: ${{candidate.similarity.toFixed(2)}}">
+                            ${{candidate.name}}
+                        </div>
+                    `;
+                }});
+
+                html += `
+                        </div>
+                    </div>
+                `;
+            }}
+
+            // 帖子候选词
+            if (postCount > 0) {{
+                html += `
+                    <div class="candidate-section">
+                        <div class="candidate-section-title">【帖子候选词】(${{postCount}}个)</div>
+                        <div class="candidate-list">
+                `;
+
+                candidates.post.forEach(candidate => {{
+                    html += `
+                        <div class="candidate-item post"
+                             data-candidate="${{candidate.name}}"
+                             onclick="highlightCandidateWord('${{candidate.name.replace(/'/g, "\\'")}}')"
+                             title="相似度: ${{candidate.similarity.toFixed(2)}}">
+                            ${{candidate.name}}
+                        </div>
+                    `;
+                }});
+
+                html += `
+                        </div>
+                    </div>
+                `;
+            }}
+
+            html += `
+                    </div>
+                </div>
+            `;
+
+            return html;
+        }}
+
+        // 渲染左侧导航
+        // 全局选中状态
+        let selectedFeatureIdx = null;
+        let selectedBaseWordIdx = null;
+        let selectedSearchWordIdx = null;
+
+        function renderLeftSidebar() {{
+            const sidebar = document.getElementById('leftSidebar');
+            let html = '';
+
+            // 添加候选词库
+            const allCandidates = extractAllCandidates();
+            html += renderCandidateLibrary(allCandidates);
+
+            // 1. 高相似度特征区域(≥0.8)
+            if (highSimilarityFeatures && highSimilarityFeatures.length > 0) {{
+                html += `
+                    <div class="high-similarity-section">
+                        <div class="high-similarity-header">
+                            <div class="high-similarity-title">📋 当前帖子-已匹配(≥0.8)</div>
+                            <div class="high-similarity-count">${{highSimilarityFeatures.length}}个</div>
+                        </div>
+                        <div class="high-similarity-list">
+                `;
+
+                highSimilarityFeatures.forEach((feature, idx) => {{
+                    const name = feature.name || '';
+                    const similarity = feature.similarity || 0;
+                    const dimension = feature.dimension || '';
+
+                    html += `
+                        <div class="high-similarity-item">
+                            <div class="high-feature-name">✓ ${{name}}</div>
+                            <div class="high-feature-score">${{similarity.toFixed(2)}}</div>
+                            <div class="high-feature-meta">${{dimension}}</div>
+                        </div>
+                    `;
+                }});
+
+                html += `
+                        </div>
+                    </div>
+                `;
+            }}
+
+            // 2. 部分匹配特征区域(0.5-0.8)
+            const partialMatchCount = data.length;
+            if (partialMatchCount > 0) {{
+                html += `
+                    <div class="partial-similarity-section">
+                        <div class="partial-similarity-header">
+                            <div class="partial-similarity-title">📋 当前帖子-部分匹配(0.5-0.8)</div>
+                            <div class="partial-similarity-count">${{partialMatchCount}}个</div>
+                        </div>
+                    </div>
+                `;
+
+                data.forEach((feature, featureIdx) => {{
+                    const featureName = feature['原始特征名称'];
+                    const featureInfo = allFeatures[featureName] || {{}};
+                    const similarity = featureInfo.similarity || 0;
+                    const dimension = featureInfo.dimension || '';
+                    const groups = feature['组合评估结果_分组'] || [];
+
+                    // 获取来源人设分类路径
+                    const top3Matches = feature['top3匹配信息'] || [];
+                    let sourcePath = '';
+                    if (top3Matches.length > 0) {{
+                        sourcePath = top3Matches[0]['所属分类路径'] || '';
+                    }}
+
+                    const isActive = selectedFeatureIdx === featureIdx;
+
+                    html += `
+                        <div class="feature-item-left ${{isActive ? 'active' : ''}}"
+                             onclick="selectFeature(${{featureIdx}})"
+                             id="feature-left-${{featureIdx}}">
+                            <div class="feature-name">🎯 ${{featureName}}</div>
+                            <div class="feature-meta-small">
+                                相似度: ${{similarity.toFixed(2)}} · ${{dimension}}
+                            </div>
+                            ${{sourcePath ? `<div class="feature-source">${{sourcePath}}</div>` : ''}}
+                            <div class="feature-stats">${{groups.length}}个base_word</div>
+                        </div>
+                    `;
+                }});
+            }}
+
+            // 3. 低相似度特征区域(<0.5)
+            if (lowSimilarityFeatures && lowSimilarityFeatures.length > 0) {{
+                html += `
+                    <div class="low-similarity-section">
+                        <div class="low-similarity-header">
+                            <div class="low-similarity-title">📋 当前帖子-未匹配(<0.5)</div>
+                            <div class="low-similarity-count">${{lowSimilarityFeatures.length}}个</div>
+                        </div>
+                        <div class="low-similarity-list">
+                `;
+
+                lowSimilarityFeatures.forEach((feature, idx) => {{
+                    const name = feature.name || '';
+                    const similarity = feature.similarity || 0;
+                    const dimension = feature.dimension || '';
+
+                    html += `
+                        <div class="low-similarity-item">
+                            <div class="low-feature-name">✗ ${{name}}</div>
+                            <div class="low-feature-score">${{similarity.toFixed(2)}}</div>
+                            <div class="low-feature-meta">${{dimension}}</div>
+                        </div>
+                    `;
+                }});
+
+                html += `
+                        </div>
+                    </div>
+                `;
+            }}
+
+            sidebar.innerHTML = html;
+        }}
+
+        // ========== 级联交互函数 ==========
+
+        // 选择特征 - 显示middle sidebar
+        function selectFeature(featureIdx) {{
+            selectedFeatureIdx = featureIdx;
+            selectedBaseWordIdx = null;
+            selectedSearchWordIdx = null;
+
+            // 更新左侧active状态
+            document.querySelectorAll('.feature-item-left').forEach((el, idx) => {{
+                if (idx === featureIdx) {{
+                    el.classList.add('active');
+                }} else {{
+                    el.classList.remove('active');
+                }}
+            }});
+
+            // 渲染middle sidebar
+            renderMiddleSidebar(featureIdx);
+
+            // 隐藏right sidebar和detail area
+            document.getElementById('rightSidebar').classList.remove('active');
+            document.getElementById('detailArea').querySelector('.detail-placeholder').style.display = 'flex';
+            document.getElementById('detailContent').innerHTML = '';
+        }}
+
+        // 渲染middle sidebar - 显示base_words
+        function renderMiddleSidebar(featureIdx) {{
+            const feature = data[featureIdx];
+            const groups = feature['组合评估结果_分组'] || [];
+
+            let html = '';
+            groups.forEach((group, groupIdx) => {{
+                const baseWord = group['base_word'] || '';
+                const baseSimilarity = group['base_word_similarity'] || 0;
+                const searches = group['top10_searches'] || [];
+
+                const relatedWords = feature['高相似度候选_按base_word']?.[baseWord] || [];
+                const relatedWordNames = relatedWords
+                    .map(w => w['人设特征名称'] || '')
+                    .filter(name => name.trim() !== '')
+                    .slice(0, 5)
+                    .join('、') || '暂无关联特征';
+
+                const isActive = selectedBaseWordIdx === groupIdx;
+
+                html += `
+                    <div class="baseword-item ${{isActive ? 'active' : ''}}"
+                         onclick="selectBaseWord(${{featureIdx}}, ${{groupIdx}})"
+                         id="baseword-${{featureIdx}}-${{groupIdx}}">
+                        <div class="cascade-item-title" style="color:#059669;">👤 ${{baseWord}}</div>
+                        <div class="cascade-item-meta">
+                            相似度: ${{baseSimilarity.toFixed(2)}} · ${{searches.length}}个搜索词
+                        </div>
+                        ${{relatedWordNames ? `<div style="font-size:10px;color:#92400e;margin-top:4px;">相关: ${{relatedWordNames}}</div>` : ''}}
+                    </div>
+                `;
+            }});
+
+            document.getElementById('middleContent').innerHTML = html;
+            document.getElementById('middleSidebar').classList.add('active');
+        }}
+
+        // 选择base_word - 显示right sidebar
+        function selectBaseWord(featureIdx, baseWordIdx) {{
+            selectedBaseWordIdx = baseWordIdx;
+            selectedSearchWordIdx = null;
+
+            // 更新middle sidebar active状态
+            document.querySelectorAll('.baseword-item').forEach((el, idx) => {{
+                if (idx === baseWordIdx) {{
+                    el.classList.add('active');
+                }} else {{
+                    el.classList.remove('active');
+                }}
+            }});
+
+            // 渲染right sidebar
+            renderRightSidebar(featureIdx, baseWordIdx);
+
+            // 隐藏detail content
+            document.getElementById('detailArea').querySelector('.detail-placeholder').style.display = 'flex';
+            document.getElementById('detailContent').innerHTML = '';
+        }}
+
+        // 渲染right sidebar - 显示search words
+        function renderRightSidebar(featureIdx, baseWordIdx) {{
+            const feature = data[featureIdx];
+            const groups = feature['组合评估结果_分组'] || [];
+            const group = groups[baseWordIdx];
+            const searches = group['top10_searches'] || [];
+
+            let html = '';
+            searches.forEach((sw, swIdx) => {{
+                const searchWord = sw.search_word || '';
+                const sourceWord = sw.source_word || '';
+                const score = sw.score || 0;
+
+                const evaluation = sw['evaluation_with_filter'];
+                let evalBadges = '';
+                if (evaluation) {{
+                    const stats = evaluation.statistics || {{}};
+                    const complete = stats['完全匹配(0.8-1.0)'] || 0;
+                    const similar = stats['相似匹配(0.6-0.79)'] || 0;
+                    const weak = stats['弱相似(0.5-0.59)'] || 0;
+                    const none = stats['无匹配(≤0.4)'] || 0;
+                    const filtered = evaluation.filtered_count || 0;
+
+                    if (complete > 0) evalBadges += `<span class="eval-badge eval-complete">🟢${{complete}}</span>`;
+                    if (similar > 0) evalBadges += `<span class="eval-badge eval-similar">🟡${{similar}}</span>`;
+                    if (weak > 0) evalBadges += `<span class="eval-badge eval-weak">🟠${{weak}}</span>`;
+                    if (none > 0) evalBadges += `<span class="eval-badge eval-none">🔴${{none}}</span>`;
+                    if (filtered > 0) evalBadges += `<span class="eval-badge eval-filtered">⚫${{filtered}}</span>`;
+                }}
+
+                const isActive = selectedSearchWordIdx === swIdx;
+                const reasoning = sw.reasoning || '';
+                const reasoningId = `reasoning-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}`;
+
+                html += `
+                    <div class="searchword-item ${{isActive ? 'active' : ''}}"
+                         onclick="selectSearchWord(${{featureIdx}}, ${{baseWordIdx}}, ${{swIdx}})"
+                         id="searchword-${{featureIdx}}-${{baseWordIdx}}-${{swIdx}}">
+                        <div class="cascade-item-title">📝 ${{searchWord}}</div>
+                        <div style="font-size:11px;color:#6b7280;margin-top:4px;">
+                            组合词: ${{sourceWord}} → [${{score.toFixed(2)}}]
+                        </div>
+                        ${{evalBadges ? `<div style="margin-top:4px;">${{evalBadges}}</div>` : ''}}
+                        ${{reasoning ? `
+                            <div class="search-word-reasoning">
+                                <span class="reasoning-label">💭 推理理由:</span>
+                                <div class="reasoning-text" id="${{reasoningId}}-text">
+                                    ${{reasoning}}
+                                </div>
+                                <div class="reasoning-toggle"
+                                     onclick="event.stopPropagation(); toggleSearchWordReasoning('${{reasoningId}}')">
+                                    展开 ▼
+                                </div>
+                            </div>
+                        ` : ''}}
+                    </div>
+                `;
+            }});
+
+            document.getElementById('rightSidebarContent').innerHTML = html;
+            document.getElementById('rightSidebar').classList.add('active');
+        }}
+
+        // 折叠/展开推理理由
+        function toggleSearchWordReasoning(reasoningId) {{
+            const textEl = document.getElementById(reasoningId + '-text');
+            const toggleBtn = event.currentTarget;
+
+            if (textEl.classList.contains('expanded')) {{
+                // 当前是展开状态,收起
+                textEl.classList.remove('expanded');
+                toggleBtn.innerHTML = '展开 ▼';
+            }} else {{
+                // 当前是收起状态,展开
+                textEl.classList.add('expanded');
+                toggleBtn.innerHTML = '收起 ▲';
+            }}
+        }}
+
+        // 选择search word - 显示detail area
+        function selectSearchWord(featureIdx, baseWordIdx, swIdx) {{
+            selectedSearchWordIdx = swIdx;
+
+            // 更新right sidebar active状态
+            document.querySelectorAll('.searchword-item').forEach((el, idx) => {{
+                if (idx === swIdx) {{
+                    el.classList.add('active');
+                }} else {{
+                    el.classList.remove('active');
+                }}
+            }});
+
+            // 渲染detail area
+            renderDetailArea(featureIdx, baseWordIdx, swIdx);
+        }}
+
+        // 渲染detail area - 显示搜索结果
+        function renderDetailArea(featureIdx, baseWordIdx, swIdx) {{
+            const feature = data[featureIdx];
+            const groups = feature['组合评估结果_分组'] || [];
+            const group = groups[baseWordIdx];
+            const searches = group['top10_searches'] || [];
+            const sw = searches[swIdx];
+
+            const searchWord = sw.search_word || '';
+            const sourceWord = sw.source_word || '';
+            const searchResult = sw.search_result || {{}};
+            const notes = searchResult.data?.data || [];
+
+            // 隐藏占位符
+            document.getElementById('detailArea').querySelector('.detail-placeholder').style.display = 'none';
+
+            // 渲染搜索结果
+            let html = `
+                <div class="search-result-header" style="padding:20px;background:#f9fafb;border-bottom:2px solid #e5e7eb;">
+                    <h3 style="margin:0 0 10px 0;">📝 ${{searchWord}}</h3>
+                    <div style="font-size:12px;color:#6b7280;">
+                        组合词: ${{sourceWord}} · ${{notes.length}}个搜索结果
+                    </div>
+                </div>
+                <div class="notes-grid" style="padding:20px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:15px;">
+            `;
+
+            notes.forEach((note, noteIdx) => {{
+                const noteCard = note.note_card || {{}};
+                const title = noteCard.display_title || '无标题';
+                const cover = (noteCard.image_list && noteCard.image_list[0]) || noteCard.cover?.url_default || '';
+                const noteId = note.id || note.note_id || '';
+                const type = noteCard.type || 'normal';
+                const typeIcon = type === 'video' ? '🎬' : '📷';
+
+                // 获取评估信息
+                const noteEval = sw.evaluation_with_filter?.notes_with_scores?.find(n => n.note_id === noteId || n.id === noteId);
+                const evalScore = noteEval?.evaluation_score || 0;
+                const matchLevel = noteEval?.match_level || '未评估';
+
+                let matchClass = '';
+                let matchColor = '';
+                if (matchLevel.includes('完全匹配')) {{
+                    matchClass = 'match-complete';
+                    matchColor = '#22c55e';
+                }} else if (matchLevel.includes('相似匹配')) {{
+                    matchClass = 'match-similar';
+                    matchColor = '#f59e0b';
+                }} else if (matchLevel.includes('弱相似')) {{
+                    matchClass = 'match-weak';
+                    matchColor = '#f97316';
+                }} else if (matchLevel.includes('无匹配')) {{
+                    matchClass = 'match-none';
+                    matchColor = '#dc2626';
+                }} else {{
+                    matchClass = 'match-filtered';
+                    matchColor = '#6b7280';
+                }}
+
+                html += `
+                    <div class="note-card ${{matchClass}}" style="border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;background:white;cursor:pointer;transition:all 0.2s;border-left:4px solid ${{matchColor}};"
+                         onclick="openDeconstructionModal('${{noteId}}')">
+                        ${{cover ? `<img src="${{cover}}" style="width:100%;height:180px;object-fit:cover;">` : `<div style="width:100%;height:180px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;color:#9ca3af;">${{typeIcon}}</div>`}}
+                        <div style="padding:12px;">
+                            <div style="font-size:14px;font-weight:600;margin-bottom:8px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;">
+                                ${{title}}
+                            </div>
+                            <div style="font-size:11px;color:#6b7280;display:flex;justify-content:space-between;align-items:center;">
+                                <span>${{typeIcon}} ${{type}}</span>
+                                <span style="color:${{matchColor}};font-weight:600;">${{evalScore.toFixed(2)}}</span>
+                            </div>
+                        </div>
+                    </div>
+                `;
+            }});
+
+            html += `
+                </div>
+            `;
+
+            document.getElementById('detailContent').innerHTML = html;
+        }}
+
+        // 渲染右侧结果区
+        function renderRightContent() {{
+            const content = document.getElementById('rightContent');
+            let html = '';
+
+            data.forEach((feature, featureIdx) => {{
+                const groups = feature['组合评估结果_分组'] || [];
+
+                // 获取特征信息
+                const featureName = feature['原始特征名称'];
+                const featureInfo = allFeatures[featureName] || {{}};
+                const similarity = featureInfo.similarity || 0;
+                const weight = featureInfo.weight || 0;
+                const dimension = featureInfo.dimension || '';
+                const category = featureInfo.category || '';
+
+                // 根据相似度确定样式
+                let featureTitleClass = '';
+                let featureIcon = '';
+                let matchLabel = '';
+                let featureColor = '';
+
+                if (similarity >= 0.8) {{
+                    featureTitleClass = 'feature-section-high';
+                    featureIcon = '🟢';
+                    matchLabel = '已匹配';
+                    featureColor = '#22c55e';
+                }} else if (similarity >= 0.5) {{
+                    featureTitleClass = 'feature-section-partial';
+                    featureIcon = '🟡';
+                    matchLabel = '部分匹配';
+                    featureColor = '#f97316';
+                }} else {{
+                    featureTitleClass = 'feature-section-low';
+                    featureIcon = '🔴';
+                    matchLabel = '未匹配';
+                    featureColor = '#dc2626';
+                }}
+
+                // 添加特征标题区域
+                html += `
+                    <div class="feature-section ${{featureTitleClass}}" id="feature-section-${{featureIdx}}">
+                        <div class="feature-section-header" style="border-left: 4px solid ${{featureColor}};">
+                            <div class="feature-section-title" style="color: ${{featureColor}};">
+                                ${{featureIcon}} ${{featureName}}
+                            </div>
+                            <div class="feature-section-meta">
+                                ${{matchLabel}} · 相似度: ${{similarity.toFixed(2)}} · ${{dimension}}
+                            </div>
+                        </div>
+                    </div>
+                `;
+
+                groups.forEach((group, groupIdx) => {{
+                    const searches = group['top10_searches'] || [];
+
+                    searches.forEach((sw, swIdx) => {{
+                        const blockId = `block-${{featureIdx}}-${{groupIdx}}-${{swIdx}}`;
+                        const hasSearchResult = sw.search_result != null;
+                        const searchResult = sw.search_result || {{}};
+                        const notes = searchResult.data?.data || [];
+
+                        const videoCount = notes.filter(n => n.note_card?.type === 'video').length;
+                        const normalCount = notes.length - videoCount;
+
+                        const evaluation = sw['evaluation_with_filter'];
+                        let evalStats = '';
+                        if (evaluation) {{
+                            const stats = evaluation.statistics || {{}};
+                            const complete = stats['完全匹配(0.8-1.0)'] || 0;
+                            const similar = stats['相似匹配(0.6-0.79)'] || 0;
+                            const weak = stats['弱相似(0.5-0.59)'] || 0;
+                            const none = stats['无匹配(≤0.4)'] || 0;
+                            const filtered = evaluation.filtered_count || 0;
+
+                            if (complete > 0) evalStats += `<span class="stat-badge eval complete">🟢 完全:${{complete}}</span>`;
+                            if (similar > 0) evalStats += `<span class="stat-badge eval similar">🟡 相似:${{similar}}</span>`;
+                            if (weak > 0) evalStats += `<span class="stat-badge eval weak">🟠 弱:${{weak}}</span>`;
+                            if (none > 0) evalStats += `<span class="stat-badge eval none">🔴 无:${{none}}</span>`;
+                            if (filtered > 0) evalStats += `<span class="stat-badge eval filtered">⚫ 过滤:${{filtered}}</span>`;
+                        }}
+
+                        html += `
+                            <div class="result-block" id="${{blockId}}">
+                                <div class="result-header">
+                                    <div class="result-title">${{sw.search_word}}</div>
+                                    <div class="result-stats">
+                        `;
+
+                        if (!hasSearchResult) {{
+                            html += `<span class="stat-badge" style="background:#fef3c7;color:#92400e;font-weight:600">⏸️ 未执行搜索</span>`;
+                        }} else if (notes.length === 0) {{
+                            html += `
+                                <span class="stat-badge">📝 0 条帖子</span>
+                                <span class="stat-badge" style="background:#fee2e2;color:#991b1b;font-weight:600">❌ 未找到匹配</span>
+                            `;
+                        }} else {{
+                            html += `
+                                <span class="stat-badge">📝 ${{notes.length}} 条帖子</span>
+                                <span class="stat-badge">🎬 ${{videoCount}} 视频</span>
+                                <span class="stat-badge">📷 ${{normalCount}} 图文</span>
+                                ${{evalStats}}
+                            `;
+                        }}
+
+                        html += `</div></div>`;
+
+                        if (!hasSearchResult) {{
+                            html += `
+                                <div class="empty-state">
+                                    <div class="empty-icon">⏸️</div>
+                                    <div class="empty-title">该搜索词未执行搜索</div>
+                                    <div class="empty-desc">由于搜索次数限制,该搜索词未被执行</div>
+                                </div>
+                            `;
+                        }} else if (notes.length === 0) {{
+                            html += `
+                                <div class="empty-state">
+                                    <div class="empty-icon">❌</div>
+                                    <div class="empty-title">搜索完成,但未找到匹配的帖子</div>
+                                    <div class="empty-desc">该搜索词已执行,但小红书返回了 0 条结果</div>
+                                </div>
+                            `;
+                        }} else {{
+                            html += `
+                                <div class="notes-grid">
+                                    ${{notes.map((note, noteIdx) => renderNoteCard(note, featureIdx, groupIdx, swIdx, noteIdx)).join('')}}
+                                </div>
+                            `;
+                        }}
+
+                        html += `</div>`;
+                    }});
+                }});
+            }});
+
+            content.innerHTML = html;
+        }}
+
+        // 渲染单个帖子卡片
+        function renderNoteCard(note, featureIdx, groupIdx, swIdx, noteIdx) {{
+            const card = note.note_card || {{}};
+            const images = card.image_list || [];
+            const title = card.display_title || '无标题';
+            const noteType = card.type || 'normal';
+            const noteId = note.id || '';
+            const user = card.user || {{}};
+            const userName = user.nick_name || '未知用户';
+            const userAvatar = user.avatar || '';
+
+            const carouselId = `carousel-${{featureIdx}}-${{groupIdx}}-${{swIdx}}-${{noteIdx}}`;
+
+            const evalKey = `${{featureIdx}}-${{groupIdx}}-${{swIdx}}-${{noteIdx}}`;
+            const noteEval = noteEvaluations[evalKey];
+            const evalCategory = getEvalCategory(noteEval);
+            const evalClass = `eval-${{evalCategory}}`;
+
+            let evalSection = '';
+            if (noteEval) {{
+                const score = noteEval['综合得分'];
+                const scoreEmoji = score >= 0.8 ? '🟢' : score >= 0.6 ? '🟡' : score >= 0.5 ? '🟠' : '🔴';
+                const scoreText = score >= 0.8 ? '完全匹配' : score >= 0.6 ? '相似匹配' : score >= 0.5 ? '弱相似' : '无匹配';
+                const reasoning = noteEval['评分说明'] || '无';
+                const matchingPoints = (noteEval['关键匹配点'] || []).join('、') || '无';
+
+                evalSection = `
+                    <div class="note-eval">
+                        <div class="note-eval-header" onclick="event.stopPropagation(); toggleEvalDetails('${{carouselId}}')">
+                            <span class="note-eval-score">${{scoreEmoji}} ${{scoreText}} (${{score}}分)</span>
+                            <span class="note-eval-toggle" id="${{carouselId}}-toggle">▼ 详情</span>
+                        </div>
+                        <div class="note-eval-details" id="${{carouselId}}-details">
+                            <div class="eval-detail-label">评估理由:</div>
+                            <div class="eval-detail-text">${{reasoning}}</div>
+                            <div class="eval-detail-label">匹配要点:</div>
+                            <div class="eval-detail-text">${{matchingPoints}}</div>
+                        </div>
+                    </div>
+                `;
+            }} else if (evalCategory === 'filtered') {{
+                evalSection = `
+                    <div class="note-eval">
+                        <div class="note-eval-score">⚫ 已过滤(与搜索无关)</div>
+                    </div>
+                `;
+            }}
+
+            // 检查是否有解构数据(仅完全匹配)
+            const hasSimilarity = !!similarityData[noteId];
+            const similarityFeatures = hasSimilarity ? (similarityData[noteId].deconstructed_features || []) : [];
+            const hasValidFeatures = similarityFeatures.length > 0;
+            const hasDeconstruction = evalCategory === 'complete' && hasValidFeatures;
+
+            // 调试日志: 记录解构按钮判断过程
+            if (noteId === '67e554a300000000090148a7' || hasDeconstruction) {{
+                console.log(`[解构按钮] noteId=${{noteId}}, evalCategory=${{evalCategory}}, hasSimilarity=${{hasSimilarity}}, 特征数=${{similarityFeatures.length}}, 显示按钮=${{hasDeconstruction}}`);
+            }}
+
+            let deconstructionSection = '';
+
+            if (hasDeconstruction) {{
+                deconstructionSection = `
+                    <button class="deconstruction-toggle-btn" data-note-id="${{noteId}}" data-note-title="${{title.replace(/"/g, '&quot;')}}">
+                        <span>📊</span>
+                        <span>查看解构结果</span>
+                    </button>
+                `;
+            }}
+
+            return `
+                <div class="note-card ${{evalClass}}" data-eval-category="${{evalCategory}}" onclick="openNoteImagesModal(${{featureIdx}}, ${{groupIdx}}, ${{swIdx}}, ${{noteIdx}})">
+                    <div class="image-carousel" id="${{carouselId}}">
+                        <div class="carousel-images">
+                            ${{images.map(img => `<img class="carousel-image" src="${{img}}" alt="帖子图片" loading="lazy">`).join('')}}
+                        </div>
+                        ${{images.length > 1 ? `
+                            <button class="carousel-btn prev" onclick="event.stopPropagation(); changeImage('${{carouselId}}', -1)">←</button>
+                            <button class="carousel-btn next" onclick="event.stopPropagation(); changeImage('${{carouselId}}', 1)">→</button>
+                            <div class="carousel-indicators">
+                                ${{images.map((_, i) => `<span class="dot ${{i === 0 ? 'active' : ''}}" onclick="event.stopPropagation(); goToImage('${{carouselId}}', ${{i}})"></span>`).join('')}}
+                            </div>
+                            <span class="image-counter">1/${{images.length}}</span>
+                        ` : ''}}
+                    </div>
+                    <div class="note-info">
+                        <div class="note-title">${{title}}</div>
+                        <div class="note-meta">
+                            <span class="note-type type-${{noteType}}">
+                                ${{noteType === 'video' ? '🎬 视频' : '📷 图文'}}
+                            </span>
+                            <div class="note-author">
+                                ${{userAvatar ? `<img class="author-avatar" src="${{userAvatar}}" alt="${{userName}}">` : ''}}
+                                <span>${{userName}}</span>
+                            </div>
+                        </div>
+                    </div>
+                    ${{evalSection}}
+                    ${{deconstructionSection}}
+                </div>
+            `;
+        }}
+
+        // 打开解构模态窗口
+        function openDeconstructionModal(noteId, noteTitle) {{
+            console.log('🔧 [调试] openDeconstructionModal被调用, noteId:', noteId);
+
+            const modal = document.getElementById('deconstructionModal');
+            const modalContent = document.getElementById('modalContent');
+            const modalNoteTitle = document.getElementById('modalNoteTitle');
+
+            if (!modal || !modalContent || !modalNoteTitle) {{
+                console.error('❌ [错误] 无法找到模态窗口元素');
+                return;
+            }}
+
+            // 设置标题
+            modalNoteTitle.textContent = noteTitle || '解构分析';
+
+            // 检查是否有数据
+            const hasSimilarityData = !!similarityData[noteId];
+            console.log('📊 [调试] 相似度数据存在:', hasSimilarityData);
+
+            if (!hasSimilarityData) {{
+                console.warn('⚠️ [警告] 未找到相似度数据, noteId:', noteId);
+                console.log('📋 [调试] 可用的noteId列表:', Object.keys(similarityData));
+                modalContent.innerHTML = '<div style="padding: 30px; text-align: center; color: #6b7280;">暂无解构数据</div>';
+            }} else {{
+                try {{
+                    modalContent.innerHTML = renderDeconstructionContent(noteId);
+                    console.log('✅ [调试] 解构内容渲染成功');
+                }} catch (error) {{
+                    console.error('❌ [错误] 渲染解构内容失败:', error);
+                    modalContent.innerHTML = `<div style="padding: 30px; text-align: center; color: red;">渲染错误: ${{error.message}}</div>`;
+                }}
+            }}
+
+            // 显示模态窗口
+            modal.classList.add('active');
+            document.body.style.overflow = 'hidden'; // 禁止背景滚动
+            console.log('✅ [调试] 模态窗口已显示');
+        }}
+
+        // 关闭模态窗口
+        function closeModal() {{
+            console.log('🔧 [调试] closeModal被调用');
+            const modal = document.getElementById('deconstructionModal');
+            if (modal) {{
+                modal.classList.remove('active');
+                document.body.style.overflow = ''; // 恢复滚动
+                console.log('✅ [调试] 模态窗口已关闭');
+            }}
+        }}
+
+        // ESC键关闭模态窗口
+        document.addEventListener('keydown', function(e) {{
+            if (e.key === 'Escape') {{
+                const modal = document.getElementById('deconstructionModal');
+                if (modal && modal.classList.contains('active')) {{
+                    closeModal();
+                }}
+            }}
+        }});
+
+        // 点击遮罩层关闭模态窗口
+        document.addEventListener('click', function(e) {{
+            const modal = document.getElementById('deconstructionModal');
+            if (e.target === modal) {{
+                closeModal();
+            }}
+
+            const pModal = document.getElementById('pDetailModal');
+            if (e.target === pModal) {{
+                closePDetailModal();
+            }}
+        }});
+
+        // 打开P值详情模态窗口
+        function openPDetailModal(searchWord, featureIdx, groupIdx, swIdx) {{
+            console.log('🔧 [调试] openPDetailModal被调用, searchWord:', searchWord);
+
+            const modal = document.getElementById('pDetailModal');
+            const modalContent = document.getElementById('pDetailContent');
+
+            if (!modal || !modalContent) {{
+                console.error('❌ [错误] 无法找到P值详情模态窗口元素');
+                return;
+            }}
+
+            // 获取对应的搜索词数据
+            const feature = data[featureIdx];
+            const group = feature['组合评估结果_分组'][groupIdx];
+            const sw = group['top10_searches'][swIdx];
+
+            const pScore = sw.comprehensive_score;
+            const pDetail = sw.comprehensive_score_detail;
+
+            if (!pDetail) {{
+                console.warn('⚠️ [警告] 未找到P值详情数据');
+                modalContent.innerHTML = '<div style="padding: 30px; text-align: center; color: #6b7280;">暂无P值详情数据</div>';
+            }} else {{
+                try {{
+                    modalContent.innerHTML = renderPDetailContent(searchWord, feature['原始特征名称'], sw.source_word, pScore, pDetail);
+                    console.log('✅ [调试] P值详情内容渲染成功');
+                }} catch (error) {{
+                    console.error('❌ [错误] 渲染P值详情内容失败:', error);
+                    modalContent.innerHTML = `<div style="padding: 30px; text-align: center; color: red;">渲染错误: ${{error.message}}</div>`;
+                }}
+            }}
+
+            // 显示模态窗口
+            modal.classList.add('active');
+            document.body.style.overflow = 'hidden';
+            console.log('✅ [调试] P值详情模态窗口已显示');
+        }}
+
+        // 关闭P值详情模态窗口
+        function closePDetailModal() {{
+            console.log('🔧 [调试] closePDetailModal被调用');
+            const modal = document.getElementById('pDetailModal');
+            if (modal) {{
+                modal.classList.remove('active');
+                document.body.style.overflow = '';
+                console.log('✅ [调试] P值详情模态窗口已关闭');
+            }}
+        }}
+
+        // 渲染P值详情内容
+        function renderPDetailContent(searchWord, targetFeature, sourceWord, pScore, pDetail) {{
+            const N = pDetail.N || 0;
+            const M = pDetail.M || 0;
+            const totalContribution = pDetail.total_contribution || 0;
+            const matches = pDetail.complete_matches || [];
+
+            let html = `
+                <div class="p-formula-section">
+                    <div class="p-formula-title">📐 计算公式</div>
+                    <div class="p-formula">P = Σ(a × b) / N = ${{totalContribution.toFixed(3)}} / ${{N}} = ${{pScore.toFixed(3)}}</div>
+                    <div style="font-size: 12px; color: #64748b; margin-top: 8px;">
+                        <div>• a: 评估得分(综合得分)</div>
+                        <div>• b: 最高相似度(解构特征与目标特征的最高相似度)</div>
+                        <div>• N: 总帖子数</div>
+                        <div>• M: 完全匹配数(得分 ≥ 0.8)</div>
+                    </div>
+                </div>
+
+                <div class="p-summary">
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">搜索词</div>
+                        <div class="p-summary-value" style="font-size: 16px;">${{searchWord}}</div>
+                    </div>
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">来源</div>
+                        <div class="p-summary-value" style="font-size: 16px;">${{sourceWord}}</div>
+                    </div>
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">目标特征</div>
+                        <div class="p-summary-value" style="font-size: 16px;">${{targetFeature}}</div>
+                    </div>
+                </div>
+
+                <div class="p-summary">
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">总帖子数 (N)</div>
+                        <div class="p-summary-value">${{N}}</div>
+                    </div>
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">完全匹配数 (M)</div>
+                        <div class="p-summary-value highlight">${{M}}</div>
+                    </div>
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">总贡献值</div>
+                        <div class="p-summary-value highlight">${{totalContribution.toFixed(3)}}</div>
+                    </div>
+                    <div class="p-summary-item">
+                        <div class="p-summary-label">综合得分 (P)</div>
+                        <div class="p-summary-value highlight">${{pScore.toFixed(3)}}</div>
+                    </div>
+                </div>
+            `;
+
+            if (matches.length > 0) {{
+                html += `
+                    <div class="p-matches-section">
+                        <div class="p-matches-title">🎯 完全匹配帖子详情 (${{M}}个)</div>
+                `;
+
+                matches.forEach((match, idx) => {{
+                    html += `
+                        <div class="p-match-item">
+                            <div class="p-match-header">
+                                <div class="p-match-title">${{idx + 1}}. ${{match.note_title || '未知标题'}}</div>
+                                <div class="p-match-contribution">+${{match.contribution.toFixed(3)}}</div>
+                            </div>
+                            <div class="p-match-details">
+                                <div class="p-match-detail-item">
+                                    <span class="p-match-detail-label">评估得分 (a):</span>
+                                    <span>${{match.evaluation_score.toFixed(3)}}</span>
+                                </div>
+                                <div class="p-match-detail-item">
+                                    <span class="p-match-detail-label">最高相似度 (b):</span>
+                                    <span>${{match.max_similarity.toFixed(3)}}</span>
+                                </div>
+                                <div class="p-match-detail-item">
+                                    <span class="p-match-detail-label">贡献值 (a×b):</span>
+                                    <span>${{match.contribution.toFixed(3)}}</span>
+                                </div>
+                            </div>
+                        </div>
+                    `;
+                }});
+
+                html += `</div>`;
+            }} else {{
+                html += `
+                    <div class="p-matches-section">
+                        <div style="text-align: center; padding: 30px; color: #6b7280;">
+                            没有完全匹配的帖子(得分 ≥ 0.8)
+                        </div>
+                    </div>
+                `;
+            }}
+
+            return html;
+        }}
+
+        // 渲染解构内容
+        function renderDeconstructionContent(noteId) {{
+            const similarityInfo = similarityData[noteId];
+            if (!similarityInfo) {{
+                return `<div style="padding: 30px; text-align: center; color: #6b7280;">
+                    <div style="font-size: 48px; margin-bottom: 10px;">📭</div>
+                    <div style="font-size: 16px; margin-bottom: 8px;">暂无解构数据</div>
+                    <div style="font-size: 14px; opacity: 0.7;">该帖子未进行特征解构分析</div>
+                </div>`;
+            }}
+
+            const originalFeature = similarityInfo.original_feature || '未知特征';
+            const features = similarityInfo.deconstructed_features || [];
+
+            if (features.length === 0) {{
+                return `<div style="padding: 30px; text-align: center; color: #6b7280;">
+                    <div style="font-size: 48px; margin-bottom: 10px;">🔍</div>
+                    <div style="font-size: 16px; margin-bottom: 8px;">未提取到解构特征</div>
+                    <div style="font-size: 14px; opacity: 0.7;">原始特征:"${{originalFeature}}"</div>
+                    <div style="font-size: 13px; opacity: 0.6; margin-top: 10px;">该帖子虽然评分较高,但AI未能从中提取到有效的解构特征</div>
+                </div>`;
+            }}
+
+            // 按维度分组
+            const dimensionGroups = {{}};
+            features.forEach(feat => {{
+                const dim = feat.dimension || '未分类';
+                if (!dimensionGroups[dim]) {{
+                    dimensionGroups[dim] = [];
+                }}
+                dimensionGroups[dim].push(feat);
+            }});
+
+            // 为每个维度找出最高分
+            Object.keys(dimensionGroups).forEach(dim => {{
+                const feats = dimensionGroups[dim];
+                if (feats.length > 0) {{
+                    const maxScore = Math.max(...feats.map(f => f.similarity_score || 0));
+                    feats.forEach(f => {{
+                        f.isTopInDimension = (f.similarity_score === maxScore);
+                    }});
+                }}
+            }});
+
+            let html = `
+                <div class="deconstruction-header">
+                    <div>🎯 解构特征相似度分析</div>
+                    <div class="original-feature">目标特征: "${{originalFeature}}"</div>
+                </div>
+            `;
+
+            // 按维度排序: 灵感点 -> 目的点 -> 关键点
+            const dimensionOrder = ['灵感点-全新内容', '灵感点-共性差异', '灵感点-共性内容', '目的点', '关键点'];
+            const sortedDimensions = Object.keys(dimensionGroups).sort((a, b) => {{
+                const aIndex = dimensionOrder.findIndex(d => a.startsWith(d));
+                const bIndex = dimensionOrder.findIndex(d => b.startsWith(d));
+                if (aIndex === -1 && bIndex === -1) return a.localeCompare(b);
+                if (aIndex === -1) return 1;
+                if (bIndex === -1) return -1;
+                return aIndex - bIndex;
+            }});
+
+            sortedDimensions.forEach((dimension, dimIdx) => {{
+                const feats = dimensionGroups[dimension];
+                const dimId = `dim-${{noteId}}-${{dimIdx}}`;
+
+                html += `
+                    <div class="dimension-card">
+                        <div class="dimension-header" onclick="event.stopPropagation(); toggleDimension('${{dimId}}')">
+                            <div class="dimension-title">
+                                <span>${{getDimensionIcon(dimension)}} ${{dimension}}</span>
+                                <span class="dimension-count">(${{feats.length}}个特征)</span>
+                            </div>
+                            <span class="dimension-toggle" id="${{dimId}}-toggle">▼</span>
+                        </div>
+                        <div class="dimension-body expanded" id="${{dimId}}">
+                            <div class="feature-list">
+                `;
+
+                // 按分数降序排列
+                feats.sort((a, b) => (b.similarity_score || 0) - (a.similarity_score || 0));
+
+                feats.forEach(feat => {{
+                    const score = feat.similarity_score || 0;
+                    const scoreClass = score >= 0.7 ? 'high' : score >= 0.5 ? 'medium' : 'low';
+                    const barWidth = Math.min(score * 100, 100);
+                    const isTop = feat.isTopInDimension;
+
+                    html += `
+                        <div class="feature-item ${{isTop ? 'top-score' : ''}}">
+                            <div class="feature-name">
+                                ${{isTop ? '<span class="top-badge">🏆 最高分</span>' : ''}}
+                                ${{feat.feature_name || '未命名特征'}}
+                            </div>
+                            <div class="feature-meta-row">
+                                <span class="feature-dimension-detail">${{feat.dimension_detail || '无分类'}}</span>
+                                <span class="feature-weight">权重: ${{(feat.weight || 0).toFixed(1)}}</span>
+                            </div>
+                            <div class="similarity-row">
+                                <span class="similarity-score ${{scoreClass}}">${{score.toFixed(3)}}</span>
+                                <div class="similarity-bar-container">
+                                    <div class="similarity-bar ${{scoreClass}}" style="width: ${{barWidth}}%"></div>
+                                </div>
+                            </div>
+                            <div class="similarity-explanation">
+                                ${{feat.similarity_explanation || '无说明'}}
+                            </div>
+                        </div>
+                    `;
+                }});
+
+                html += `
+                            </div>
+                        </div>
+                    </div>
+                `;
+            }});
+
+            return html;
+        }}
+
+        // 获取维度图标
+        function getDimensionIcon(dimension) {{
+            if (dimension.includes('灵感点')) return '💡';
+            if (dimension.includes('目的点')) return '🎯';
+            if (dimension.includes('关键点')) return '🔑';
+            return '📋';
+        }}
+
+        // 切换维度卡片
+        function toggleDimension(dimId) {{
+            const body = document.getElementById(dimId);
+            const toggle = document.getElementById(`${{dimId}}-toggle`);
+
+            if (body.classList.contains('expanded')) {{
+                body.classList.remove('expanded');
+                toggle.textContent = '▶';
+            }} else {{
+                body.classList.add('expanded');
+                toggle.textContent = '▼';
+            }}
+        }}
+
+        // 切换候选词库展开/收起
+        function toggleCandidateLibrary() {{
+            const content = document.getElementById('candidate-content');
+            const toggle = document.getElementById('candidate-toggle');
+
+            if (content.classList.contains('expanded')) {{
+                content.classList.remove('expanded');
+                toggle.classList.remove('rotated');
+                toggle.textContent = '▼';
+            }} else {{
+                content.classList.add('expanded');
+                toggle.classList.add('rotated');
+                toggle.textContent = '▲';
+            }}
+        }}
+
+        // 切换推理展开/收起
+        function toggleReasoning(reasoningId) {{
+            const reasoningDiv = document.getElementById(reasoningId);
+            const toggle = reasoningDiv.previousElementSibling;
+
+            if (reasoningDiv.classList.contains('expanded')) {{
+                reasoningDiv.classList.remove('expanded');
+                toggle.innerHTML = '点击查看推理 ▼';
+            }} else {{
+                reasoningDiv.classList.add('expanded');
+                toggle.innerHTML = '收起推理 ▲';
+            }}
+        }}
+
+        // 高亮候选词及其相关搜索词
+        let currentHighlightedCandidate = null;
+
+        function highlightCandidateWord(candidateWord) {{
+            // 如果点击的是已高亮的候选词,取消高亮
+            if (currentHighlightedCandidate === candidateWord) {{
+                clearCandidateHighlight();
+                currentHighlightedCandidate = null;
+                return;
+            }}
+
+            // 清除之前的高亮
+            clearCandidateHighlight();
+
+            // 高亮候选词
+            const candidateItems = document.querySelectorAll('.candidate-item');
+            candidateItems.forEach(item => {{
+                if (item.getAttribute('data-candidate') === candidateWord) {{
+                    item.classList.add('highlighted');
+                }}
+            }});
+
+            // 高亮包含该候选词的搜索词
+            const searchWordItems = document.querySelectorAll('.search-word-item');
+            searchWordItems.forEach(item => {{
+                const sourceWords = item.getAttribute('data-source-words') || '';
+                // 检查source_words中是否包含该候选词
+                if (sourceWords.includes(candidateWord)) {{
+                    item.classList.add('highlighted');
+                }}
+            }});
+
+            currentHighlightedCandidate = candidateWord;
+        }}
+
+        // 清除候选词高亮
+        function clearCandidateHighlight() {{
+            // 清除候选词高亮
+            const candidateItems = document.querySelectorAll('.candidate-item.highlighted');
+            candidateItems.forEach(item => {{
+                item.classList.remove('highlighted');
+            }});
+
+            // 清除搜索词高亮
+            const searchWordItems = document.querySelectorAll('.search-word-item.highlighted');
+            searchWordItems.forEach(item => {{
+                item.classList.remove('highlighted');
+            }});
+        }}
+
+        // 图片轮播逻辑
+        const carouselStates = {{}};
+
+        function changeImage(carouselId, direction) {{
+            if (!carouselStates[carouselId]) {{
+                carouselStates[carouselId] = {{ currentIndex: 0 }};
+            }}
+
+            const carousel = document.getElementById(carouselId);
+            const imagesContainer = carousel.querySelector('.carousel-images');
+            const images = carousel.querySelectorAll('.carousel-image');
+            const dots = carousel.querySelectorAll('.dot');
+            const counter = carousel.querySelector('.image-counter');
+
+            let newIndex = carouselStates[carouselId].currentIndex + direction;
+            if (newIndex < 0) newIndex = images.length - 1;
+            if (newIndex >= images.length) newIndex = 0;
+
+            carouselStates[carouselId].currentIndex = newIndex;
+            imagesContainer.style.transform = `translateX(-${{newIndex * 100}}%)`;
+
+            dots.forEach((dot, i) => {{
+                dot.classList.toggle('active', i === newIndex);
+            }});
+
+            if (counter) {{
+                counter.textContent = `${{newIndex + 1}}/${{images.length}}`;
+            }}
+        }}
+
+        function goToImage(carouselId, index) {{
+            if (!carouselStates[carouselId]) {{
+                carouselStates[carouselId] = {{ currentIndex: 0 }};
+            }}
+
+            const carousel = document.getElementById(carouselId);
+            const imagesContainer = carousel.querySelector('.carousel-images');
+            const dots = carousel.querySelectorAll('.dot');
+            const counter = carousel.querySelector('.image-counter');
+
+            carouselStates[carouselId].currentIndex = index;
+            imagesContainer.style.transform = `translateX(-${{index * 100}}%)`;
+
+            dots.forEach((dot, i) => {{
+                dot.classList.toggle('active', i === index);
+            }});
+
+            if (counter) {{
+                counter.textContent = `${{index + 1}}/${{dots.length}}`;
+            }}
+        }}
+
+        function toggleFeature(featureIdx) {{
+            const searchWordsList = document.getElementById(`search-words-${{featureIdx}}`);
+            const featureHeader = document.getElementById(`feature-header-${{featureIdx}}`);
+
+            searchWordsList.classList.toggle('expanded');
+            featureHeader.classList.toggle('active');
+        }}
+
+        function toggleBaseWord(featureIdx, groupIdx) {{
+            const baseWordHeader = document.getElementById(`base-word-header-${{featureIdx}}-${{groupIdx}}`);
+            const baseWordDesc = document.getElementById(`base-word-desc-${{featureIdx}}-${{groupIdx}}`);
+            const searchWordsSublist = document.getElementById(`search-words-sublist-${{featureIdx}}-${{groupIdx}}`);
+
+            baseWordHeader.classList.toggle('active');
+            baseWordDesc.classList.toggle('expanded');
+            searchWordsSublist.classList.toggle('expanded');
+        }}
+
+        function scrollToBlock(blockId) {{
+            const block = document.getElementById(blockId);
+            if (block) {{
+                block.scrollIntoView({{ behavior: 'smooth', block: 'start' }});
+
+                document.querySelectorAll('.search-word-item').forEach(item => {{
+                    item.classList.remove('active');
+                }});
+
+                document.querySelectorAll(`[data-block-id="${{blockId}}"]`).forEach(item => {{
+                    item.classList.add('active');
+                }});
+            }}
+        }}
+
+        function toggleEvalDetails(carouselId) {{
+            const details = document.getElementById(`${{carouselId}}-details`);
+            const toggle = document.getElementById(`${{carouselId}}-toggle`);
+
+            if (details && toggle) {{
+                details.classList.toggle('expanded');
+                toggle.textContent = details.classList.contains('expanded') ? '▲ 收起' : '▼ 详情';
+            }}
+        }}
+
+        function filterNotes(category) {{
+            currentFilter = category;
+
+            document.querySelectorAll('.filter-btn').forEach(btn => {{
+                btn.classList.remove('active');
+            }});
+            event.target.classList.add('active');
+
+            document.querySelectorAll('.note-card').forEach(card => {{
+                const evalCategory = card.getAttribute('data-eval-category');
+                if (category === 'all' || evalCategory === category) {{
+                    card.classList.remove('hidden');
+                }} else {{
+                    card.classList.add('hidden');
+                }}
+            }});
+
+            document.querySelectorAll('.result-block').forEach(block => {{
+                const visibleCards = block.querySelectorAll('.note-card:not(.hidden)');
+                if (visibleCards.length === 0) {{
+                    block.classList.add('hidden');
+                }} else {{
+                    block.classList.remove('hidden');
+                }}
+            }});
+        }}
+
+        function openNote(noteId) {{
+            if (noteId) {{
+                window.open(`https://www.xiaohongshu.com/explore/${{noteId}}`, '_blank');
+            }}
+        }}
+
+        // 页面加载时输出调试信息
+        console.log('='.repeat(60));
+        console.log('🚀 [系统] 页面脚本加载完成');
+        console.log('📊 [数据] 评估特征数:', data.length);
+        console.log('📊 [数据] 解构结果数:', Object.keys(deconstructionData).length);
+        console.log('📊 [数据] 相似度分析数:', Object.keys(similarityData).length);
+        console.log('📋 [数据] 相似度分析可用noteId:', Object.keys(similarityData));
+        console.log('='.repeat(60));
+
+        // 初始化
+        document.addEventListener('DOMContentLoaded', () => {{
+            console.log('✅ [系统] DOM加载完成,开始初始化...');
+
+            try {{
+                renderLeftSidebar();
+                console.log('✅ [系统] 左侧导航(级联模式)渲染完成');
+
+                // 级联模式:自动选择第一个特征
+                if (data.length > 0) {{
+                    selectFeature(0);
+                    console.log('✅ [系统] 自动选择第一个特征');
+
+                    const firstGroups = data[0]['组合评估结果_分组'];
+                    if (firstGroups && firstGroups.length > 0) {{
+                        selectBaseWord(0, 0);
+                        console.log('✅ [系统] 自动选择第一个base_word');
+
+                        const firstSearches = firstGroups[0]['top10_searches'];
+                        if (firstSearches && firstSearches.length > 0) {{
+                            selectSearchWord(0, 0, 0);
+                            console.log('✅ [系统] 自动选择第一个搜索词');
+                        }}
+                    }}
+                }}
+
+                console.log('✅ [系统] 页面初始化完成');
+
+                // 为所有解构按钮添加事件监听器
+                setTimeout(() => {{
+                    const buttons = document.querySelectorAll('.deconstruction-toggle-btn');
+                    console.log('🔍 [系统] 找到解构按钮数量:', buttons.length);
+
+                    buttons.forEach((btn, index) => {{
+                        const noteId = btn.getAttribute('data-note-id');
+                        const noteTitle = btn.getAttribute('data-note-title');
+                        console.log(`  按钮[${{index}}] noteId:`, noteId, ', title:', noteTitle);
+
+                        // 添加事件监听器打开模态窗口
+                        btn.addEventListener('click', function(e) {{
+                            console.log('🖱️ [事件] 按钮点击, noteId:', noteId);
+                            e.stopPropagation();
+                            e.preventDefault();
+                            openDeconstructionModal(noteId, noteTitle);
+                        }});
+                    }});
+                }}, 500);
+
+            }} catch (error) {{
+                console.error('❌ [错误] 初始化失败:', error);
+            }}
+        }});
+
+        // ========== 新增功能1: 统计面板折叠/展开 ==========
+        const statsPanel = document.querySelector('.stats-panel');
+        if (statsPanel) {{
+            statsPanel.addEventListener('click', function() {{
+                this.classList.toggle('collapsed');
+            }});
+        }}
+
+        // ========== 新增功能2: 帖子图片浮层模态窗口 ==========
+        function openNoteImagesModal(featureIdx, groupIdx, searchIdx, noteIdx) {{
+            const feature = data[featureIdx];
+            const group = feature['组合评估结果_分组'][groupIdx];
+            const search = group['top10_searches'][searchIdx];
+            const searchResult = search.search_result || {{}};
+            const notes = searchResult.data?.data || [];
+            const note = notes[noteIdx];
+
+            if (!note) {{
+                console.log('❌ [浮层] 找不到帖子数据');
+                return;
+            }}
+
+            const card = note.note_card || {{}};
+            const images = card.image_list || [];
+            const title = card.display_title || '无标题';
+            const noteId = note.id || '';
+
+            console.log('🔍 [帖子图片浮层] 帖子ID:', noteId);
+            console.log('🔍 [帖子图片浮层] 标题:', title);
+            console.log('🔍 [帖子图片浮层] 图片数量:', images.length);
+
+            if (images.length === 0) {{
+                console.log('❌ [浮层] 该帖子没有图片');
+                return;
+            }}
+
+            const modal = document.getElementById('notesModal');
+            const modalContent = document.getElementById('notesModalContent');
+
+            // 更新模态窗口标题
+            const modalTitle = document.querySelector('.notes-modal-title');
+            if (modalTitle) {{
+                modalTitle.innerHTML = `
+                    <div>
+                        <div style="font-size: 18px; font-weight: 600;">📷 ${{title}}</div>
+                        <div style="font-size: 14px; opacity: 0.9; margin-top: 5px;">共 ${{images.length}} 张图片</div>
+                    </div>
+                `;
+            }}
+
+            // 构建图片网格HTML
+            let imagesHtml = '<div class="notes-grid-modal">';
+
+            images.forEach((imageUrl, idx) => {{
+                imagesHtml += `
+                    <div class="note-card-modal" onclick="window.open('https://www.xiaohongshu.com/explore/${{noteId}}', '_blank'); event.stopPropagation();">
+                        <div class="note-image-wrapper" style="height: 300px;">
+                            <img src="${{imageUrl}}" alt="图片 ${{idx + 1}}" class="note-image" style="object-fit: contain; background: #000;"/>
+                        </div>
+                        <div class="note-content">
+                            <div style="text-align: center; color: #6b7280; font-size: 13px;">
+                                第 ${{idx + 1}} / ${{images.length}} 张
+                            </div>
+                        </div>
+                    </div>
+                `;
+            }});
+
+            imagesHtml += '</div>';
+
+            modalContent.innerHTML = imagesHtml;
+            modal.classList.add('active');
+
+            // 点击背景关闭
+            modal.onclick = function(e) {{
+                if (e.target === modal) {{
+                    closeNotesModal();
+                }}
+            }};
+        }}
+
+        function closeNotesModal() {{
+            const modal = document.getElementById('notesModal');
+            modal.classList.remove('active');
+        }}
+
+        // ========== 新增功能3: 右侧滚动与左侧联动 ==========
+        // 使用Intersection Observer监听右侧内容块的可见性
+        const observerOptions = {{
+            root: null,
+            rootMargin: '-20% 0px -70% 0px',  // 当元素在视口上20%位置时触发
+            threshold: 0
+        }};
+
+        const observer = new IntersectionObserver((entries) => {{
+            entries.forEach(entry => {{
+                if (entry.isIntersecting) {{
+                    const blockId = entry.target.id;
+                    const leftItem = document.getElementById('search-' + blockId.replace('block-', ''));
+
+                    if (leftItem) {{
+                        // 移除所有active状态
+                        document.querySelectorAll('.search-word-item').forEach(item => {{
+                            item.classList.remove('active');
+                        }});
+
+                        // 添加active到当前项
+                        leftItem.classList.add('active');
+
+                        // 滚动到可见区域
+                        leftItem.scrollIntoView({{
+                            behavior: 'smooth',
+                            block: 'nearest'
+                        }});
+                    }}
+                }}
+            }});
+        }}, observerOptions);
+
+        // 观察所有result-block
+        document.addEventListener('DOMContentLoaded', () => {{
+            setTimeout(() => {{
+                const blocks = document.querySelectorAll('.result-block');
+                blocks.forEach(block => {{
+                    observer.observe(block);
+                }});
+                console.log('✅ [系统] 已设置', blocks.length, '个内容块的滚动监听');
+            }}, 1000);
+        }});
+    </script>
+</body>
+</html>
+'''
+
+    with open(output_path, 'w', encoding='utf-8') as f:
+        f.write(html_content)
+
+
+def main():
+    """主函数"""
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    # 修复路径:从src/visualizers/回到项目根目录
+    project_root = os.path.dirname(os.path.dirname(script_dir))
+
+    # 加载数据(使用项目根目录)
+    evaluation_path = os.path.join(project_root, 'output_v2', 'evaluated_results.json')
+    deconstruction_path = os.path.join(project_root, 'output_v2', 'deep_analysis_results.json')
+    similarity_path = os.path.join(project_root, 'output_v2', 'similarity_analysis_results.json')
+    persona_library_path = os.path.join(project_root, 'optimized_clustered_data_gemini-3-pro-preview.json')
+    how_json_path = os.path.join(project_root, 'input/posts/690d977d0000000007036331_how.json')
+
+    output_dir = os.path.join(project_root, 'visualization')
+    os.makedirs(output_dir, exist_ok=True)
+
+    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+    output_path = os.path.join(output_dir, f'integrated_results_{timestamp}.html')
+
+    print(f"📖 加载评估数据: {evaluation_path}")
+    data = load_data(evaluation_path)
+    print(f"✓ 加载了 {len(data)} 个原始特征")
+
+    print(f"📖 加载解构数据: {deconstruction_path}")
+    deconstruction_mapping = load_deconstruction_data(deconstruction_path)
+    print(f"✓ 加载了 {len(deconstruction_mapping)} 个解构结果")
+
+    print(f"📖 加载Stage8数据: {similarity_path}")
+    similarity_mapping = load_similarity_data(similarity_path)
+    print(f"✓ 加载了 {len(similarity_mapping)} 个相似度评分")
+
+    print("📊 计算统计数据...")
+    stats = calculate_statistics(data)
+    print(f"✓ 统计完成:")
+    print(f"  - 原始特征: {stats['total_features']}")
+    print(f"  - 搜索词总数: {stats['total_search_words']}")
+    print(f"  - 帖子总数: {stats['total_notes']}")
+    print(f"  - 完全匹配: {stats['match_complete']} ({stats['complete_rate']}%)")
+
+    # 关系图已移除
+    relationship_graph_html = ""
+
+    # 提取所有特征信息(包括低相似度和高相似度特征)
+    print(f"\n📊 提取所有特征信息...")
+    all_features = {}
+    high_similarity_features = []
+    low_similarity_features = []
+    try:
+        all_features = extract_all_features_from_how(how_json_path)
+
+        # 标记评估结果中已搜索的特征
+        evaluated_feature_names = set([item.get('原始特征名称') for item in data])
+        for feature_name in evaluated_feature_names:
+            if feature_name in all_features and all_features[feature_name]['category'] == '待搜索':
+                all_features[feature_name]['category'] = '已搜索'
+
+        # 提取高相似度特征(≥0.8)
+        high_similarity_features = [
+            {'name': name, **info}
+            for name, info in all_features.items()
+            if info['category'] == '高相似度'
+        ]
+        # 按相似度降序排序
+        high_similarity_features.sort(key=lambda x: x['similarity'], reverse=True)
+
+        # 提取低相似度特征(<0.5)
+        low_similarity_features = [
+            {'name': name, **info}
+            for name, info in all_features.items()
+            if info['category'] == '低相似度'
+        ]
+        # 按相似度降序排序
+        low_similarity_features.sort(key=lambda x: x['similarity'], reverse=True)
+
+        print(f"✓ 提取了 {len(all_features)} 个特征")
+        print(f"  - 高相似度特征(≥0.8): {len(high_similarity_features)} 个")
+        print(f"  - 部分匹配特征(0.5-0.8): {len([f for f in all_features.values() if f['category'] == '已搜索'])} 个")
+        print(f"  - 低相似度特征(<0.5): {len(low_similarity_features)} 个")
+    except Exception as e:
+        print(f"⚠️ 警告: 提取特征信息时出错 - {e}")
+        high_similarity_features = []
+        low_similarity_features = []
+
+    print(f"\n🎨 生成可视化页面...")
+    generate_html(data, stats, deconstruction_mapping, similarity_mapping, relationship_graph_html, all_features, high_similarity_features, low_similarity_features, output_path)
+    print(f"✓ 生成完成: {output_path}")
+
+    print(f"\n🌐 在浏览器中打开查看:")
+    print(f"   file://{output_path}")
+
+    return output_path
+
+
+if __name__ == '__main__':
+    main()