"""
分类优化结果可视化工具

功能：
1. 读取优化后的聚类数据 (optimized_clustered_data_*.json)
2. 生成交互式HTML树形可视化
3. 支持查看帖子详情和所有点的信息
4. 区分显示原始分类、LLM抽象、LLM细分
"""

import json
import os
from typing import Dict, Any, List, Optional
from pathlib import Path
from datetime import datetime


class ClassificationTreeVisualizer:
    """分类树可视化工具"""

    def __init__(self):
        self.post_cache: Dict[str, Dict[str, Any]] = {}
        self.xuanti_point_map: Dict[str, Dict[str, Any]] = {}
        self.clustered_data: Dict[str, Any] = {}
        self.dimension_associations: Dict[str, Any] = {}
        self.intra_dimension_associations: Dict[str, Any] = {}

    def load_post_data(self, post_id: str, posts_dir: Path) -> Optional[Dict[str, Any]]:
        """加载帖子详细数据"""
        if post_id in self.post_cache:
            return self.post_cache[post_id]

        post_file = posts_dir / f"{post_id}.json"
        if not post_file.exists():
            return None

        try:
            with open(post_file, 'r', encoding='utf-8') as f:
                post_data = json.load(f)
                self.post_cache[post_id] = post_data
                return post_data
        except Exception as e:
            print(f"加载帖子 {post_id} 失败: {e}")
            return None

    def generate_tree_node_html(
        self,
        node_name: str,
        node_data: Dict[str, Any],
        level: int,
        point_type: str,
        path: List[str]
    ) -> str:
        """递归生成树节点的HTML - 支持部分细分结构"""
        import html as html_module

        node_name_escaped = html_module.escape(node_name)
        current_path = path + [node_name]
        node_id = f"{point_type}_{'_'.join(current_path)}".replace('/', '_').replace(' ', '_')

        meta = node_data.get('_meta', {})
        source = meta.get('分类来源', '')

        # 检查是否有保留的点
        has_kept_points = '点列表' in node_data and len(node_data.get('点列表', [])) > 0

        # 检查是否有子分类
        has_children = False
        for key in node_data.keys():
            if key not in ['_meta', '点列表', '帖子数', '点数', '帖子列表'] and isinstance(node_data[key], dict):
                has_children = True
                break

        # 确定节点样式
        if source == 'LLM抽象' or source == 'LLM细分':
            node_class = f"tree-node tree-node-llm level-{level}"
        else:
            node_class = f"tree-node tree-node-original level-{level}"

        html = f'<div class="{node_class}">\n'
        html += f'  <div class="tree-node-header">\n'
        html += f'    <span class="tree-node-icon toggle-icon" data-node-id="{node_id}" data-action="toggle-node">▶</span>\n'
        html += f'    <span class="tree-node-title" data-node-id="{node_id}" data-action="show-classification-detail">{node_name_escaped}</span>\n'
        # 只有存在下级分类节点时,才显示展开/收起全部按钮
        if has_children:
            html += f'    <span class="expand-all-icon" data-node-id="{node_id}" data-action="toggle-all-children" title="展开/收起所有下级分类节点">⇅</span>\n'
        html += f'  </div>\n'

        html += f'  <div class="tree-node-content" id="{node_id}-content">\n'
        html += '    <div class="tree-children">\n'

        # 1. 先显示保留在原分类的点（如果有）
        if has_kept_points:
            point_name_field = "灵感点" if point_type == "灵感点列表" else (
                "目的点" if point_type == "目的点" else "关键点"
            )

            for point in node_data.get('点列表', []):
                point_name = point.get(point_name_field, '')
                if not point_name:
                    continue

                point_name_escaped = html_module.escape(point_name)
                point_id = f"{node_id}_kept_{point_name}".replace('/', '_').replace(' ', '_')

                # 获取封面图
                post_id = point.get('帖子id', '')
                thumbnail_html = ''
                if post_id and post_id in self.post_cache:
                    post_data = self.post_cache[post_id]
                    images = post_data.get('images', [])
                    if images and len(images) > 0:
                        first_image = html_module.escape(images[0])
                        thumbnail_html = f'<img src="{first_image}" class="node-thumbnail" alt="封面图" loading="lazy">'

                html += f'<div class="tree-node tree-node-leaf level-{level + 1}">\n'
                html += f'  <div class="tree-node-header" data-node-id="{point_id}" data-parent-id="{node_id}" data-action="show-point-detail">\n'
                html += f'    <span class="tree-node-icon">📄</span>\n'
                html += f'    <span class="tree-node-title">{point_name_escaped}</span>\n'
                html += f'    {thumbnail_html}\n'
                html += f'  </div>\n'
                html += '</div>\n'

        # 2. 再显示子分类（如果有）
        if has_children:
            for child_name, child_data in node_data.items():
                if child_name in ['_meta', '点列表', '帖子数', '点数', '帖子列表']:
                    continue
                if isinstance(child_data, dict):
                    html += self.generate_tree_node_html(
                        child_name, child_data, level + 1, point_type, current_path
                    )

        html += '    </div>\n'
        html += '  </div>\n'
        html += '</div>\n'

        return html

    def generate_html(
        self,
        clustered_data: Dict[str, Any],
        posts_dir: Path,
        xuanti_point_map: Dict[str, Dict[str, Any]],
        dimension_associations: Optional[Dict[str, Any]] = None,
        intra_dimension_associations: Optional[Dict[str, Any]] = None,
        expanded_orthogonal_combinations: Optional[Dict[str, Any]] = None,
        enriched_xuanti_point_map: Optional[Dict[str, Any]] = None
    ) -> str:
        """生成完整的HTML页面"""
        self.xuanti_point_map = xuanti_point_map
        self.clustered_data = clustered_data
        if dimension_associations:
            self.dimension_associations = dimension_associations
        if intra_dimension_associations:
            self.intra_dimension_associations = intra_dimension_associations
        if expanded_orthogonal_combinations:
            self.expanded_orthogonal_combinations = expanded_orthogonal_combinations
        if enriched_xuanti_point_map:
            self.enriched_xuanti_point_map = enriched_xuanti_point_map

        # 从clustered_data构建完整的帖子ID到特征对象的映射
        self.post_to_features_map = self._build_post_to_features_map(clustered_data)

        # 预加载所有帖子数据
        all_post_ids = set()
        for point_type_data in clustered_data.values():
            self._collect_post_ids(point_type_data, all_post_ids)

        # 从xuanti_point_map中也加载帖子ID
        for post_id in xuanti_point_map.keys():
            all_post_ids.add(post_id)

        for post_id in all_post_ids:
            self.load_post_data(post_id, posts_dir)

        # 生成HTML
        html = self._generate_html_head()
        html += '<body>\n'

        # Tab切换结构
        html += '<div class="tabs-container">\n'
        html += '  <div class="tabs-header">\n'
        html += '    <button class="tab-button active" data-tab="tab1">帖子视角 - 原始选题点</button>\n'
        html += '    <button class="tab-button" data-tab="tab2">特征视角 - 分类优化结果</button>\n'
        html += '    <button class="tab-button" data-tab="tab3">叶子分类组合聚类</button>\n'
        html += '    <button class="tab-button" data-tab="tab4">维度关联分析</button>\n'
        html += '  </div>\n'
        html += '</div>\n'

        # Tab 1: 帖子视角 - 原始选题点
        html += '<div id="tab1" class="tab-content active">\n'
        html += self._generate_tab1_content()
        html += '</div>\n'

        # Tab 2: 特征视角 - 分类优化结果
        html += '<div id="tab2" class="tab-content">\n'
        html += self._generate_tab2_content(clustered_data)
        html += '</div>\n'

        # Tab 3: 叶子分类组合聚类
        html += '<div id="tab3" class="tab-content">\n'
        if self.intra_dimension_associations:
            html += self._generate_tab4_content()
        else:
            html += '<div style="padding: 40px; text-align: center; color: #999;">未加载叶子分类组合聚类数据</div>\n'
        html += '</div>\n'

        # Tab 4: 维度关联分析
        html += '<div id="tab4" class="tab-content">\n'
        if self.dimension_associations:
            html += self._generate_tab3_content()
        else:
            html += '<div style="padding: 40px; text-align: center; color: #999;">未加载维度关联分析数据</div>\n'
        html += '</div>\n'

        # 添加弹窗容器
        html += '''
<div id="detail-modal" class="modal" style="display: none;">
    <div class="modal-overlay"></div>
    <div class="modal-content">
        <div class="modal-header">
            <h3 id="modal-title"></h3>
            <button class="modal-close" data-action="close-modal">✕</button>
        </div>
        <div class="modal-body" id="modal-body">
        </div>
    </div>
</div>
'''

        # 添加JavaScript
        html += self._generate_javascript(posts_dir)

        html += '</body>\n</html>\n'

        return html

    def _generate_tab1_content(self) -> str:
        """生成Tab1内容：帖子视角 - 原始选题点"""
        import html as html_module

        html = '<div class="mindmap-container">\n'

        # 按帖子ID排序
        sorted_post_ids = sorted(self.xuanti_point_map.keys())

        for post_id in sorted_post_ids:
            xuanti_point = self.xuanti_point_map[post_id]
            post_data = self.post_cache.get(post_id)

            if not post_data:
                continue

            # 帖子卡片
            html += '  <div class="mindmap-section">\n'

            # 左侧：帖子信息
            html += '    <div class="mindmap-root" style="min-width: 250px; max-width: 250px;">\n'
            html += f'      <div><strong>{html_module.escape(post_data.get("title", "无标题"))}</strong></div>\n'

            # 封面图
            images = post_data.get('images', [])
            if images and len(images) > 0:
                first_image = html_module.escape(images[0])
                html += f'      <img src="{first_image}" style="width:100%; margin-top:8px; border-radius:4px;" alt="封面" loading="lazy">\n'

            html += f'      <div style="margin-top:8px; font-size:0.8rem; opacity:0.8;">ID: {html_module.escape(post_id[:12])}...</div>\n'
            html += '    </div>\n'

            # 右侧：选题点树状结构
            html += '    <div class="mindmap-tree">\n'

            # 遍历三种点类型
            for point_type in ["灵感点列表", "目的点", "关键点列表"]:
                points = xuanti_point.get(point_type, [])
                if not points:
                    continue

                point_name_field = "灵感点" if point_type == "灵感点列表" else (
                    "目的点" if point_type == "目的点" else "关键点"
                )

                html += f'      <div class="tree-node tree-node-original level-0" style="margin-bottom:12px;">\n'
                html += f'        <div class="tree-node-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight:600;">\n'
                html += f'          <span>{point_type} ({len(points)})</span>\n'
                html += '        </div>\n'
                html += '        <div class="tree-node-content" style="display:flex;">\n'
                html += '          <div class="tree-children">\n'

                # 遍历该类型下的所有点
                for idx, point in enumerate(points):
                    point_name = point.get(point_name_field, '')
                    point_id = f"tab1_{post_id}_{point_type}_{idx}"

                    html += f'            <div class="tree-node level-1">\n'
                    html += f'              <div class="tree-node-header">\n'
                    html += f'                <span class="tree-node-icon toggle-icon expanded" data-node-id="{point_id}" data-action="toggle-node">▶</span>\n'
                    html += f'                <span class="tree-node-title" data-node-id="{point_id}" data-action="show-tab1-point-detail">{html_module.escape(point_name)}</span>\n'
                    html += '              </div>\n'

                    # 点的内容：只显示特征列表（扁平化展示）
                    html += f'              <div class="tree-node-content" id="{point_id}-content">\n'

                    # 显示提取的特征（扁平化展示为标签）
                    features = point.get('提取的特征', [])
                    if features:
                        html += '                <div style="display:flex; flex-wrap:wrap; gap:8px; padding:8px; background:#fafbfc; border-radius:4px;">\n'
                        for feature_idx, feature in enumerate(features):
                            feature_name = feature.get('特征名称', '')
                            feature_weight = feature.get('权重', 0)
                            feature_level1 = feature.get('一级分类', '')
                            feature_level2 = feature.get('二级分类', '')

                            # 构建特征分类标签
                            feature_class_tag = ''
                            if feature_level1:
                                feature_class_tag = f'{html_module.escape(feature_level1)}'
                                if feature_level2:
                                    feature_class_tag += f' / {html_module.escape(feature_level2)}'

                            # 扁平化的特征标签样式
                            html += f'                  <div style="display:inline-flex; align-items:center; gap:6px; padding:6px 12px; background:white; border:1px solid #e0e0e0; border-radius:6px; font-size:0.85rem; box-shadow:0 1px 3px rgba(0,0,0,0.08);">\n'
                            html += f'                    <span style="font-weight:500; color:#333;">🔖 {html_module.escape(feature_name)}</span>\n'
                            html += f'                    <span style="font-size:0.75rem; color:#999;">({feature_weight})</span>\n'
                            if feature_class_tag:
                                html += f'                    <span style="font-size:0.75rem; background:#fff3e0; padding:2px 8px; border-radius:3px; color:#e65100;">{feature_class_tag}</span>\n'
                            html += '                  </div>\n'

                        html += '                </div>\n'

                    html += '              </div>\n'
                    html += '            </div>\n'

                    # 在点之间添加分割线（最后一个点不添加）
                    if idx < len(points) - 1:
                        html += '            <div style="height:1px; background:linear-gradient(to right, transparent, #e0e0e0 20%, #e0e0e0 80%, transparent); margin:12px 0;"></div>\n'

                html += '          </div>\n'
                html += '        </div>\n'
                html += '      </div>\n'

            html += '    </div>\n'
            html += '  </div>\n'

        html += '</div>\n'
        return html

    def _generate_tab2_content(self, clustered_data: Dict[str, Any]) -> str:
        """生成Tab2内容：特征视角 - 分类优化结果"""
        html = '<div class="mindmap-container">\n'

        # 为每种点类型生成树
        for point_type in ["灵感点列表", "目的点", "关键点列表"]:
            type_data = clustered_data.get(point_type, {})
            if type_data:
                html += f'  <div class="mindmap-section">\n'
                html += f'    <div class="mindmap-root">{point_type}</div>\n'
                html += '    <div class="mindmap-tree">\n'

                for node_name, node_data in type_data.items():
                    html += self.generate_feature_tree_node_html(
                        node_name, node_data, 0, point_type, []
                    )

                html += '    </div>\n'
                html += '  </div>\n'

        html += '</div>\n'
        return html

    def _generate_tab3_content(self) -> str:
        """生成Tab3内容：维度关联分析 - 交互式分类树"""
        html = '<div style="padding: 20px;">\n'
        html += '<h2 style="text-align: center; color: #667eea; margin-bottom: 10px;">维度关联分析</h2>\n'

        # 模式切换按钮
        html += '<div style="text-align: center; margin-bottom: 20px;">\n'
        html += '  <div style="display: inline-flex; background: #f0f0f0; border-radius: 8px; padding: 4px;">\n'
        html += '    <button id="mode-single" class="mode-button active" data-mode="single" style="padding: 8px 20px; border: none; background: white; cursor: pointer; border-radius: 6px; font-weight: 500; transition: all 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">单维度关联</button>\n'
        html += '    <button id="mode-three" class="mode-button" data-mode="three" style="padding: 8px 20px; border: none; background: transparent; cursor: pointer; border-radius: 6px; font-weight: 500; transition: all 0.2s; margin-left: 4px;">三维正交关联</button>\n'
        html += '  </div>\n'
        html += '  <div id="mode-description" style="margin-top: 10px; color: #666; font-size: 0.9rem;">\n'
        html += '    <span id="desc-single">点击一个维度的分类，查看它与其他维度的关联关系</span>\n'
        html += '    <span id="desc-three" style="display: none;">点击灵感点分类，右侧显示目的点和关键点的正交组合及共同帖子</span>\n'
        html += '  </div>\n'
        html += '</div>\n'

        # 单维度关联模式：三列布局
        html += '<div id="single-dim-layout" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; margin-top: 20px;">\n'

        dimensions = [
            ("灵感点列表", "灵感点"),
            ("目的点", "目的点"),
            ("关键点列表", "关键点")
        ]

        for point_type, display_name in dimensions:
            html += f'<div style="background: white; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">\n'
            html += f'  <h3 style="color: #667eea; margin-bottom: 16px; text-align: center;">{display_name}</h3>\n'
            html += f'  <div id="tab3-tree-{point_type}" class="tab3-tree-container">\n'

            # 生成分类树
            type_data = self.clustered_data.get(point_type, {})
            if type_data:
                for node_name, node_data in type_data.items():
                    html += self._generate_tab3_tree_node(
                        node_name, node_data, 0, point_type, []
                    )

            html += '  </div>\n'
            html += '</div>\n'

        html += '</div>\n'

        # 三维正交关联模式：全屏平铺布局
        html += '<div id="three-dim-layout" style="display: none; margin-top: 20px;">\n'

        # 全屏正交关系结果区域
        html += '<div style="background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);" id="orthogonal-scroll-container">\n'
        html += '  <div id="orthogonal-results" style="min-height: 200px;">\n'
        html += '    <div style="text-align: center; padding: 60px 20px; color: #999;">\n'
        html += '      <p style="font-size: 1.1rem; margin-bottom: 10px;">🔄 加载中...</p>\n'
        html += '      <p style="font-size: 0.9rem;">正在加载所有三维正交扩展结果</p>\n'
        html += '    </div>\n'
        html += '  </div>\n'
        html += '</div>\n'

        html += '</div>\n'
        html += '</div>\n'
        return html

    def _generate_tab3_tree_node(
        self,
        node_name: str,
        node_data: Dict[str, Any],
        level: int,
        point_type: str,
        path: List[str]
    ) -> str:
        """递归生成Tab3的树节点HTML，使用数据属性存储关联信息"""
        import html as html_module

        node_name_escaped = html_module.escape(node_name)
        current_path = path + [node_name]
        path_str = '/'.join(current_path)
        node_id = f"tab3_{point_type}_{'_'.join(current_path)}".replace('/', '_').replace(' ', '_').replace('(', '').replace(')', '')

        meta = node_data.get('_meta', {})
        source = meta.get('分类来源', '')

        # 检查是否有子分类
        has_children = any(
            key not in ['_meta', '特征列表', '点列表', '帖子数', '特征数', '点数', '帖子列表'] and isinstance(node_data[key], dict)
            for key in node_data.keys()
        )

        # 确定节点样式 - 不使用绿色，使用浅灰色背景区分LLM生成的分类
        if source in ['LLM抽象', 'LLM细分']:
            bg_color = '#f0f0f0'
            text_color = '#333'
            border_style = '1px solid #d0d0d0'
        else:
            bg_color = '#fafafa'
            text_color = '#333'
            border_style = '1px solid #e0e0e0'

        # 节点容器
        html = f'<div class="tab3-node" style="margin-bottom: 6px;">\n'

        # 节点头部 - 使用data属性存储分类路径和维度信息以及原始样式
        padding_left = level * 16
        html += f'  <div class="tab3-node-header" '
        html += f'data-dimension="{point_type}" '
        html += f'data-path="{html_module.escape(path_str)}" '
        html += f'data-node-id="{node_id}" '
        html += f'data-original-bg="{bg_color}" '
        html += f'data-original-border="{border_style}" '
        html += f'style="padding: 6px 12px; padding-left: {padding_left + 12}px; '
        html += f'background: {bg_color}; color: {text_color}; '
        html += f'border: {border_style}; '
        html += 'border-radius: 4px; cursor: pointer; transition: all 0.2s; '
        html += 'box-shadow: 0 1px 2px rgba(0,0,0,0.05); margin-bottom: 4px;">\n'

        if has_children:
            # 默认展开，所以使用▼图标
            html += f'    <span class="tab3-toggle" data-target="{node_id}-content" style="margin-right: 8px; user-select: none;">▼</span>\n'
        else:
            html += '    <span style="margin-right: 8px;">•</span>\n'

        html += f'    <span>{node_name_escaped}</span>\n'
        html += '  </div>\n'

        # 封面图容器 - 用于显示该分类下的帖子封面图
        html += f'  <div class="tab3-thumbnails" id="{node_id}-thumbnails" style="display: none; margin-top: 8px; margin-left: {padding_left + 12}px; flex-wrap: wrap; gap: 8px;"></div>\n'

        # 子节点容器 - 默认展开
        if has_children:
            html += f'  <div id="{node_id}-content" style="display: block;">\n'
            for child_name, child_data in node_data.items():
                if child_name not in ['_meta', '特征列表', '点列表', '帖子数', '特征数', '点数', '帖子列表'] and isinstance(child_data, dict):
                    html += self._generate_tab3_tree_node(
                        child_name, child_data, level + 1, point_type, current_path
                    )
            html += '  </div>\n'

        html += '</div>\n'
        return html

    def _generate_leaf_color(self, index: int, total: int) -> str:
        """为叶子分类生成不同的颜色（使用HSL色轮）"""
        # 使用HSL色轮，在0-360度范围内均匀分布
        # 降低亮度到45%，增加饱和度到85%，让白色字体更突出
        hue = int((index * 360) / max(total, 1))
        return f'hsl({hue}, 85%, 45%)'

    def _highlight_features_in_point_name(self, point_name: str, features: list, leaf_color_map: dict) -> str:
        """在点名称中高亮显示特征词（使用对应的叶子分类颜色）"""
        import html as html_module

        # 构建特征名称到颜色的映射
        feature_colors = {}
        for feature in features:
            feature_name = feature.get('特征名称', '')
            leaf_class = feature.get('叶子分类', '')
            if feature_name and leaf_class in leaf_color_map:
                feature_colors[feature_name] = leaf_color_map[leaf_class]

        # 按特征名称长度倒序排序，避免短的先替换导致长的无法匹配
        sorted_features = sorted(feature_colors.items(), key=lambda x: len(x[0]), reverse=True)

        # 先转义整个字符串
        highlighted_name = html_module.escape(point_name)

        # 逐个替换特征词为带颜色的span
        for feature_name, color in sorted_features:
            escaped_name = html_module.escape(feature_name)
            if escaped_name in highlighted_name:
                highlighted_name = highlighted_name.replace(
                    escaped_name,
                    f'<span style="display: inline-block; background: {color}; color: white; padding: 2px 6px; border-radius: 3px; font-weight: 600; margin: 0 2px; white-space: nowrap;">{escaped_name}</span>'
                )

        return highlighted_name

    def _generate_tab4_content(self) -> str:
        """生成Tab4内容：叶子分类组合聚类"""
        import html as html_module

        html = '<div style="padding: 20px;">\n'
        html += '<h2 style="text-align: center; color: #667eea; margin-bottom: 20px;">叶子分类组合聚类分析</h2>\n'

        # 获取聚类数据
        clustering_data = self.intra_dimension_associations.get('叶子分类组合聚类', {})

        dimensions = [
            ("灵感点", "#9C27B0"),
            ("目的点", "#E91E63"),
            ("关键点", "#FF9800")
        ]

        for dimension_name, dimension_color in dimensions:
            dimension_clusters = clustering_data.get(dimension_name, {})

            # 按点数倒序排列
            sorted_clusters = sorted(
                dimension_clusters.items(),
                key=lambda x: x[1]['点数'],
                reverse=True
            )

            html += f'<div style="margin-bottom: 40px;">\n'
            html += f'<h3 style="color: {dimension_color}; border-bottom: 3px solid {dimension_color}; padding-bottom: 10px; margin-bottom: 20px;">\n'
            html += f'  {dimension_name} ({len(sorted_clusters)} 个聚类)\n'
            html += '</h3>\n'

            if not sorted_clusters:
                html += '<p style="color: #999; text-align: center; padding: 20px;">暂无聚类数据</p>\n'
            else:
                html += '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px;">\n'

                for cluster_key, cluster_data in sorted_clusters:
                    leaf_classifications = cluster_data.get('叶子分类组合', [])
                    point_count = cluster_data.get('点数', 0)
                    points_details = cluster_data.get('点详情列表', [])

                    html += '<div style="background: white; border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); transition: transform 0.2s, box-shadow 0.2s;" onmouseover="this.style.transform=\'translateY(-4px)\'; this.style.boxShadow=\'0 8px 20px rgba(0,0,0,0.15)\';" onmouseout="this.style.transform=\'translateY(0)\'; this.style.boxShadow=\'0 4px 12px rgba(0,0,0,0.1)\';">\n'

                    # 建立叶子分类到颜色的映射
                    total_classifications = len(leaf_classifications)
                    leaf_color_map = {}
                    for idx, leaf_class in enumerate(leaf_classifications):
                        leaf_color_map[leaf_class] = self._generate_leaf_color(idx, total_classifications)

                    # 标题：叶子分类标签 + 点数徽章
                    html += '<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 16px; flex-wrap: wrap;">\n'
                    # 叶子分类标签 - 每个分类使用不同颜色
                    for leaf_class in leaf_classifications:
                        leaf_color = leaf_color_map[leaf_class]
                        html += f'<span style="background: {leaf_color}; color: white; padding: 6px 12px; border-radius: 6px; font-size: 0.9rem; font-weight: 600; box-shadow: 0 2px 4px rgba(0,0,0,0.15);">{html_module.escape(leaf_class)}</span>\n'
                    # 点数徽章
                    html += f'<span style="background: {dimension_color}; color: white; padding: 6px 12px; border-radius: 20px; font-size: 0.85rem; font-weight: 600; margin-left: auto;">{point_count} 个点</span>\n'
                    html += '</div>\n'

                    # 点详情列表
                    html += '<div style="display: flex; flex-direction: column; gap: 16px;">\n'

                    for idx, point_detail in enumerate(points_details[:10]):  # 最多显示10个点
                        point_name = point_detail.get('点名称', '')
                        point_desc = point_detail.get('点描述', '')
                        features = point_detail.get('特征列表', [])

                        # 点卡片
                        html += f'<div style="background: #f8f9fa; border-radius: 8px; padding: 12px; border-left: 4px solid {dimension_color};">\n'

                        # 点名称（高亮显示特征词）
                        highlighted_point_name = self._highlight_features_in_point_name(point_name, features, leaf_color_map)
                        html += f'<div style="font-weight: 600; color: #333; margin-bottom: 8px; font-size: 0.95rem; line-height: 1.8; overflow-wrap: break-word;">{highlighted_point_name}</div>\n'

                        # 特征列表
                        if features:
                            html += '<div style="margin-top: 8px; padding-left: 12px;">\n'
                            for feature in features:
                                feature_name = feature.get('特征名称', '')
                                full_path = feature.get('完整路径', '')
                                weight = feature.get('权重', 0)

                                # 特征容器（分两行显示）
                                html += '<div style="margin-bottom: 8px;">\n'

                                # 第一行：特征名称 + 权重
                                html += '<div style="display: flex; align-items: center; gap: 6px; font-size: 0.85rem; margin-bottom: 3px;">\n'
                                html += f'<span style="color: {dimension_color}; font-weight: 600;">•</span>\n'
                                html += f'<span style="color: #555; font-weight: 600;">{html_module.escape(feature_name)}</span>\n'
                                html += f'<span style="background: #e0e0e0; color: #666; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem;">权重 {weight}</span>\n'
                                html += '</div>\n'

                                # 第二行：完整分类路径（缩进），使用灰色背景
                                html += '<div style="padding-left: 18px; font-size: 0.8rem;">\n'
                                html += f'<span style="color: #888; font-family: monospace; background: #f5f5f5; padding: 2px 6px; border-radius: 3px;">{html_module.escape(full_path)}</span>\n'
                                html += '</div>\n'

                                html += '</div>\n'
                            html += '</div>\n'

                        html += '</div>\n'

                    if len(points_details) > 10:
                        html += f'<div style="color: #999; font-size: 0.9rem; text-align: center; padding: 12px; background: #f0f0f0; border-radius: 8px;">... 还有 {len(points_details) - 10} 个点</div>\n'

                    html += '</div>\n'
                    html += '</div>\n'

                html += '</div>\n'

            html += '</div>\n'

        html += '</div>\n'
        return html

    def generate_feature_tree_node_html(
        self,
        node_name: str,
        node_data: Dict[str, Any],
        level: int,
        point_type: str,
        path: List[str]
    ) -> str:
        """递归生成特征树节点的HTML"""
        import html as html_module

        node_name_escaped = html_module.escape(node_name)
        current_path = path + [node_name]
        node_id = f"tab2_{point_type}_{'_'.join(current_path)}".replace('/', '_').replace(' ', '_')

        meta = node_data.get('_meta', {})
        source = meta.get('分类来源', '')

        # 检查是否有保留的特征
        has_kept_features = '特征列表' in node_data and len(node_data.get('特征列表', [])) > 0

        # 检查是否有子分类
        has_children = False
        for key in node_data.keys():
            if key not in ['_meta', '特征列表', '帖子数', '特征数', '帖子列表'] and isinstance(node_data[key], dict):
                has_children = True
                break

        # 确定节点样式
        if source == 'LLM抽象' or source == 'LLM细分':
            node_class = f"tree-node tree-node-llm level-{level}"
        else:
            node_class = f"tree-node tree-node-original level-{level}"

        html = f'<div class="{node_class}">\n'
        html += f'  <div class="tree-node-header">\n'
        html += f'    <span class="tree-node-icon toggle-icon" data-node-id="{node_id}" data-action="toggle-node">▶</span>\n'
        html += f'    <span class="tree-node-title" data-node-id="{node_id}" data-action="show-classification-detail">{node_name_escaped}</span>\n'
        if has_children:
            html += f'    <span class="expand-all-icon" data-node-id="{node_id}" data-action="toggle-all-children" title="展开/收起所有下级分类节点">⇅</span>\n'
        html += f'  </div>\n'

        html += f'  <div class="tree-node-content" id="{node_id}-content">\n'
        html += '    <div class="tree-children">\n'

        # 1. 先显示保留在原分类的特征（如果有）
        if has_kept_features:
            features_list = node_data.get('特征列表', [])
            # 去重特征名称用于显示
            unique_features = {}
            for feature in features_list:
                feature_name = feature.get("特征名称", "")
                if feature_name not in unique_features:
                    unique_features[feature_name] = []
                unique_features[feature_name].append(feature)

            for feature_name, feature_instances in unique_features.items():
                feature_name_escaped = html_module.escape(feature_name)
                feature_id = f"{node_id}_kept_{feature_name}".replace('/', '_').replace(' ', '_')
                count = len(feature_instances)

                # 计算权重平均值
                weights = [f.get('权重', 0) for f in feature_instances]
                avg_weight = sum(weights) / len(weights) if weights else 0
                avg_weight_str = f"{avg_weight:.2f}"

                html += f'<div class="tree-node tree-node-leaf level-{level + 1}">\n'
                html += f'  <div class="tree-node-header" data-node-id="{feature_id}" data-parent-id="{node_id}" data-feature-name="{feature_name_escaped}" data-action="show-feature-detail" style="display:flex; justify-content:space-between; align-items:center;">\n'
                html += f'    <div style="display:flex; align-items:center; gap:8px;">\n'
                html += f'      <span class="tree-node-icon">🔖</span>\n'
                html += f'      <span class="tree-node-title">{feature_name_escaped}</span>\n'
                html += f'      <span style="font-size:0.8rem; color:#999;">×{count}</span>\n'
                html += f'    </div>\n'
                html += f'    <span style="font-size:0.8rem; color:#667eea; font-weight:500;">⚖️ {avg_weight_str}</span>\n'
                html += f'  </div>\n'
                html += '</div>\n'

        # 2. 再显示子分类（如果有）
        if has_children:
            for child_name, child_data in node_data.items():
                if child_name in ['_meta', '特征列表', '帖子数', '特征数', '帖子列表']:
                    continue
                if isinstance(child_data, dict):
                    html += self.generate_feature_tree_node_html(
                        child_name, child_data, level + 1, point_type, current_path
                    )

        html += '    </div>\n'
        html += '  </div>\n'
        html += '</div>\n'

        return html

    def _build_post_to_features_map(self, clustered_data: Dict[str, Any]) -> Dict[str, Dict[str, list]]:
        """从clustered_data构建帖子ID到完整特征对象的映射"""
        post_to_features = {}

        def collect_features(node: Any, point_type: str):
            """递归收集所有特征"""
            if not isinstance(node, dict):
                return

            # 如果有特征列表，收集所有特征
            if '特征列表' in node:
                for feature in node['特征列表']:
                    post_id = feature.get('帖子id')
                    if post_id:
                        if post_id not in post_to_features:
                            post_to_features[post_id] = {
                                '灵感点列表': [],
                                '目的点': [],
                                '关键点列表': []
                            }
                        post_to_features[post_id][point_type].append(feature)

            # 递归处理子节点
            for key, value in node.items():
                if key != '_meta' and isinstance(value, dict):
                    collect_features(value, point_type)

        # 收集三种类型的特征
        for point_type in ['灵感点列表', '目的点', '关键点列表']:
            if point_type in clustered_data:
                collect_features(clustered_data[point_type], point_type)

        return post_to_features

    def _collect_post_ids(self, data: Any, post_ids: set):
        """递归收集所有帖子ID"""
        if isinstance(data, dict):
            if '帖子列表' in data:
                post_ids.update(data['帖子列表'])
            for value in data.values():
                self._collect_post_ids(value, post_ids)

    def _generate_html_head(self) -> str:
        """生成HTML头部"""
        return '''<!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;
        }

        .tabs-container {
            background: white;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            position: sticky;
            top: 0;
            z-index: 1000;
        }

        .tabs-header {
            display: flex;
            border-bottom: 2px solid #e0e0e0;
        }

        .tab-button {
            flex: 1;
            padding: 16px 24px;
            background: white;
            border: none;
            cursor: pointer;
            font-size: 1rem;
            font-weight: 500;
            color: #666;
            transition: all 0.3s;
            border-bottom: 3px solid transparent;
        }

        .tab-button:hover {
            background: #f5f7fa;
            color: #333;
        }

        .tab-button.active {
            color: #667eea;
            border-bottom-color: #667eea;
            background: #f5f7fa;
        }

        .tab-content {
            display: none;
            padding: 20px;
        }

        .tab-content.active {
            display: block;
        }

        .mindmap-container {
            display: flex;
            flex-direction: column;
            gap: 40px;
        }

        .mindmap-section {
            display: flex;
            align-items: flex-start;
            gap: 20px;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            overflow-x: auto;
        }

        .mindmap-root {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
            font-weight: 600;
            font-size: 1.1rem;
            white-space: nowrap;
            box-shadow: 0 2px 6px rgba(0,0,0,0.15);
            flex-shrink: 0;
        }

        .mindmap-tree {
            display: flex;
            flex-direction: column;
            gap: 8px;
            flex: 1;
        }

        .tree-node {
            display: grid;
            grid-template-columns: 350px 1fr;
            gap: 10px;
            align-items: start;
            position: relative;
        }

        .tree-node-header {
            padding: 6px 12px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            border-radius: 6px;
            transition: all 0.2s ease;
            font-size: 0.9rem;
            grid-column: 1;
            max-width: 350px;
            word-wrap: break-word;
            word-break: break-word;
            line-height: 1.4;
        }

        .tree-node-header:hover {
            transform: translateX(2px);
            box-shadow: 0 2px 6px rgba(0,0,0,0.15);
        }

        .tree-node-original > .tree-node-header {
            background: white;
            color: #333;
            border: 1px solid #e0e0e0;
        }

        .tree-node-llm > .tree-node-header {
            background: #4caf50;
            color: white;
        }

        .tree-node-leaf > .tree-node-header {
            background: white;
            color: #333;
            border: 1px solid #e0e0e0;
        }

        .tree-node-icon {
            font-size: 1rem;
        }

        .toggle-icon {
            cursor: pointer;
            transition: transform 0.2s;
            user-select: none;
        }

        .toggle-icon.expanded {
            transform: rotate(90deg);
        }

        .expand-all-icon {
            cursor: pointer;
            font-size: 0.9rem;
            margin-left: auto;
            padding: 2px 6px;
            border-radius: 4px;
            transition: all 0.2s;
            user-select: none;
            background: rgba(0, 0, 0, 0.1);
            color: #333;
        }

        .expand-all-icon:hover {
            background: rgba(0, 0, 0, 0.2);
            transform: scale(1.1);
        }

        .tree-node-llm .expand-all-icon {
            background: rgba(255, 255, 255, 0.3);
            color: white;
        }

        .tree-node-llm .expand-all-icon:hover {
            background: rgba(255, 255, 255, 0.5);
        }

        .tree-node-title {
            font-weight: 500;
            cursor: pointer;
        }

        .tree-node-title:hover {
            text-decoration: underline;
        }

        .tree-node-content {
            grid-column: 2;
            display: flex;
            flex-direction: column;
            min-width: 0;
        }

        .tree-children {
            display: flex;
            flex-direction: column;
            gap: 6px;
            padding-left: 20px;
            border-left: 2px solid #e0e0e0;
        }

        .modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 9999;
        }

        .modal-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
        }

        .modal-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border-radius: 8px;
            max-width: 900px;
            max-height: 85vh;
            width: 95%;
            display: flex;
            flex-direction: column;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        }

        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 16px 20px;
            border-bottom: 1px solid #e0e0e0;
        }

        .modal-header h3 {
            margin: 0;
            font-size: 1.2rem;
            color: #333;
        }

        .modal-close {
            background: none;
            border: none;
            font-size: 1.5rem;
            cursor: pointer;
            color: #666;
            padding: 0;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 4px;
            transition: all 0.2s;
        }

        .modal-close:hover {
            background: #f0f0f0;
            color: #333;
        }

        .modal-body {
            padding: 20px;
            overflow-y: auto;
            flex: 1;
        }

        .modal-section {
            margin-bottom: 20px;
        }

        .modal-section:last-child {
            margin-bottom: 0;
        }

        .modal-section h4 {
            margin: 0 0 12px 0;
            color: #667eea;
            font-size: 1rem;
        }

        .modal-section p {
            margin: 8px 0;
            line-height: 1.6;
            color: #555;
        }

        .modal-list {
            list-style: none;
            padding: 0;
            margin: 0;
        }

        .modal-list-item {
            padding: 10px;
            background: #f8f9fa;
            border-radius: 6px;
            margin-bottom: 8px;
            border-left: 3px solid #667eea;
        }

        .modal-list-item strong {
            display: block;
            margin-bottom: 4px;
            color: #333;
        }

        .modal-list-item p {
            margin: 4px 0 0 0;
            font-size: 0.9rem;
            color: #666;
        }

        .post-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
            gap: 8px;
        }

        .post-card {
            padding: 8px 12px;
            background: #e3f2fd;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.2s;
            text-align: center;
            font-size: 0.85rem;
            color: #1976d2;
        }

        .post-card:hover {
            background: #bbdefb;
            transform: translateY(-2px);
            box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
        }

        .post-title {
            font-size: 1.1rem;
            color: #333;
            margin-bottom: 12px;
        }

        .post-images {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 12px;
            margin: 12px 0;
        }

        .post-image {
            width: 100%;
            max-width: 400px;
            height: auto;
            border-radius: 8px;
            border: 1px solid #e0e0e0;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .post-image:hover {
            transform: scale(1.02);
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
        }

        /* 叶子节点的封面图 */
        .node-thumbnail {
            width: 40px;
            height: 40px;
            object-fit: cover;
            border-radius: 4px;
            border: 1px solid #e0e0e0;
            margin-left: 8px;
        }

        .tree-node-leaf .tree-node-header {
            padding: 4px 8px;
        }

        .more-images {
            color: #666;
            font-size: 0.9rem;
            font-style: italic;
            margin-top: 8px;
        }

        .post-body {
            font-weight: 600;
            margin-top: 12px;
            margin-bottom: 8px;
        }

        .post-body-text {
            background: #f8f9fa;
            padding: 12px;
            border-radius: 6px;
            border-left: 3px solid #667eea;
            line-height: 1.6;
            color: #555;
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .post-meta {
            color: #999;
            font-size: 0.85rem;
            margin-top: 12px;
            font-style: italic;
        }

        /* 弹窗内的TAB样式 */
        .modal-tabs-header {
            display: flex;
            gap: 8px;
            padding: 8px;
            background: #f5f7fa;
            border-radius: 6px;
            margin-bottom: 16px;
            overflow-x: auto;
            flex-wrap: wrap;
        }

        .modal-tab-button {
            padding: 8px 16px;
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9rem;
            color: #666;
            transition: all 0.2s;
            white-space: nowrap;
        }

        .modal-tab-button:hover {
            background: #f0f0f0;
            color: #333;
        }

        .modal-tab-button.active {
            background: #667eea;
            color: white;
            border-color: #667eea;
        }

        .modal-tab-content {
            display: none;
        }

        .modal-tab-content.active {
            display: block;
        }
    </style>
</head>
'''

    def _generate_javascript(self, posts_dir: Path) -> str:
        """生成JavaScript代码"""
        import json as json_module

        post_cache_json = json_module.dumps(self.post_cache, ensure_ascii=True)
        xuanti_point_map_json = json_module.dumps(self.xuanti_point_map, ensure_ascii=True)
        clustered_data_json = json_module.dumps(self.clustered_data, ensure_ascii=True)
        post_to_features_map_json = json_module.dumps(self.post_to_features_map, ensure_ascii=True)

        js_code = '''<script>
document.addEventListener('DOMContentLoaded', function() {
    const postCache = ''' + post_cache_json + ''';
    const xuantiPointMap = ''' + xuanti_point_map_json + ''';
    const clusteredData = ''' + clustered_data_json + ''';
    const postToFeaturesMap = ''' + post_to_features_map_json + ''';

    console.log('数据加载完成');

    // Tab切换功能
    function switchTab(tabId) {
        // 隐藏所有tab内容
        document.querySelectorAll('.tab-content').forEach(tab => {
            tab.classList.remove('active');
        });
        // 移除所有按钮的active状态
        document.querySelectorAll('.tab-button').forEach(btn => {
            btn.classList.remove('active');
        });
        // 显示选中的tab
        document.getElementById(tabId).classList.add('active');
        // 激活对应的按钮
        document.querySelector(`[data-tab="${tabId}"]`).classList.add('active');
    }

    function escapeHtml(text) {
        if (!text) return '';
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    function showModal(title, content) {
        const modal = document.getElementById('detail-modal');
        document.getElementById('modal-title').textContent = title;
        document.getElementById('modal-body').innerHTML = content;
        modal.style.display = 'block';
    }

    function closeModal() {
        document.getElementById('detail-modal').style.display = 'none';
    }

    function toggleNode(nodeId, toggleIcon) {
        const content = document.getElementById(nodeId + '-content');
        if (!content) return;
        if (content.style.display === 'none') {
            content.style.display = 'flex';
            if (toggleIcon) toggleIcon.classList.add('expanded');
        } else {
            content.style.display = 'none';
            if (toggleIcon) toggleIcon.classList.remove('expanded');
        }
    }

    function toggleAllChildren(nodeId) {
        const content = document.getElementById(nodeId + '-content');
        if (!content) return;

        // 找到直接子分类节点（排除叶子节点）
        const childrenContainer = content.querySelector('.tree-children');
        if (!childrenContainer) return;

        const directChildren = Array.from(childrenContainer.children).filter(child => {
            return child.classList.contains('tree-node') && !child.classList.contains('tree-node-leaf');
        });

        if (directChildren.length === 0) return;

        // 检查第一个子分类节点的状态，决定是全部展开还是全部收起
        const firstChild = directChildren[0];
        const firstChildContent = firstChild.querySelector('.tree-node-content');
        const shouldExpand = !firstChildContent || firstChildContent.style.display === 'none';

        // 统一操作所有直接子分类节点
        directChildren.forEach(childNode => {
            toggleNodeRecursive(childNode, shouldExpand);
        });
    }

    function toggleNodeRecursive(node, expand) {
        const content = node.querySelector('.tree-node-content');
        const toggleIcon = node.querySelector('.toggle-icon');

        if (content) {
            content.style.display = expand ? 'flex' : 'none';
        }

        if (toggleIcon) {
            if (expand) {
                toggleIcon.classList.add('expanded');
            } else {
                toggleIcon.classList.remove('expanded');
            }
        }

        // 递归处理所有子节点
        if (content) {
            const childNodes = content.querySelectorAll(':scope > .tree-children > .tree-node');
            childNodes.forEach(childNode => {
                if (!childNode.classList.contains('tree-node-leaf')) {
                    toggleNodeRecursive(childNode, expand);
                }
            });
        }
    }

    function findNodeData(nodeId) {
        const parts = nodeId.split('_');

        // 处理tab2的节点ID格式: tab2_{point_type}_{path}
        let pointType, pathParts;
        if (parts[0] === 'tab2') {
            pointType = parts[1];
            pathParts = parts.slice(2);
        } else {
            // 处理原来的格式
            pointType = parts[0];
            pathParts = parts.slice(1);
        }

        let data = clusteredData[pointType];
        for (const part of pathParts) {
            if (data && data[part]) data = data[part];
            else return null;
        }
        return data;
    }

    function showPointDetail(nodeId, parentId) {
        const parentData = findNodeData(parentId);
        if (!parentData) return;

        const parts = nodeId.split('_');
        const pointType = parts[0];

        // 提取点名称：如果包含 "kept"，则点名称在kept之后
        let pointName;
        const keptIndex = parts.indexOf('kept');
        if (keptIndex !== -1 && keptIndex < parts.length - 1) {
            // 保留的点：格式为 ...node_id_kept_pointName
            pointName = parts.slice(keptIndex + 1).join('_');
        } else {
            // 普通点：格式为 ...node_id_pointName
            pointName = parts[parts.length - 1];
        }

        const pointField = pointType === '灵感点列表' ? '灵感点' : (pointType === '目的点' ? '目的点' : '关键点');
        const points = parentData['点列表'] || [];
        const pointInfo = points.find(p => p[pointField] === pointName);

        if (!pointInfo) {
            showModal(pointName, '<p>未找到点信息</p>');
            return;
        }

        let html = '';

        // 显示点信息
        html += '<div class="modal-section"><h4>点信息</h4>';
        html += '<p><strong>' + escapeHtml(pointInfo[pointField] || '') + '</strong></p>';
        if (pointInfo['描述']) {
            html += '<p>' + escapeHtml(pointInfo['描述']) + '</p>';
        }
        html += '</div>';

        // 获取点对应的帖子ID
        const postId = pointInfo['帖子id'];
        if (!postId) {
            showModal(pointName, html + '<p>该点没有关联的帖子</p>');
            return;
        }

        // 显示帖子详情
        const postData = postCache[postId];
        if (postData) {
            html += '<div class="modal-section"><h4>帖子详情</h4>';

            // 标题
            if (postData.title) {
                html += '<p class="post-title"><strong>标题：</strong>' + escapeHtml(postData.title) + '</p>';
            }

            // 图片
            const images = postData.images || [];
            if (images.length > 0) {
                html += '<div class="post-images">';
                images.forEach((img, idx) => {
                    html += '<img src="' + escapeHtml(img) + '" alt="图片' + (idx + 1) + '" class="post-image" loading="lazy" onclick="window.open(this.src)">';
                });
                html += '</div>';
            }

            // 正文
            if (postData.body_text) {
                html += '<p class="post-body"><strong>正文：</strong></p>';
                html += '<p class="post-body-text">' + escapeHtml(postData.body_text) + '</p>';
            }

            html += '<p class="post-meta">帖子ID: ' + escapeHtml(postId) + '</p>';
            html += '</div>';

            // 显示该帖子的所有选题点（从xuantiPointMap获取完整对象）
            const xuantiPoints = xuantiPointMap[postId];
            if (xuantiPoints) {
                html += '<div class="modal-section"><h4>该帖子的所有选题点</h4>';
                ['灵感点列表', '目的点', '关键点列表'].forEach(type => {
                    const pts = xuantiPoints[type];
                    if (pts && pts.length > 0) {
                        const field = type === '灵感点列表' ? '灵感点' : (type === '目的点' ? '目的点' : '关键点');
                        html += '<h5>' + type + '</h5><ul class="modal-list">';
                        pts.forEach(pt => {
                            html += '<li class="modal-list-item"><strong>' + escapeHtml(pt[field] || '') + '</strong>';
                            if (pt['描述']) html += '<p>' + escapeHtml(pt['描述']) + '</p>';
                            html += '</li>';
                        });
                        html += '</ul>';
                    }
                });
                html += '</div>';
            }
        } else {
            html += '<div class="modal-section"><p>无法加载帖子数据 (ID: ' + escapeHtml(postId) + ')</p></div>';
        }

        showModal(pointName, html);
    }

    function showClassificationDetail(nodeId) {
        const nodeData = findNodeData(nodeId);
        if (!nodeData) return;
        const meta = nodeData._meta || {};
        const parts = nodeId.split('_');
        const nodeName = parts[parts.length - 1];

        let html = '';
        if (meta.分类来源 === 'LLM抽象') {
            html += '<div class="modal-section"><h4>合并描述</h4><p>' + escapeHtml(meta.合并描述 || '') + '</p></div>';
            html += '<div class="modal-section"><h4>合并理由</h4><p>' + escapeHtml(meta.合并理由 || '') + '</p></div>';
            const originals = meta.包含的原分类 || [];
            html += '<div class="modal-section"><h4>包含的原分类</h4><ul class="modal-list">';
            originals.forEach(name => { html += '<li class="modal-list-item">' + escapeHtml(name) + '</li>'; });
            html += '</ul></div>';
        } else if (meta.分类来源 === 'LLM细分') {
            html += '<div class="modal-section"><h4>子分类描述</h4><p>' + escapeHtml(meta.子分类描述 || '') + '</p></div>';
            html += '<div class="modal-section"><h4>分类依据</h4><p>' + escapeHtml(meta.分类依据 || '') + '</p></div>';
        } else if (meta.已细分) {
            html += '<div class="modal-section"><h4>细分说明</h4><p>' + escapeHtml(meta.细分说明 || '') + '</p></div>';
        } else {
            html += '<div class="modal-section"><p>这是原始分类，没有额外的元信息。</p></div>';
        }

        showModal(nodeName, html);
    }

    function showPostDetail(postId) {
        const postData = postCache[postId];
        if (!postData) {
            showModal(postId, '<p>无法加载帖子数据</p>');
            return;
        }

        let html = '<div class="modal-section"><h4>帖子基本信息</h4>';
        html += '<p><strong>帖子ID：</strong>' + escapeHtml(postId) + '</p>';
        html += '<p><strong>标题：</strong>' + escapeHtml(postData.title || '无标题') + '</p>';
        html += '</div>';

        // 显示所有图片
        const images = postData.images || [];
        if (images.length > 0) {
            html += '<div class="modal-section"><h4>帖子图片 (' + images.length + ')</h4>';
            html += '<div class="post-images">';
            images.forEach((img, idx) => {
                html += '<img src="' + escapeHtml(img) + '" alt="图片' + (idx + 1) + '" class="post-image" loading="lazy" onclick="window.open(this.src)">';
            });
            html += '</div></div>';
        }

        // 显示正文
        if (postData.body_text) {
            html += '<div class="modal-section"><h4>正文</h4>';
            html += '<p class="post-body-text">' + escapeHtml(postData.body_text) + '</p>';
            html += '</div>';
        }

        // 显示该帖子的选题点信息（从xuantiPointMap获取）
        const xuantiPoints = xuantiPointMap[postId];
        if (xuantiPoints) {
            html += '<div class="modal-section"><h4>选题点信息</h4>';
            ['灵感点列表', '目的点', '关键点列表'].forEach(type => {
                const points = xuantiPoints[type];
                if (points && points.length > 0) {
                    const field = type === '灵感点列表' ? '灵感点' : (type === '目的点' ? '目的点' : '关键点');
                    html += '<h5>' + type + ' (' + points.length + ')</h5><ul class="modal-list">';
                    points.forEach(point => {
                        html += '<li class="modal-list-item"><strong>' + escapeHtml(point[field] || '') + '</strong>';
                        if (point['描述']) html += '<p>' + escapeHtml(point['描述']) + '</p>';
                        html += '</li>';
                    });
                    html += '</ul>';
                }
            });
            html += '</div>';
        }

        // 显示该帖子的特征信息（从postToFeaturesMap获取）
        const xuantiFeatures = postToFeaturesMap[postId];
        if (xuantiFeatures && Object.keys(xuantiFeatures).length > 0) {
            html += '<div class="modal-section"><h4>提取的特征</h4>';
            ['灵感点列表', '目的点', '关键点列表'].forEach(type => {
                const features = xuantiFeatures[type];
                if (features && features.length > 0) {
                    // 去重特征名称
                    const uniqueFeatures = {};
                    features.forEach(f => {
                        const name = f['特征名称'];
                        if (!uniqueFeatures[name]) {
                            uniqueFeatures[name] = [];
                        }
                        uniqueFeatures[name].push(f);
                    });

                    html += '<h5>' + type + ' (' + Object.keys(uniqueFeatures).length + ' 个特征)</h5>';
                    html += '<div class="post-grid">';
                    Object.keys(uniqueFeatures).forEach(name => {
                        html += '<div class="post-card" style="background:#f0f8ff;">';
                        html += escapeHtml(name) + ' (' + uniqueFeatures[name].length + ')';
                        html += '</div>';
                    });
                    html += '</div>';
                }
            });
            html += '</div>';
        }

        showModal(postId, html);
    }

    // 暴露showPostDetail为全局函数，以便在动态生成的HTML中使用
    window.showPostDetail = showPostDetail;

    function showTab1PointDetail(nodeId) {
        // nodeId格式: tab1_{post_id}_{point_type}_{idx}
        const parts = nodeId.split('_');
        const postId = parts[1];
        const pointType = parts.slice(2, -1).join('_');
        const idx = parseInt(parts[parts.length - 1]);

        const xuantiPoint = xuantiPointMap[postId];
        if (!xuantiPoint) {
            showModal('错误', '<p>未找到帖子数据</p>');
            return;
        }

        const points = xuantiPoint[pointType] || [];
        const point = points[idx];
        if (!point) {
            showModal('错误', '<p>未找到点信息</p>');
            return;
        }

        const pointField = pointType === '灵感点列表' ? '灵感点' : (pointType === '目的点' ? '目的点' : '关键点');
        const pointName = point[pointField] || '';

        let html = '<div class="modal-section"><h4>点信息</h4>';
        html += '<p><strong>' + escapeHtml(pointName) + '</strong></p>';
        if (point['描述']) {
            html += '<p>' + escapeHtml(point['描述']) + '</p>';
        }
        html += '</div>';

        // 显示提取的特征
        const features = point['提取的特征'] || [];
        if (features.length > 0) {
            html += '<div class="modal-section"><h4>提取的特征</h4><ul class="modal-list">';
            features.forEach(feature => {
                html += '<li class="modal-list-item">';
                html += '<strong>' + escapeHtml(feature['特征名称'] || '') + '</strong>';
                html += '<p>分类: ' + escapeHtml(feature['一级分类'] || '') + ' / ' + escapeHtml(feature['二级分类'] || '') + '</p>';
                html += '<p>权重: ' + feature['权重'] + '</p>';
                html += '</li>';
            });
            html += '</ul></div>';
        }

        // 显示帖子信息
        const postData = postCache[postId];
        if (postData) {
            html += '<div class="modal-section"><h4>帖子信息</h4>';
            if (postData.title) {
                html += '<p><strong>标题：</strong>' + escapeHtml(postData.title) + '</p>';
            }
            const images = postData.images || [];
            if (images.length > 0) {
                html += '<div class="post-images">';
                images.forEach((img, idx) => {
                    html += '<img src="' + escapeHtml(img) + '" alt="图片' + (idx + 1) + '" class="post-image" loading="lazy" onclick="window.open(this.src)">';
                });
                html += '</div>';
            }
            html += '</div>';
        }

        showModal(pointName, html);
    }

    function showFeatureDetail(featureName, parentId) {
        const parentData = findNodeData(parentId);
        if (!parentData) {
            console.error('无法找到父节点数据:', parentId);
            return;
        }

        const features = parentData['特征列表'] || [];
        const matchedFeatures = features.filter(f => f['特征名称'] === featureName);

        if (matchedFeatures.length === 0) {
            showModal(featureName, '<p>未找到特征信息</p>');
            return;
        }

        // 收集所有相关的帖子ID（去重）
        const uniquePostIds = [];
        const postIdSet = new Set();
        matchedFeatures.forEach(feature => {
            const postId = feature['帖子id'];
            if (postId && postCache[postId] && !postIdSet.has(postId)) {
                postIdSet.add(postId);
                uniquePostIds.push(postId);
            }
        });

        if (uniquePostIds.length === 0) {
            showModal(featureName, '<p>未找到相关帖子</p>');
            return;
        }

        let html = '';

        // 如果有多个帖子，生成TAB切换界面
        if (uniquePostIds.length > 1) {
            html += '<div class="modal-tabs-header">';
            uniquePostIds.forEach((postId, index) => {
                const postData = postCache[postId];
                const tabTitle = postData.title ? postData.title.substring(0, 15) + '...' : 'Post ' + (index + 1);
                const activeClass = index === 0 ? ' active' : '';
                html += '<button class="modal-tab-button' + activeClass + '" data-tab-id="post-tab-' + index + '">';
                html += escapeHtml(tabTitle);
                html += '</button>';
            });
            html += '</div>';
        }

        // 为每个帖子生成完整信息
        uniquePostIds.forEach((postId, index) => {
            const postData = postCache[postId];
            const activeClass = index === 0 ? ' active' : '';

            html += '<div class="modal-tab-content' + activeClass + '" id="post-tab-' + index + '">';

            // 特征基本信息
            html += '<div class="modal-section"><h4>特征：' + escapeHtml(featureName) + '</h4>';
            const postFeatures = matchedFeatures.filter(f => f['帖子id'] === postId);
            if (postFeatures.length > 0) {
                html += '<p>权重: ' + (postFeatures[0]['权重'] || 0) + '</p>';
                html += '<p>来自点: ' + escapeHtml(postFeatures[0]['所属点'] || '') + '</p>';
                if (postFeatures[0]['点描述']) {
                    html += '<p style="font-size:0.9rem; color:#666;">点描述: ' + escapeHtml(postFeatures[0]['点描述']) + '</p>';
                }
            }
            html += '</div>';

            // 帖子完整信息
            html += '<div class="modal-section"><h4>帖子详情</h4>';
            html += '<p><strong>帖子ID：</strong>' + escapeHtml(postId) + '</p>';

            if (postData.title) {
                html += '<p class="post-title"><strong>标题：</strong>' + escapeHtml(postData.title) + '</p>';
            }

            // 图片
            const images = postData.images || [];
            if (images.length > 0) {
                html += '<div class="post-images">';
                images.forEach((img, idx) => {
                    html += '<img src="' + escapeHtml(img) + '" alt="图片' + (idx + 1) + '" class="post-image" loading="lazy" onclick="window.open(this.src)">';
                });
                html += '</div>';
            }

            // 正文
            if (postData.body_text) {
                html += '<p class="post-body"><strong>正文：</strong></p>';
                html += '<p class="post-body-text">' + escapeHtml(postData.body_text) + '</p>';
            }

            // 帖子元信息
            if (postData.publish_time) {
                html += '<p class="post-meta">发布时间: ' + escapeHtml(postData.publish_time) + '</p>';
            }
            if (postData.like_count !== undefined) {
                html += '<p class="post-meta">点赞: ' + postData.like_count + ' | 收藏: ' + (postData.collect_count || 0) + ' | 评论: ' + (postData.comment_count || 0) + '</p>';
            }

            html += '</div>';

            // 该帖子的所有选题点
            const xuantiPoints = xuantiPointMap[postId];
            if (xuantiPoints) {
                html += '<div class="modal-section"><h4>该帖子的所有选题点</h4>';
                ['灵感点列表', '目的点', '关键点列表'].forEach(type => {
                    const pts = xuantiPoints[type];
                    if (pts && pts.length > 0) {
                        const field = type === '灵感点列表' ? '灵感点' : (type === '目的点' ? '目的点' : '关键点');
                        html += '<h5>' + type + '</h5><ul class="modal-list">';
                        pts.forEach(pt => {
                            html += '<li class="modal-list-item"><strong>' + escapeHtml(pt[field] || '') + '</strong>';
                            if (pt['描述']) html += '<p>' + escapeHtml(pt['描述']) + '</p>';

                            // 显示提取的特征
                            const ptFeatures = pt['提取的特征'] || [];
                            if (ptFeatures.length > 0) {
                                html += '<p style="font-size:0.85rem; margin-top:8px;"><strong>特征:</strong> ';
                                ptFeatures.forEach((f, idx) => {
                                    if (idx > 0) html += ', ';
                                    html += escapeHtml(f['特征名称'] || '');
                                });
                                html += '</p>';
                            }
                            html += '</li>';
                        });
                        html += '</ul>';
                    }
                });
                html += '</div>';
            }

            html += '</div>'; // 结束 modal-tab-content
        });

        showModal(featureName, html);

        // 添加TAB切换事件监听
        setTimeout(() => {
            document.querySelectorAll('.modal-tab-button').forEach(btn => {
                btn.addEventListener('click', function() {
                    const tabId = this.getAttribute('data-tab-id');

                    // 移除所有active状态
                    document.querySelectorAll('.modal-tab-button').forEach(b => b.classList.remove('active'));
                    document.querySelectorAll('.modal-tab-content').forEach(c => c.classList.remove('active'));

                    // 激活选中的tab
                    this.classList.add('active');
                    document.getElementById(tabId).classList.add('active');
                });
            });
        }, 100);
    }

    document.body.addEventListener('click', function(e) {
        const target = e.target.closest('[data-action]');
        if (!target) {
            if (!e.target.closest('.modal-content')) closeModal();
            return;
        }

        const action = target.getAttribute('data-action');
        const nodeId = target.getAttribute('data-node-id');

        switch(action) {
            case 'toggle-node':
                toggleNode(nodeId, target);
                break;
            case 'toggle-all-children':
                toggleAllChildren(nodeId);
                break;
            case 'show-point-detail':
                const parentId = target.getAttribute('data-parent-id');
                showPointDetail(nodeId, parentId);
                break;
            case 'show-tab1-point-detail':
                showTab1PointDetail(nodeId);
                break;
            case 'show-feature-detail':
                const featureParentId = target.getAttribute('data-parent-id');
                const featureName = target.getAttribute('data-feature-name');
                showFeatureDetail(featureName, featureParentId);
                break;
            case 'show-classification-detail':
                showClassificationDetail(nodeId);
                break;
            case 'show-post':
                const postId = target.getAttribute('data-post-id');
                if (postId) showPostDetail(postId);
                break;
            case 'close-modal':
                closeModal();
                break;
        }

        if (target.tagName === 'A') e.preventDefault();
    });

    // Tab切换事件监听
    document.querySelectorAll('.tab-button').forEach(btn => {
        btn.addEventListener('click', function() {
            const tabId = this.getAttribute('data-tab');
            switchTab(tabId);
        });
    });

    // Tab3: 维度关联分析交互功能
    let dimensionAssociations = ''' + json_module.dumps(self.dimension_associations, ensure_ascii=True) + ''';
    let expandedOrthogonalCombinations = ''' + json_module.dumps(getattr(self, 'expanded_orthogonal_combinations', None), ensure_ascii=True) + ''';
    let enrichedXuantiPointMap = ''' + json_module.dumps(getattr(self, 'enriched_xuanti_point_map', None), ensure_ascii=True) + ''';
    let currentMode = 'single'; // 当前模式：single（单维度）或 three（三维正交）

    // 模式切换
    document.querySelectorAll('.mode-button').forEach(btn => {
        btn.addEventListener('click', function() {
            const mode = this.getAttribute('data-mode');
            currentMode = mode;

            // 更新按钮样式
            document.querySelectorAll('.mode-button').forEach(b => {
                if (b.getAttribute('data-mode') === mode) {
                    b.style.background = 'white';
                    b.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
                    b.classList.add('active');
                } else {
                    b.style.background = 'transparent';
                    b.style.boxShadow = 'none';
                    b.classList.remove('active');
                }
            });

            // 更新描述
            document.getElementById('desc-single').style.display = mode === 'single' ? 'inline' : 'none';
            document.getElementById('desc-three').style.display = mode === 'three' ? 'inline' : 'none';

            // 切换布局显示
            const singleLayout = document.getElementById('single-dim-layout');
            const threeLayout = document.getElementById('three-dim-layout');

            if (mode === 'single') {
                singleLayout.style.display = 'grid';
                threeLayout.style.display = 'none';
            } else {
                singleLayout.style.display = 'none';
                threeLayout.style.display = 'block';
                // 自动加载所有三维正交扩展结果
                loadAllOrthogonalResults();
            }

            // 清除所有高亮和封面图
            clearAllHighlights();
        });
    });

    function clearAllHighlights() {
        // 隐藏所有封面图容器
        document.querySelectorAll('.tab3-thumbnails').forEach(t => {
            t.style.display = 'none';
            t.innerHTML = '';
        });

        // 清除所有高亮
        document.querySelectorAll('.tab3-node-header').forEach(h => {
            const originalBg = h.getAttribute('data-original-bg');
            const originalBorder = h.getAttribute('data-original-border');

            h.style.backgroundColor = originalBg || '';
            h.style.border = originalBorder || '';
            h.style.borderWidth = '';
            h.style.boxShadow = '0 1px 2px rgba(0,0,0,0.05)';
            h.style.fontWeight = '';
            h.style.backgroundImage = '';
            h.style.backgroundOrigin = '';
            h.style.backgroundClip = '';

            // 删除所有组号标签（包括容器）
            const badges = h.querySelectorAll('.group-badge');
            badges.forEach(b => b.remove());

            // 删除Jaccard相似度标签
            const jaccardBadges = h.querySelectorAll('.jaccard-badge');
            jaccardBadges.forEach(b => b.remove());

            // 删除组号标签容器（如果是独立的span）
            const badgeContainers = Array.from(h.children).filter(child =>
                child.tagName === 'SPAN' && child.style.display === 'inline-flex'
            );
            badgeContainers.forEach(bc => bc.remove());
        });
    }

    // Tab3树节点切换
    document.addEventListener('click', function(e) {
        const toggleBtn = e.target.closest('.tab3-toggle');
        if (toggleBtn) {
            const targetId = toggleBtn.getAttribute('data-target');
            const content = document.getElementById(targetId);
            if (content) {
                if (content.style.display === 'none' || content.style.display === '') {
                    content.style.display = 'block';
                    toggleBtn.textContent = '▼';
                } else {
                    content.style.display = 'none';
                    toggleBtn.textContent = '▶';
                }
            }
            e.stopPropagation();
        }
    });

    // Tab3节点点击高亮关联分类
    document.addEventListener('click', function(e) {
        const header = e.target.closest('.tab3-node-header');
        if (header && !e.target.closest('.tab3-toggle')) {
            const clickedDimension = header.getAttribute('data-dimension');
            const clickedPath = header.getAttribute('data-path');
            const nodeId = header.getAttribute('data-node-id');

            console.log('点击了分类:', clickedDimension, clickedPath, '模式:', currentMode);

            // 清除所有高亮和封面图
            clearAllHighlights();

            // 高亮当前点击的节点 - 使用蓝色
            header.style.borderColor = '#2196F3';
            header.style.borderWidth = '3px';
            header.style.boxShadow = '0 0 12px rgba(33, 150, 243, 0.6)';
            header.style.fontWeight = '600';

            // 显示该分类下的帖子封面图
            showCategoryThumbnails(clickedDimension, clickedPath, nodeId);

            // 根据模式执行不同的关联查找
            if (currentMode === 'single') {
                // 单维度关联模式
                highlightSingleDimensionAssociations(clickedDimension, clickedPath);
            } else if (currentMode === 'three') {
                // 三维正交关联模式
                if (clickedDimension === '灵感点列表') {
                    highlightThreeDimensionAssociations(clickedPath);
                } else {
                    alert('三维正交关联模式只支持点击灵感点分类');
                }
            }
        }
    });

    function highlightSingleDimensionAssociations(sourceDimension, sourcePath) {
        if (!dimensionAssociations || !dimensionAssociations['单维度关联分析']) {
            console.log('dimensionAssociations数据不存在');
            return;
        }

        const singleDimAssociations = dimensionAssociations['单维度关联分析'];

        // 维度名称映射：从data-dimension的值映射到JSON中的维度名称
        const dimensionNameMap = {
            '灵感点列表': '灵感点维度',
            '目的点': '目的点维度',
            '关键点列表': '关键点维度'
        };

        const sourceDimensionKey = dimensionNameMap[sourceDimension];
        if (!sourceDimensionKey || !singleDimAssociations[sourceDimensionKey]) {
            console.log('未找到维度:', sourceDimension, sourceDimensionKey);
            return;
        }

        const dimensionData = singleDimAssociations[sourceDimensionKey];
        console.log('查找关联关系，维度:', sourceDimensionKey, '路径:', sourcePath);

        let foundCount = 0;

        // 遍历该维度下的所有关联关系（如 灵感点→目的点、灵感点→关键点）
        for (const relationKey in dimensionData) {
            if (relationKey === '说明') continue;

            const relationData = dimensionData[relationKey];

            // 在该关联关系中查找匹配sourcePath的分类
            if (relationData[sourcePath]) {
                const categoryData = relationData[sourcePath];
                console.log('找到关联关系:', relationKey, '分类:', sourcePath);

                // 确定目标维度和关联字段名
                let targetDimension, associationFieldName;
                if (relationKey.includes('→目的点')) {
                    targetDimension = '目的点';
                    associationFieldName = '与目的点的关联';
                } else if (relationKey.includes('→关键点')) {
                    targetDimension = '关键点列表';
                    associationFieldName = '与关键点的关联';
                } else if (relationKey.includes('→灵感点')) {
                    targetDimension = '灵感点列表';
                    associationFieldName = '与灵感点的关联';
                }

                // 获取关联列表并高亮所有目标分类
                const associations = categoryData[associationFieldName] || [];
                console.log('关联数量:', associations.length);
                associations.forEach(assoc => {
                    const targetPath = assoc['目标分类'];
                    const commonPostIds = assoc['共同帖子ID'] || [];
                    const jaccardSimilarity = assoc['Jaccard相似度'] || 0;
                    if (targetPath) {
                        console.log('高亮目标:', targetDimension, targetPath, '共同帖子数:', commonPostIds.length, 'Jaccard:', jaccardSimilarity);
                        highlightNodeByPath(targetDimension, targetPath, commonPostIds, jaccardSimilarity);
                        foundCount++;
                    }
                });
            }
        }

        if (foundCount === 0) {
            console.log('未找到任何关联');
        } else {
            console.log('共高亮', foundCount, '个关联分类');
        }
    }

    function highlightNodeByPath(dimension, path, commonPostIds, jaccardSimilarity) {
        // 根据维度选择不同的颜色
        let borderColor, bgColor, boxShadowColor;

        if (dimension === '灵感点列表') {
            // 灵感点：紫色
            borderColor = '#9C27B0';
            bgColor = '#F3E5F5';
            boxShadowColor = 'rgba(156, 39, 176, 0.7)';
        } else if (dimension === '目的点') {
            // 目的点：粉红色
            borderColor = '#E91E63';
            bgColor = '#FCE4EC';
            boxShadowColor = 'rgba(233, 30, 99, 0.7)';
        } else if (dimension === '关键点列表') {
            // 关键点：橙色
            borderColor = '#FF9800';
            bgColor = '#FFF3E0';
            boxShadowColor = 'rgba(255, 152, 0, 0.7)';
        } else {
            // 默认颜色
            borderColor = '#E91E63';
            bgColor = '#FCE4EC';
            boxShadowColor = 'rgba(233, 30, 99, 0.7)';
        }

        const headers = document.querySelectorAll('.tab3-node-header');
        headers.forEach(header => {
            if (header.getAttribute('data-dimension') === dimension &&
                header.getAttribute('data-path') === path) {
                // 使用根据维度选择的颜色高亮关联节点
                header.style.borderColor = borderColor;
                header.style.borderWidth = '3px';
                header.style.boxShadow = `0 0 16px ${boxShadowColor}`;
                header.style.backgroundColor = bgColor;
                header.style.fontWeight = '600';

                // 添加Jaccard相似度标签
                const existingBadge = header.querySelector('.jaccard-badge');
                if (existingBadge) {
                    existingBadge.remove();
                }

                if (jaccardSimilarity !== undefined && jaccardSimilarity !== null) {
                    const badge = document.createElement('span');
                    badge.className = 'jaccard-badge';
                    badge.textContent = `J: ${(jaccardSimilarity * 100).toFixed(1)}%`;
                    badge.style.cssText = `
                        display: inline-block;
                        background: ${borderColor};
                        color: white;
                        padding: 2px 6px;
                        border-radius: 8px;
                        font-size: 0.7rem;
                        font-weight: 600;
                        margin-left: 8px;
                        vertical-align: middle;
                    `;
                    badge.title = `Jaccard相似度: ${(jaccardSimilarity * 100).toFixed(2)}%`;

                    // 插入到节点文本后面
                    const textNode = Array.from(header.childNodes).find(node => node.nodeType === Node.TEXT_NODE);
                    if (textNode) {
                        textNode.after(badge);
                    } else {
                        header.appendChild(badge);
                    }
                }

                // 为关联的分类显示共同帖子的封面图
                const nodeId = header.getAttribute('data-node-id');
                if (nodeId && commonPostIds && commonPostIds.length > 0) {
                    showCategoryThumbnails(dimension, path, nodeId, commonPostIds);
                }
            }
        });
    }

    // 生成唯一颜色的函数，使用HSL色彩空间
    function generateUniqueColor(index, total) {
        // 使用黄金角度(137.5°)分布色相，确保颜色尽可能分散
        const hue = (index * 137.5) % 360;
        // 饱和度在60%-90%之间变化
        const saturation = 60 + (index % 4) * 10;
        // 亮度在45%-55%之间变化，确保颜色既鲜艳又不刺眼
        const lightness = 45 + (index % 3) * 5;

        const borderColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
        const bgColor = `hsl(${hue}, ${saturation}%, ${lightness + 40}%)`;

        return { border: borderColor, bg: bgColor };
    }

    // 自动加载所有灵感点的三维正交扩展结果
    function loadAllOrthogonalResults() {
        if (!expandedOrthogonalCombinations || !expandedOrthogonalCombinations['聚类结果'] || !expandedOrthogonalCombinations['聚类结果']['聚类详情']) {
            console.log('聚类结果数据不存在');
            document.getElementById('orthogonal-results').innerHTML = `
                <div style="text-align: center; padding: 60px 20px; color: #999;">
                    <p style="font-size: 1.1rem; margin-bottom: 10px;">未加载聚类结果数据</p>
                </div>
            `;
            return;
        }

        const expandedResults = expandedOrthogonalCombinations['聚类结果']['聚类详情'];
        console.log('加载所有聚类结果，共', expandedResults.length, '组');

        const resultsContainer = document.getElementById('orthogonal-results');

        let html = '';
        html += `<div style="margin-bottom: 20px; padding: 16px; background: #f0f7ff; border-radius: 8px; border-left: 4px solid #667eea;">`;
        html += `<h3 style="color: #667eea; margin: 0; font-size: 1.1rem;">🌟 三维正交扩展结果（共 ${expandedResults.length} 组）</h3>`;
        html += `<p style="color: #666; font-size: 0.85rem; margin: 8px 0 0 0;">点击每组标题可快速展开/收起</p>`;
        html += `</div>`;

        // 遍历所有组
        expandedResults.forEach((result, index) => {
            // 新数据结构：result是聚类详情中的一项
            const dimensionMerge = result['维度合并'];
            const clusterSize = result['聚类大小'];
            const coveragePosts = result['聚类覆盖帖子数'];

            // 获取各维度的第一个叶子分类用于显示
            const lingganLeafClasses = dimensionMerge['灵感点'].map(item => item['叶子分类']);
            const mudiLeafClasses = dimensionMerge['目的点'].map(item => item['叶子分类']);
            const guanjianLeafClasses = dimensionMerge['关键点'].map(item => item['叶子分类']);

            const groupId = `group-${index}`;

            // 组容器 - 添加点击展开/收起功能
            html += `<div style="margin-bottom: 20px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">`;

            // 组头部 - 可点击展开/收起，添加sticky定位
            html += `<div onclick="toggleGroup('${groupId}')" style="position: sticky; top: 0; z-index: 100; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: all 0.3s; box-shadow: 0 2px 8px rgba(0,0,0,0.15);">`;
            html += `<div style="flex: 1; display: flex; flex-direction: column; gap: 8px;">`;

            // 第一行：聚类号 + 大小 + 覆盖帖子数
            html += `<div style="display: flex; align-items: center; gap: 12px;">`;
            html += `<span style="background: rgba(255,255,255,0.2); padding: 6px 14px; border-radius: 16px; font-weight: 700; font-size: 0.95rem;">聚类 #${result['聚类ID']}</span>`;
            html += `<span style="background: rgba(255,255,255,0.15); padding: 4px 10px; border-radius: 12px; font-size: 0.85rem;">组合数: ${clusterSize}</span>`;
            html += `<span style="background: rgba(255,255,255,0.15); padding: 4px 10px; border-radius: 12px; font-size: 0.85rem;">覆盖帖子: ${coveragePosts}</span>`;
            html += `</div>`;

            // 第二行：半选题维度（显示前几个）
            html += `<div style="display: flex; align-items: center; gap: 8px; font-size: 0.85rem; flex-wrap: wrap;">`;
            html += `<span style="background: rgba(33, 150, 243, 0.3); padding: 3px 10px; border-radius: 4px;">💡 ${escapeHtml(lingganLeafClasses.slice(0, 2).join(', '))}${lingganLeafClasses.length > 2 ? '...' : ''}</span>`;
            html += `<span style="color: rgba(255,255,255,0.7);">+</span>`;
            html += `<span style="background: rgba(255, 152, 0, 0.3); padding: 3px 10px; border-radius: 4px;">🎯 ${escapeHtml(mudiLeafClasses.slice(0, 2).join(', '))}${mudiLeafClasses.length > 2 ? '...' : ''}</span>`;
            html += `<span style="color: rgba(255,255,255,0.7);">+</span>`;
            html += `<span style="background: rgba(156, 39, 176, 0.3); padding: 3px 10px; border-radius: 4px;">🔑 ${escapeHtml(guanjianLeafClasses.slice(0, 2).join(', '))}${guanjianLeafClasses.length > 2 ? '...' : ''}</span>`;
            html += `</div>`;

            html += `</div>`;
            html += `<div id="${groupId}-icon" style="font-size: 1.2rem; transition: transform 0.3s; flex-shrink: 0;">▼</div>`;
            html += `</div>`;

            // 组内容 - 默认展开
            html += `<div id="${groupId}" style="background: #f8f9fa; padding: 20px;">`;

            // 调用原有的showOrthogonalResults逻辑，但嵌入到这里
            html += generateGroupContent(result, index);

            html += `</div>`;
            html += `</div>`;
        });

        resultsContainer.innerHTML = html;
    }

    // 展开/收起组的函数
    window.toggleGroup = function(groupId) {
        const content = document.getElementById(groupId);
        const icon = document.getElementById(groupId + '-icon');

        if (content.style.display === 'none') {
            content.style.display = 'block';
            icon.style.transform = 'rotate(0deg)';
            icon.textContent = '▼';
        } else {
            content.style.display = 'none';
            icon.style.transform = 'rotate(-90deg)';
            icon.textContent = '▶';
        }
    };

    // 生成单组内容的函数
    function generateGroupContent(result, index) {
        // 新数据结构：result是聚类详情中的一项
        const dimensionMerge = result['维度合并'];
        const dimensionExpansion = result['维度扩展'];

        let html = '';

        // 扩展结果 - 表格式
        const expandId = `expand-${index}`;
        html += `<div style="background: white; padding: 16px; border-radius: 8px; margin-bottom: 12px; border-left: 4px solid #4caf50;">`;

        // 计算各维度的扩展组合总数
        const lingganExpandList = dimensionExpansion ? dimensionExpansion['灵感点'] : [];
        const mudiExpandList = dimensionExpansion ? dimensionExpansion['目的点'] : [];
        const guanjianExpandList = dimensionExpansion ? dimensionExpansion['关键点'] : [];

        // 获取各维度的组合数（累加所有叶子分类的扩展组合数）
        let lingganCount = 0;
        if (lingganExpandList && lingganExpandList.length > 0) {
            lingganExpandList.forEach(item => {
                lingganCount += (item['扩展组合数'] || 0);
            });
        }

        let mudiCount = 0;
        if (mudiExpandList && mudiExpandList.length > 0) {
            mudiExpandList.forEach(item => {
                mudiCount += (item['扩展组合数'] || 0);
            });
        }

        let guanjianTotalCount = 0;
        if (guanjianExpandList && guanjianExpandList.length > 0) {
            guanjianExpandList.forEach(item => {
                guanjianTotalCount += (item['扩展组合数'] || 0);
            });
        }

        // 计算总的扩展组合数（不是笛卡尔积，是各维度的总和）
        const totalCombinations = lingganCount + mudiCount + guanjianTotalCount;

        html += `<div style="display: flex; justify-content: space-between; align-items: center; cursor: pointer;" onclick="document.getElementById('${expandId}').style.display = document.getElementById('${expandId}').style.display === 'none' ? 'block' : 'none';">`;
        html += `<h4 style="color: #4caf50; margin: 0; font-size: 0.95rem;">🌟 半选题维度的点模式映射（共 ${totalCombinations} 个扩展组合）</h4>`;
        html += `<span style="color: #999; font-size: 0.85rem;">点击展开/收起</span>`;
        html += `</div>`;

        html += `<div id="${expandId}" style="display: none; margin-top: 12px; padding-top: 12px; border-top: 1px solid #e0e0e0;">`;

        // 获取组合详情，为每个叶子分类保存其组合列表（新数据结构：维度扩展是数组）
        const lingganCombosList = [];
        const lingganMergeInfo = [];
        if (lingganExpandList && lingganExpandList.length > 0) {
            lingganExpandList.forEach(item => {
                const combos = item['组合详情'] || [];
                // 过滤：只保留包含扩展叶子分类的组合（叶子分类组合长度>1表示有扩展）
                const filteredCombos = combos.filter(combo => {
                    const leafCombo = combo['叶子分类组合'] || [];
                    return leafCombo.length > 1; // 只有一个叶子分类表示没有扩展
                });
                lingganCombosList.push({
                    leafClass: item['叶子分类'],
                    combos: filteredCombos
                });
                lingganMergeInfo.push({
                    leafClass: item['叶子分类'],
                    frequency: item['出现频率']
                });
            });
        }

        const mudiCombosList = [];
        const mudiMergeInfo = [];
        if (mudiExpandList && mudiExpandList.length > 0) {
            mudiExpandList.forEach(item => {
                const combos = item['组合详情'] || [];
                const filteredCombos = combos.filter(combo => {
                    const leafCombo = combo['叶子分类组合'] || [];
                    return leafCombo.length > 1;
                });
                mudiCombosList.push({
                    leafClass: item['叶子分类'],
                    combos: filteredCombos
                });
                mudiMergeInfo.push({
                    leafClass: item['叶子分类'],
                    frequency: item['出现频率']
                });
            });
        }

        const guanjianCombosList = [];
        const guanjianMergeInfo = [];
        if (guanjianExpandList && guanjianExpandList.length > 0) {
            guanjianExpandList.forEach(item => {
                const combos = item['组合详情'] || [];
                const filteredCombos = combos.filter(combo => {
                    const leafCombo = combo['叶子分类组合'] || [];
                    return leafCombo.length > 1;
                });
                guanjianCombosList.push({
                    keyLeafClass: item['叶子分类'],
                    combos: filteredCombos
                });
                guanjianMergeInfo.push({
                    leafClass: item['叶子分类'],
                    frequency: item['出现频率']
                });
            });
        }

        // 计算最大行数（所有叶子分类的扩展组合数的最大值）
        let maxRows = 0;
        lingganCombosList.forEach(item => {
            maxRows = Math.max(maxRows, item.combos.length);
        });
        mudiCombosList.forEach(item => {
            maxRows = Math.max(maxRows, item.combos.length);
        });
        guanjianCombosList.forEach(item => {
            maxRows = Math.max(maxRows, item.combos.length);
        });

        // 计算表格总列数：标签列 + 灵感点列数 + 目的点列数 + 关键点列数
        const lingganColCount = lingganMergeInfo.length || 1;
        const mudiColCount = mudiMergeInfo.length || 1;
        const guanjianColCount = guanjianMergeInfo.length || 1;
        const totalColumns = 1 + lingganColCount + mudiColCount + guanjianColCount;

        html += `<div style="overflow-x: auto;">`;
        html += `<table style="width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden;">`;

        // 表头
        html += `<thead>`;
        html += `<tr style="background: #667eea; color: white;">`;
        html += `<th style="padding: 12px; text-align: left; font-size: 0.85rem; border-right: 1px solid rgba(255,255,255,0.2);">半选题：</th>`;

        // 灵感点表头 - 跨多列
        html += `<th colspan="${lingganColCount}" style="padding: 12px; text-align: center; font-size: 0.85rem; border-right: 1px solid rgba(255,255,255,0.2);">💡 灵感点</th>`;

        // 目的点表头 - 跨多列
        html += `<th colspan="${mudiColCount}" style="padding: 12px; text-align: center; font-size: 0.85rem; border-right: 1px solid rgba(255,255,255,0.2);">🎯 目的点</th>`;

        // 关键点表头 - 跨多列
        html += `<th colspan="${guanjianColCount}" style="padding: 12px; text-align: center; font-size: 0.85rem;">🔑 关键点</th>`;
        html += `</tr>`;

        // 半选题值行（显示叶子分类名称）
        html += `<tr style="background: #f5f5f5; font-weight: 600;">`;
        html += `<td style="padding: 8px; text-align: center; font-size: 0.75rem; color: #999; border-right: 1px solid #e0e0e0;">半选题</td>`;

        // 灵感点叶子分类 - 每个占一列
        lingganMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 10px; text-align: center; ${idx < lingganMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.8rem; display: inline-block;">${escapeHtml(info.leafClass)}</span>`;
            html += `</td>`;
        });

        // 目的点叶子分类 - 每个占一列
        mudiMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 10px; text-align: center; ${idx < mudiMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.8rem; display: inline-block;">${escapeHtml(info.leafClass)}</span>`;
            html += `</td>`;
        });

        // 关键点叶子分类 - 每个占一列
        guanjianMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 10px; text-align: center; ${idx < guanjianMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''}">`;
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.8rem; display: inline-block;">${escapeHtml(info.leafClass)}</span>`;
            html += `</td>`;
        });
        html += `</tr>`;

        // 频率行（显示每个叶子分类的出现频率）
        html += `<tr style="background: #fafafa;">`;
        html += `<td style="padding: 8px; text-align: center; font-size: 0.75rem; color: #999; border-right: 1px solid #e0e0e0; border-bottom: 2px solid #667eea;">出现频率</td>`;

        // 灵感点频率
        lingganMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; ${idx < lingganMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'} border-bottom: 2px solid #667eea;">`;
            html += `<span style="color: #666; font-size: 0.75rem; font-weight: 600;">${(info.frequency * 100).toFixed(0)}%</span>`;
            html += `</td>`;
        });

        // 目的点频率
        mudiMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; ${idx < mudiMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'} border-bottom: 2px solid #667eea;">`;
            html += `<span style="color: #666; font-size: 0.75rem; font-weight: 600;">${(info.frequency * 100).toFixed(0)}%</span>`;
            html += `</td>`;
        });

        // 关键点频率
        guanjianMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; ${idx < guanjianMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''} border-bottom: 2px solid #667eea;">`;
            html += `<span style="color: #666; font-size: 0.75rem; font-weight: 600;">${(info.frequency * 100).toFixed(0)}%</span>`;
            html += `</td>`;
        });
        html += `</tr>`;

        // 箭头行
        html += `<tr style="background: white;">`;
        html += `<td style="padding: 8px; text-align: center; font-size: 1.2rem; color: #667eea; border-right: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0;">↓</td>`;

        // 灵感点箭头 - 每列一个箭头
        lingganMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; font-size: 1.2rem; color: #2196f3; ${idx < lingganMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'} border-bottom: 1px solid #e0e0e0;">↓</td>`;
        });

        // 目的点箭头 - 每列一个箭头
        mudiMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; font-size: 1.2rem; color: #ff9800; ${idx < mudiMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'} border-bottom: 1px solid #e0e0e0;">↓</td>`;
        });

        // 关键点箭头 - 每列一个箭头
        guanjianMergeInfo.forEach((info, idx) => {
            html += `<td style="padding: 8px; text-align: center; font-size: 1.2rem; color: #9c27b0; ${idx < guanjianMergeInfo.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''} border-bottom: 1px solid #e0e0e0;">↓</td>`;
        });
        html += `</tr>`;

        // 扩展标题行
        html += `<tr style="background: #4caf50; color: white;">`;
        html += `<th colspan="${totalColumns}" style="padding: 10px; text-align: left; font-size: 0.85rem;">🌟 点模式映射（仅显示扩展的叶子分类）</th>`;
        html += `</tr>`;
        html += `</thead>`;

        html += `<tbody>`;

        // 数据行 - 并列显示各维度的扩展组合（只显示扩展的叶子分类）
        for (let rowIdx = 0; rowIdx < maxRows; rowIdx++) {
            const rowBg = rowIdx % 2 === 0 ? '#fafafa' : 'white';
            html += `<tr style="background: ${rowBg}; border-bottom: 1px solid #e0e0e0;">`;
            html += `<td style="padding: 8px; text-align: center; font-size: 0.75rem; color: #999; font-weight: 600; border-right: 1px solid #e0e0e0;">#${rowIdx + 1}</td>`;

            // 灵感点列 - 每个叶子分类占一列,只显示新增的扩展分类
            lingganCombosList.forEach((lingganInfo, lingganIdx) => {
                html += `<td style="padding: 8px; text-align: center; ${lingganIdx < lingganCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
                if (rowIdx < lingganInfo.combos.length) {
                    const combo = lingganInfo.combos[rowIdx];
                    const leafCombo = combo['叶子分类组合'] || [];
                    // 过滤掉原始叶子分类,只显示新增的扩展分类
                    const newLeafs = leafCombo.filter(leaf => leaf !== lingganInfo.leafClass);
                    if (newLeafs.length > 0) {
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;">`;
                        newLeafs.forEach(leaf => {
                            html += `<span style="background: #fff3e0; color: #f57c00; padding: 3px 8px; border-radius: 3px; font-size: 0.75rem; font-weight: 500;">${escapeHtml(leaf)} ✨</span>`;
                        });
                        html += `</div>`;
                    } else {
                        html += `<span style="color: #ccc;">-</span>`;
                    }
                } else {
                    html += `<span style="color: #ccc;">-</span>`;
                }
                html += `</td>`;
            });

            // 目的点列 - 每个叶子分类占一列,只显示新增的扩展分类
            mudiCombosList.forEach((mudiInfo, mudiIdx) => {
                html += `<td style="padding: 8px; text-align: center; ${mudiIdx < mudiCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
                if (rowIdx < mudiInfo.combos.length) {
                    const combo = mudiInfo.combos[rowIdx];
                    const leafCombo = combo['叶子分类组合'] || [];
                    // 过滤掉原始叶子分类,只显示新增的扩展分类
                    const newLeafs = leafCombo.filter(leaf => leaf !== mudiInfo.leafClass);
                    if (newLeafs.length > 0) {
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;">`;
                        newLeafs.forEach(leaf => {
                            html += `<span style="background: #fff3e0; color: #f57c00; padding: 3px 8px; border-radius: 3px; font-size: 0.75rem; font-weight: 500;">${escapeHtml(leaf)} ✨</span>`;
                        });
                        html += `</div>`;
                    } else {
                        html += `<span style="color: #ccc;">-</span>`;
                    }
                } else {
                    html += `<span style="color: #ccc;">-</span>`;
                }
                html += `</td>`;
            });

            // 关键点列 - 每个叶子分类占一列,只显示新增的扩展分类
            guanjianCombosList.forEach((keyInfo, keyIdx) => {
                html += `<td style="padding: 8px; text-align: center; ${keyIdx < guanjianCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''}">`;
                if (rowIdx < keyInfo.combos.length) {
                    const guanjianCombo = keyInfo.combos[rowIdx];
                    const leafCombo = guanjianCombo['叶子分类组合'] || [];
                    // 过滤掉原始叶子分类,只显示新增的扩展分类
                    const newLeafs = leafCombo.filter(leaf => leaf !== keyInfo.keyLeafClass);
                    if (newLeafs.length > 0) {
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; justify-content: center;">`;
                        newLeafs.forEach(leaf => {
                            html += `<span style="background: #fff3e0; color: #f57c00; padding: 3px 8px; border-radius: 3px; font-size: 0.75rem; font-weight: 500;">${escapeHtml(leaf)} ✨</span>`;
                        });
                        html += `</div>`;
                    } else {
                        html += `<span style="color: #ccc;">-</span>`;
                    }
                } else {
                    html += `<span style="color: #ccc;">-</span>`;
                }
                html += `</td>`;
            });

            html += `</tr>`;
        }

        // 组合分类合并行
        html += `<tr style="background: #e8f5e9; border-top: 2px solid #4caf50;">`;
        html += `<td style="padding: 10px; text-align: center; font-size: 0.8rem; font-weight: 600; color: #2e7d32; border-right: 1px solid #e0e0e0;">组合分类合并</td>`;

        // 灵感点扩展分类合并 - 每个叶子分类占一列
        lingganCombosList.forEach((lingganInfo, lingganIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${lingganIdx < lingganCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            const item = lingganExpandList.find(x => x['叶子分类'] === lingganInfo.leafClass);
            const merged = item ? item['扩展分类合并'] : '';
            if (merged) {
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 5px 12px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; display: inline-block;">${escapeHtml(merged)}</span>`;
            } else {
                html += `<span style="color: #ccc;">-</span>`;
            }
            html += `</td>`;
        });

        // 目的点扩展分类合并 - 每个叶子分类占一列
        mudiCombosList.forEach((mudiInfo, mudiIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${mudiIdx < mudiCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            const item = mudiExpandList.find(x => x['叶子分类'] === mudiInfo.leafClass);
            const merged = item ? item['扩展分类合并'] : '';
            if (merged) {
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 5px 12px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; display: inline-block;">${escapeHtml(merged)}</span>`;
            } else {
                html += `<span style="color: #ccc;">-</span>`;
            }
            html += `</td>`;
        });

        // 关键点扩展分类合并 - 每个叶子分类占一列
        guanjianCombosList.forEach((keyInfo, keyIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${keyIdx < guanjianCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''}">`;
            const item = guanjianExpandList.find(x => x['叶子分类'] === keyInfo.keyLeafClass);
            const merged = item ? item['扩展分类合并'] : '';
            if (merged) {
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 5px 12px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; display: inline-block;">${escapeHtml(merged)}</span>`;
            } else {
                html += `<span style="color: #ccc;">-</span>`;
            }
            html += `</td>`;
        });
        html += `</tr>`;

        // 完整选题结果行
        html += `<tr style="background: #f3e5f5; border-top: 2px solid #9c27b0;">`;
        html += `<td style="padding: 10px; text-align: center; font-size: 0.8rem; font-weight: 600; color: #7b1fa2; border-right: 1px solid #e0e0e0;">🎉 完整选题结果</td>`;

        // 灵感点完整选题 - 每个叶子分类占一列
        lingganCombosList.forEach((lingganInfo, lingganIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${lingganIdx < lingganCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            const item = lingganExpandList.find(x => x['叶子分类'] === lingganInfo.leafClass);
            const merged = item ? item['扩展分类合并'] : '';

            // 使用flex布局让内容更紧凑
            html += `<div style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px;">`;

            // 显示原始叶子分类 - 使用蓝色（与半选题对应）
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(lingganInfo.leafClass)}</span>`;

            // 如果有扩展分类合并,添加加号和扩展分类 - 使用绿色（与组合分类合并对应）
            if (merged) {
                html += `<span style="color: #666; font-weight: bold; font-size: 0.85rem;">+</span>`;
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(merged)}</span>`;
            }

            html += `</div>`;
            html += `</td>`;
        });

        // 目的点完整选题 - 每个叶子分类占一列
        mudiCombosList.forEach((mudiInfo, mudiIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${mudiIdx < mudiCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : 'border-right: 1px solid #e0e0e0;'}">`;
            const item = mudiExpandList.find(x => x['叶子分类'] === mudiInfo.leafClass);
            const merged = item ? item['扩展分类合并'] : '';

            // 使用flex布局让内容更紧凑
            html += `<div style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px;">`;

            // 显示原始叶子分类 - 使用蓝色（与半选题对应）
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(mudiInfo.leafClass)}</span>`;

            // 如果有扩展分类合并,添加加号和扩展分类 - 使用绿色（与组合分类合并对应）
            if (merged) {
                html += `<span style="color: #666; font-weight: bold; font-size: 0.85rem;">+</span>`;
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(merged)}</span>`;
            }

            html += `</div>`;
            html += `</td>`;
        });

        // 关键点完整选题 - 每个叶子分类占一列
        guanjianCombosList.forEach((keyInfo, keyIdx) => {
            html += `<td style="padding: 10px; text-align: center; ${keyIdx < guanjianCombosList.length - 1 ? 'border-right: 1px solid #e0e0e0;' : ''}">`;
            const item = guanjianExpandList.find(x => x['叶子分类'] === keyInfo.keyLeafClass);
            const merged = item ? item['扩展分类合并'] : '';

            // 使用flex布局让内容更紧凑
            html += `<div style="display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 6px;">`;

            // 显示原始叶子分类 - 使用蓝色（与半选题对应）
            html += `<span style="background: #e3f2fd; color: #1976d2; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(keyInfo.keyLeafClass)}</span>`;

            // 如果有扩展分类合并,添加加号和扩展分类 - 使用绿色（与组合分类合并对应）
            if (merged) {
                html += `<span style="color: #666; font-weight: bold; font-size: 0.85rem;">+</span>`;
                html += `<span style="background: #c8e6c9; color: #2e7d32; padding: 4px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; white-space: nowrap;">${escapeHtml(merged)}</span>`;
            }

            html += `</div>`;
            html += `</td>`;
        });
        html += `</tr>`;

        html += `</tbody>`;
        html += `</table>`;
        html += `</div>`;

        html += `</div>`;
        html += `</div>`;

        // 共同帖子详情 - 从聚类结果中获取
        const commonPostIds = result['聚类覆盖帖子ID'] || [];
        if (commonPostIds.length > 0) {
            html += `<div style="background: white; padding: 16px; border-radius: 8px; border-left: 4px solid #ff9800;">`;
            html += `<h4 style="color: #ff9800; margin: 0 0 12px 0; font-size: 0.95rem;">📝 共同帖子详情 (${commonPostIds.length}个)</h4>`;

            commonPostIds.forEach(postId => {
                const postData = enrichedXuantiPointMap ? enrichedXuantiPointMap[postId] : null;
                const cachedPostData = postCache[postId];

                if (postData) {
                    html += `<div style="margin-bottom: 12px; padding: 12px; background: #f5f5f5; border-radius: 6px; border-left: 3px solid #ff9800; display: flex; gap: 12px;">`;

                    // 显示第一张图片（如果有）
                    if (cachedPostData && cachedPostData.images && cachedPostData.images.length > 0) {
                        html += `<img src="${escapeHtml(cachedPostData.images[0])}"
                            alt="${escapeHtml(postData['选题'] || '帖子')}"
                            title="点击查看详情"
                            onclick="window.showPostDetail('${postId}');"
                            style="width: 100px; height: 100px; object-fit: cover; border-radius: 6px; cursor: pointer; flex-shrink: 0;
                            transition: all 0.2s; border: 2px solid #ff9800;"
                            onmouseenter="this.style.transform='scale(1.05)'; this.style.boxShadow='0 4px 12px rgba(0,0,0,0.2)';"
                            onmouseleave="this.style.transform='scale(1)'; this.style.boxShadow='none';">`;
                    }

                    // 右侧信息区域
                    html += `<div style="flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px;">`;

                    // 标题、选题描述和ID
                    html += `<div>`;
                    html += `<div style="font-weight: 600; color: #333; font-size: 0.9rem; cursor: pointer; margin-bottom: 5px;" onclick="window.showPostDetail('${postId}');" title="点击查看详情">${escapeHtml(postData['选题'] || '未命名选题')}</div>`;
                    if (postData['选题描述']) {
                        html += `<div style="color: #666; font-size: 0.75rem; line-height: 1.4; margin-bottom: 4px;">${escapeHtml(postData['选题描述'])}</div>`;
                    }
                    html += `<div style="color: #999; font-size: 0.7rem;">ID: ${escapeHtml(postId)}</div>`;
                    html += `</div>`;

                    // 显示灵感点、目的点、关键点详情（优化布局）
                    // 灵感点
                    const lingganList = postData['灵感点列表'] || [];
                    if (lingganList.length > 0) {
                        html += `<div style="padding: 8px; background: #e8f5e9; border-radius: 4px; border-left: 3px solid #2196f3;">`;
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; align-items: center;">`;
                        html += `<span style="font-weight: 700; color: #1565c0; font-size: 0.75rem; margin-right: 4px;">💡 灵感点</span>`;
                        lingganList.forEach(point => {
                            html += `<span style="background: #1565c0; color: white; padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 700; text-decoration: underline;">${escapeHtml(point['灵感点'] || '')}</span>`;
                            // 显示该灵感点的特征和叶子分类
                            const features = point['提取的特征'] || [];
                            features.forEach(f => {
                                html += `<span style="background: white; color: #1976d2; padding: 2px 6px; border-radius: 2px; font-size: 0.65rem; border: 1px solid #bbdefb;">`;
                                html += `${escapeHtml(f['特征名称'])}→${escapeHtml(f['叶子分类'])}`;
                                html += `</span>`;
                            });
                        });
                        html += `</div>`;
                        html += `</div>`;
                    }

                    // 目的点
                    const mudiList = postData['目的点'] || [];
                    if (mudiList.length > 0) {
                        html += `<div style="padding: 8px; background: #fff8e1; border-radius: 4px; border-left: 3px solid #ff9800;">`;
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; align-items: center;">`;
                        html += `<span style="font-weight: 700; color: #ef6c00; font-size: 0.75rem; margin-right: 4px;">🎯 目的点</span>`;
                        mudiList.forEach(point => {
                            html += `<span style="background: #ef6c00; color: white; padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 700; text-decoration: underline;">${escapeHtml(point['目的点'] || '')}</span>`;
                            // 显示该目的点的特征和叶子分类
                            const features = point['提取的特征'] || [];
                            features.forEach(f => {
                                html += `<span style="background: white; color: #f57c00; padding: 2px 6px; border-radius: 2px; font-size: 0.65rem; border: 1px solid #ffe0b2;">`;
                                html += `${escapeHtml(f['特征名称'])}→${escapeHtml(f['叶子分类'])}`;
                                html += `</span>`;
                            });
                        });
                        html += `</div>`;
                        html += `</div>`;
                    }

                    // 关键点
                    const guanjianList = postData['关键点列表'] || [];
                    if (guanjianList.length > 0) {
                        html += `<div style="padding: 8px; background: #f3e5f5; border-radius: 4px; border-left: 3px solid #9c27b0;">`;
                        html += `<div style="display: flex; flex-wrap: wrap; gap: 4px; align-items: center;">`;
                        html += `<span style="font-weight: 700; color: #6a1b9a; font-size: 0.75rem; margin-right: 4px;">🔑 关键点</span>`;
                        guanjianList.forEach(point => {
                            html += `<span style="background: #6a1b9a; color: white; padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; font-weight: 700; text-decoration: underline;">${escapeHtml(point['关键点'] || '')}</span>`;
                            // 显示该关键点的特征和叶子分类
                            const features = point['提取的特征'] || [];
                            features.forEach(f => {
                                html += `<span style="background: white; color: #7b1fa2; padding: 2px 6px; border-radius: 2px; font-size: 0.65rem; border: 1px solid #e1bee7;">`;
                                html += `${escapeHtml(f['特征名称'])}→${escapeHtml(f['叶子分类'])}`;
                                html += `</span>`;
                            });
                        });
                        html += `</div>`;
                        html += `</div>`;
                    }

                    html += `</div>`; // 结束右侧信息区域
                    html += `</div>`;
                } else if (cachedPostData && cachedPostData.images && cachedPostData.images.length > 0) {
                    // 如果没有enrichedXuantiPointMap数据，仅显示图片
                    html += `<img src="${escapeHtml(cachedPostData.images[0])}"
                        alt="${escapeHtml(cachedPostData.title || '帖子')}"
                        title="点击查看详情"
                        onclick="window.showPostDetail('${postId}');"
                        style="width: 80px; height: 80px; object-fit: cover; border-radius: 6px; cursor: pointer; margin-right: 8px; margin-bottom: 8px;
                        transition: all 0.2s; border: 2px solid #ff9800; display: inline-block;"
                        onmouseenter="this.style.transform='scale(1.1)'; this.style.boxShadow='0 4px 12px rgba(0,0,0,0.2)';"
                        onmouseleave="this.style.transform='scale(1)'; this.style.boxShadow='none';">`;
                }
            });

            html += `</div>`;
        }

        return html;
    }

    function showOrthogonalResults(lingganPath, matchedResults) {
        const resultsContainer = document.getElementById('orthogonal-results');

        // 设置标题
        const lingganLeafClass = lingganPath.split('/').pop();

        let html = '';

        // 灵感点信息区域 - 滑动置顶
        html += `<div style="position: sticky; top: -20px; background: white; z-index: 10; padding-bottom: 16px; margin: -20px -20px 16px -20px; padding: 20px 20px 16px 20px; border-bottom: 2px solid #e0e0e0;">`;
        html += `<h3 style="color: #667eea; margin-bottom: 12px;">
            灵感点「${escapeHtml(lingganLeafClass)}」
            <span style="color: #999; font-size: 0.9rem; font-weight: normal; margin-left: 10px;">(${matchedResults.length}组三维正交组合)</span>
        </h3>`;
        html += `</div>`;

        // 遍历每个匹配结果
        matchedResults.forEach((result, index) => {
            html += `<div style="margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px;">`;

            // 组号标题（显示聚类信息）
            html += `<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 16px;">`;
            html += `<span style="background: #667eea; color: white; padding: 6px 14px; border-radius: 16px; font-weight: 700; font-size: 0.95rem;">聚类 #${result['聚类ID']}</span>`;
            html += `<span style="color: #666; font-size: 0.9rem;">组合数: ${result['聚类大小']}</span>`;
            html += `<span style="color: #666; font-size: 0.9rem;">覆盖帖子: ${result['聚类覆盖帖子数']}</span>`;
            html += `</div>`;

            // 直接调用generateGroupContent生成表格内容
            html += generateGroupContent(result, index);
            html += `</div>`;
        });

        resultsContainer.innerHTML = html;
    }

    // 获取灵感点的所有帖子ID
    function getLingganPostIds(lingganPath) {
        const pathParts = lingganPath.split('/');
        let categoryData = clusteredData['灵感点列表'];

        // 逐级查找到目标分类
        for (const part of pathParts) {
            if (categoryData && categoryData[part]) {
                categoryData = categoryData[part];
            } else {
                return [];
            }
        }

        return categoryData['帖子列表'] || [];
    }

    function highlightThreeDimensionAssociations(lingganPath) {
        if (!expandedOrthogonalCombinations || !expandedOrthogonalCombinations['聚类结果'] || !expandedOrthogonalCombinations['聚类结果']['聚类详情']) {
            console.log('聚类结果数据不存在');
            document.getElementById('orthogonal-results').innerHTML = `
                <div style="text-align: center; padding: 60px 20px; color: #999;">
                    <p style="font-size: 1.1rem; margin-bottom: 10px;">未加载扩展正交组合数据</p>
                </div>
            `;
            return;
        }

        const expandedResults = expandedOrthogonalCombinations['聚类结果']['聚类详情'];

        // 从路径中提取叶子分类
        const lingganLeafClass = lingganPath.split('/').pop();
        console.log('查找灵感点叶子分类:', lingganLeafClass);

        // 查找匹配的聚类结果（灵感点维度合并中包含该叶子分类）
        const matchedResults = expandedResults.filter(result => {
            const dimensionMerge = result['维度合并'];
            if (!dimensionMerge || !dimensionMerge['灵感点']) return false;
            return dimensionMerge['灵感点'].some(item => item['叶子分类'] === lingganLeafClass);
        });

        console.log('找到匹配的扩展结果数量:', matchedResults.length);

        if (matchedResults.length === 0) {
            document.getElementById('orthogonal-results').innerHTML = `
                <div style="text-align: center; padding: 60px 20px; color: #999;">
                    <p style="font-size: 1.1rem; margin-bottom: 10px;">该灵感点没有三维正交关联</p>
                    <p style="font-size: 0.9rem;">（叶子分类: ${escapeHtml(lingganLeafClass)}）</p>
                </div>
            `;
            return;
        }

        // 显示正交关系卡片
        showOrthogonalResults(lingganPath, matchedResults);

        console.log('共显示', matchedResults.length, '组三维正交关联');
    }

    function highlightOrthogonalNodeMultiGroup(dimension, path, groups) {
        const headers = document.querySelectorAll('.tab3-node-header');
        headers.forEach(header => {
            if (header.getAttribute('data-dimension') === dimension &&
                header.getAttribute('data-path') === path) {

                // 如果参与多个组，使用渐变边框
                let borderStyle, bgColor, boxShadow;

                if (groups.length === 1) {
                    // 只参与一个组，使用单一颜色
                    borderStyle = `3px solid ${groups[0].color.border}`;
                    bgColor = groups[0].color.bg;
                    boxShadow = `0 0 16px ${groups[0].color.border}99`;
                } else {
                    // 参与多个组，使用渐变边框
                    const colors = groups.map(g => g.color.border).join(', ');
                    borderStyle = `3px solid transparent`;
                    header.style.backgroundImage = `linear-gradient(white, white), linear-gradient(90deg, ${colors})`;
                    header.style.backgroundOrigin = 'padding-box, border-box';
                    header.style.backgroundClip = 'padding-box, border-box';

                    // 背景色使用第一个组的颜色
                    bgColor = groups[0].color.bg;
                    // 组合所有组的阴影效果
                    boxShadow = groups.map((g, i) =>
                        `${i * 2}px ${i * 2}px ${12 + i * 2}px ${g.color.border}66`
                    ).join(', ');
                }

                header.style.border = borderStyle;
                header.style.backgroundColor = bgColor;
                header.style.boxShadow = boxShadow;
                header.style.fontWeight = '600';

                // 删除旧的组号标签
                const existingBadges = header.querySelectorAll('.group-badge');
                existingBadges.forEach(b => b.remove());

                // 添加所有组号标签
                const badgeContainer = document.createElement('span');
                badgeContainer.style.cssText = 'display: inline-flex; gap: 4px; margin-right: 6px; flex-wrap: wrap;';

                groups.forEach(group => {
                    const badge = document.createElement('span');
                    badge.className = 'group-badge';
                    badge.textContent = `#${group.groupNumber}`;
                    badge.style.cssText = `
                        display: inline-block;
                        background: ${group.color.border};
                        color: white;
                        padding: 2px 6px;
                        border-radius: 10px;
                        font-size: 0.75rem;
                        font-weight: 700;
                    `;
                    badgeContainer.appendChild(badge);
                });

                header.insertBefore(badgeContainer, header.firstChild);

                // 收集所有组的共同帖子（取并集）
                const allPostIds = new Set();
                groups.forEach(group => {
                    group.commonPostIds.forEach(id => allPostIds.add(id));
                });

                // 显示所有相关的帖子封面图
                const nodeId = header.getAttribute('data-node-id');
                if (nodeId && allPostIds.size > 0) {
                    showCategoryThumbnails(dimension, path, nodeId, Array.from(allPostIds));
                }
            }
        });
    }

    function highlightOrthogonalNode(dimension, path, commonPostIds, borderColor, bgColor, groupNumber) {
        const headers = document.querySelectorAll('.tab3-node-header');
        headers.forEach(header => {
            if (header.getAttribute('data-dimension') === dimension &&
                header.getAttribute('data-path') === path) {
                // 高亮节点
                header.style.borderColor = borderColor;
                header.style.borderWidth = '3px';
                header.style.boxShadow = `0 0 16px ${borderColor}99`;
                header.style.backgroundColor = bgColor;
                header.style.fontWeight = '600';

                // 在节点前添加组号标签
                const existingBadge = header.querySelector('.group-badge');
                if (existingBadge) {
                    existingBadge.remove();
                }
                const badge = document.createElement('span');
                badge.className = 'group-badge';
                badge.textContent = `#${groupNumber}`;
                badge.style.cssText = `
                    display: inline-block;
                    background: ${borderColor};
                    color: white;
                    padding: 2px 6px;
                    border-radius: 10px;
                    font-size: 0.75rem;
                    font-weight: 700;
                    margin-right: 6px;
                `;
                header.insertBefore(badge, header.firstChild);

                // 显示三维共同帖子的封面图
                const nodeId = header.getAttribute('data-node-id');
                if (nodeId && commonPostIds && commonPostIds.length > 0) {
                    showCategoryThumbnails(dimension, path, nodeId, commonPostIds);
                }
            }
        });
    }

    function showCategoryThumbnails(dimension, path, nodeId, filterPostIds) {
        // 在clusteredData中查找该分类的数据
        const pathParts = path.split('/');
        let categoryData = clusteredData[dimension];

        // 逐级查找到目标分类
        for (const part of pathParts) {
            if (categoryData && categoryData[part]) {
                categoryData = categoryData[part];
            } else {
                console.log('未找到分类数据:', dimension, path);
                return;
            }
        }

        // 获取帖子列表
        let postIds = categoryData['帖子列表'] || [];

        // 如果提供了过滤列表，只显示过滤列表中的帖子（用于关联分类显示共同帖子）
        if (filterPostIds && filterPostIds.length > 0) {
            postIds = postIds.filter(id => filterPostIds.includes(id));
        }

        console.log('该分类的帖子数量:', postIds.length);

        if (postIds.length === 0) {
            return;
        }

        // 获取封面图容器
        const thumbnailsContainer = document.getElementById(nodeId + '-thumbnails');
        if (!thumbnailsContainer) {
            return;
        }

        // 清空容器
        thumbnailsContainer.innerHTML = '';

        // 为每个帖子添加封面图
        postIds.forEach(postId => {
            const postData = postCache[postId];
            if (postData && postData.images && postData.images.length > 0) {
                const img = document.createElement('img');
                img.src = postData.images[0];
                img.alt = postData.title || '帖子封面';
                img.style.cssText = 'width: 80px; height: 80px; object-fit: cover; border-radius: 6px; cursor: pointer; transition: all 0.2s; border: 2px solid #e0e0e0;';

                // 鼠标悬停效果
                img.addEventListener('mouseenter', function() {
                    this.style.transform = 'scale(1.1)';
                    this.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
                    this.style.borderColor = '#2196F3';
                });

                img.addEventListener('mouseleave', function() {
                    this.style.transform = 'scale(1)';
                    this.style.boxShadow = 'none';
                    this.style.borderColor = '#e0e0e0';
                });

                // 点击封面图显示帖子详情
                img.addEventListener('click', function(e) {
                    e.stopPropagation();
                    showPostDetail(postId);
                });

                thumbnailsContainer.appendChild(img);
            }
        });

        // 显示容器
        if (thumbnailsContainer.children.length > 0) {
            thumbnailsContainer.style.display = 'flex';
        }
    }

    document.querySelector('.modal-overlay')?.addEventListener('click', closeModal);
});
</script>
'''
        return js_code


def visualize_classification_tree(
    optimized_data_path: str,
    posts_dir: str,
    xuanti_point_map: Dict[str, Dict[str, Any]],
    output_path: str = None,
    dimension_associations_path: str = None,
    intra_dimension_associations_path: str = None,
    expanded_orthogonal_combinations_path: str = None,
    enriched_xuanti_point_map_path: str = None
) -> str:
    """可视化分类树"""
    with open(optimized_data_path, 'r', encoding='utf-8') as f:
        clustered_data = json.load(f)

    # 加载跨维度关联分析数据（如果提供了路径）
    dimension_associations = None
    if dimension_associations_path and os.path.exists(dimension_associations_path):
        try:
            with open(dimension_associations_path, 'r', encoding='utf-8') as f:
                dimension_associations = json.load(f)
            print(f"✅ 已加载跨维度关联分析数据: {dimension_associations_path}")
        except Exception as e:
            print(f"⚠️ 加载跨维度关联分析数据失败: {e}")

    # 加载维度内部关联分析数据（如果提供了路径）
    intra_dimension_associations = None
    if intra_dimension_associations_path and os.path.exists(intra_dimension_associations_path):
        try:
            with open(intra_dimension_associations_path, 'r', encoding='utf-8') as f:
                intra_dimension_associations = json.load(f)
            print(f"✅ 已加载维度内部关联分析数据: {intra_dimension_associations_path}")
        except Exception as e:
            print(f"⚠️ 加载维度内部关联分析数据失败: {e}")

    # 加载扩展正交组合数据（新增）
    expanded_orthogonal_combinations = None
    if expanded_orthogonal_combinations_path and os.path.exists(expanded_orthogonal_combinations_path):
        try:
            with open(expanded_orthogonal_combinations_path, 'r', encoding='utf-8') as f:
                expanded_orthogonal_combinations = json.load(f)
            print(f"✅ 已加载扩展正交组合数据: {expanded_orthogonal_combinations_path}")
        except Exception as e:
            print(f"⚠️ 加载扩展正交组合数据失败: {e}")

    # 加载丰富选题点映射数据（新增）
    enriched_xuanti_point_map = None
    if enriched_xuanti_point_map_path and os.path.exists(enriched_xuanti_point_map_path):
        try:
            with open(enriched_xuanti_point_map_path, 'r', encoding='utf-8') as f:
                enriched_xuanti_point_map = json.load(f)
            print(f"✅ 已加载丰富选题点映射数据: {enriched_xuanti_point_map_path}")
        except Exception as e:
            print(f"⚠️ 加载丰富选题点映射数据失败: {e}")

    visualizer = ClassificationTreeVisualizer()

    html_content = visualizer.generate_html(
        clustered_data,
        Path(posts_dir),
        xuanti_point_map,
        dimension_associations,
        intra_dimension_associations,
        expanded_orthogonal_combinations,
        enriched_xuanti_point_map
    )

    if output_path is None:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        base_dir = os.path.dirname(optimized_data_path)
        output_path = os.path.join(base_dir, f"visualization/classification_tree_visualization_{timestamp}.html")

    os.makedirs(os.path.dirname(output_path), exist_ok=True)

    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(html_content)

    print(f"✅ 可视化文件已生成: {output_path}")
    return output_path


if __name__ == "__main__":
    account_name = "阿里多多酱"
    base_dir = "/Users/nieqi/Documents/workspace/python/image_article_comprehension/aiddit/pattern/pattern_from_xuanti_point_label"
    mode = "detail"

    optimized_data_path = f"{base_dir}/result/{account_name}/optimization/{mode}/optimized_clustered_data_gemini-3-pro-preview.json"
    posts_dir = f"/Users/nieqi/Documents/workspace/python/image_article_comprehension/aigc_data/{account_name}"
    dimension_associations_path = f"{base_dir}/result/{account_name}/optimization/{mode}/dimension_associations_analysis.json"
    intra_dimension_associations_path = f"{base_dir}/result/{account_name}/optimization/{mode}/intra_dimension_associations_analysis.json"
    expanded_orthogonal_combinations_path = f"{base_dir}/result/{account_name}/optimization/detail/orthogonal_combinations_clustering.json"
    enriched_xuanti_point_map_path = f"{base_dir}/result/{account_name}/optimization/{mode}/enriched_xuanti_point_map.json"

    from aiddit.pattern.pattern_from_xuanti_point_label import pattern_utils
    xuanti_point_map = pattern_utils.get_xuanti_point__map(account_name)

    output_path = visualize_classification_tree(
        optimized_data_path=optimized_data_path,
        posts_dir=posts_dir,
        xuanti_point_map=xuanti_point_map,
        dimension_associations_path=dimension_associations_path,
        intra_dimension_associations_path=intra_dimension_associations_path,
        expanded_orthogonal_combinations_path=expanded_orthogonal_combinations_path,
        enriched_xuanti_point_map_path=enriched_xuanti_point_map_path
    )

    print(f"🎉 可视化完成！请在浏览器中打开: {output_path}")
