#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
How解构结果可视化脚本 V3

改进版：
- 使用标签页展示多个帖子
- 参考 visualize_inspiration_points.py 的帖子详情展示
- 分层可折叠的匹配结果
- V3: 使用新的输入目录 当前帖子_how解构结果_v2
"""

import json
from pathlib import Path
from typing import Dict, List
import sys
import html as html_module

# 添加项目根目录到路径
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))


def get_relation_color(relation: str) -> str:
    """根据关系类型返回对应的颜色"""
    color_map = {
        "same": "#10b981",           # 绿色 - 同义
        "contains": "#3b82f6",       # 蓝色 - 包含
        "contained_by": "#8b5cf6",   # 紫色 - 被包含
        "coordinate": "#f59e0b",     # 橙色 - 同级
        "overlap": "#ec4899",        # 粉色 - 部分重叠
        "related": "#6366f1",        # 靛蓝 - 相关
        "unrelated": "#9ca3af"       # 灰色 - 无关
    }
    return color_map.get(relation, "#9ca3af")


def get_relation_label(relation: str) -> str:
    """返回关系类型的中文标签"""
    label_map = {
        "same": "同义",
        "contains": "包含",
        "contained_by": "被包含",
        "coordinate": "同级",
        "overlap": "部分重叠",
        "related": "相关",
        "unrelated": "无关"
    }
    return label_map.get(relation, relation)


def generate_historical_post_card_html(post_detail: Dict, inspiration_point: Dict) -> str:
    """生成历史帖子的紧凑卡片HTML"""
    title = post_detail.get("title", "无标题")
    body_text = post_detail.get("body_text", "")
    images = post_detail.get("images", [])
    like_count = post_detail.get("like_count", 0)
    collect_count = post_detail.get("collect_count", 0)
    comment_count = post_detail.get("comment_count", 0)
    author = post_detail.get("channel_account_name", "")
    link = post_detail.get("link", "#")
    publish_time = post_detail.get("publish_time", "")

    # 获取灵感点信息
    point_name = inspiration_point.get("点的名称", "")
    point_desc = inspiration_point.get("点的描述", "")

    # 准备详情数据（用于模态框）
    import json
    post_detail_data = {
        "title": title,
        "body_text": body_text,
        "images": images,
        "like_count": like_count,
        "comment_count": comment_count,
        "collect_count": collect_count,
        "author": author,
        "publish_time": publish_time,
        "link": link
    }
    post_data_json = json.dumps(post_detail_data, ensure_ascii=False)
    post_data_json_escaped = html_module.escape(post_data_json)

    # 截取正文预览（前80个字符）
    body_preview = body_text[:80] + "..." if len(body_text) > 80 else body_text

    # 生成缩略图
    thumbnail_html = ""
    if images:
        thumbnail_html = f'<img src="{images[0]}" alt="Post thumbnail" class="historical-post-thumbnail" loading="lazy">'

    html = f'''
    <div class="historical-post-card" data-post-data='{post_data_json_escaped}' onclick="showPostDetail(this)">
        <div class="historical-post-image">
            {thumbnail_html}
            {f'<div class="post-card-image-count">{len(images)}</div>' if len(images) > 1 else ''}
        </div>
        <div class="historical-post-content">
            <div class="historical-post-title">{html_module.escape(title)}</div>
            <div class="historical-inspiration-info">
                <span class="inspiration-type-badge-small">灵感点</span>
                <span class="inspiration-name-small">{html_module.escape(point_name)}</span>
            </div>
            <div class="historical-inspiration-desc">{html_module.escape(point_desc[:100])}{"..." if len(point_desc) > 100 else ""}</div>
            <div class="historical-post-meta">
                <span class="historical-post-time">📅 {publish_time}</span>
            </div>
            <div class="historical-post-stats">
                <span>❤ {like_count}</span>
                <span>⭐ {collect_count}</span>
                <a href="{link}" target="_blank" class="view-link" onclick="event.stopPropagation()">查看原帖 →</a>
            </div>
        </div>
    </div>
    '''
    return html


def generate_post_detail_html(post_data: Dict, post_idx: int) -> str:
    """生成帖子详情HTML（紧凑的卡片样式，点击可展开）"""
    post_detail = post_data.get("帖子详情", {})

    title = post_detail.get("title", "无标题")
    body_text = post_detail.get("body_text", "")
    images = post_detail.get("images", [])
    like_count = post_detail.get("like_count", 0)
    comment_count = post_detail.get("comment_count", 0)
    collect_count = post_detail.get("collect_count", 0)
    author = post_detail.get("channel_account_name", "")
    publish_time = post_detail.get("publish_time", "")
    link = post_detail.get("link", "")
    post_id = post_data.get("帖子id", f"post-{post_idx}")

    # 准备详情数据（用于模态框）
    import json
    post_detail_data = {
        "title": title,
        "body_text": body_text,
        "images": images,
        "like_count": like_count,
        "comment_count": comment_count,
        "collect_count": collect_count,
        "author": author,
        "publish_time": publish_time,
        "link": link
    }
    post_data_json = json.dumps(post_detail_data, ensure_ascii=False)
    post_data_json_escaped = html_module.escape(post_data_json)

    # 生成缩略图HTML
    thumbnail_html = ""
    if images and len(images) > 0:
        # 使用第一张图片作为缩略图
        thumbnail_html = f'<img src="{images[0]}" class="post-card-thumbnail" alt="缩略图">'
    else:
        thumbnail_html = '<div class="post-card-thumbnail-placeholder">📄</div>'

    # 截断正文用于预览
    body_preview = body_text[:80] + "..." if len(body_text) > 80 else body_text

    html = f'''
    <div class="post-card-compact" data-post-data='{post_data_json_escaped}' onclick="showPostDetail(this)">
        <div class="post-card-image">
            {thumbnail_html}
            {f'<div class="post-card-image-count">📷 {len(images)}</div>' if len(images) > 1 else ''}
        </div>
        <div class="post-card-content">
            <div class="post-card-title">{html_module.escape(title)}</div>
            <div class="post-card-preview">{html_module.escape(body_preview) if body_preview else "暂无正文"}</div>
            <div class="post-card-meta">
                <span class="post-card-author">👤 {html_module.escape(author)}</span>
                <span class="post-card-time">📅 {publish_time}</span>
            </div>
            <div class="post-card-stats">
                <span>👍 {like_count}</span>
                <span>💬 {comment_count if comment_count else 0}</span>
                <span>⭐ {collect_count if collect_count else 0}</span>
            </div>
        </div>
    </div>
    '''
    return html


def generate_inspiration_detail_html(inspiration_point: Dict) -> str:
    """生成灵感点详情HTML"""
    name = inspiration_point.get("名称", "")
    desc = inspiration_point.get("描述", "")
    features = inspiration_point.get("特征列表", [])

    features_html = "".join([
        f'<span class="feature-tag">{html_module.escape(f)}</span>'
        for f in features
    ])

    html = f'''
    <div class="inspiration-detail-card">
        <div class="inspiration-header">
            <span class="inspiration-type-badge">灵感点</span>
            <h3 class="inspiration-name">{html_module.escape(name)}</h3>
        </div>
        <div class="inspiration-description">
            <div class="desc-label">描述:</div>
            <div class="desc-text">{html_module.escape(desc)}</div>
        </div>
        <div class="inspiration-features">
            <div class="features-label">特征列表:</div>
            <div class="features-tags">{features_html}</div>
        </div>
    </div>
    '''
    return html


def load_feature_category_mapping() -> Dict:
    """加载特征名称到分类的映射"""
    script_dir = Path(__file__).parent
    project_root = script_dir.parent.parent
    mapping_file = project_root / "data" / "data_1118" / "特征名称_分类映射.json"

    try:
        with open(mapping_file, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception as e:
        print(f"警告: 无法加载特征分类映射文件: {e}")
        return {}


def load_feature_source_mapping() -> Dict:
    """加载特征名称到帖子来源的映射"""
    script_dir = Path(__file__).parent
    project_root = script_dir.parent.parent
    mapping_file = project_root / "data" / "data_1118" / "特征名称_帖子来源.json"

    try:
        with open(mapping_file, "r", encoding="utf-8") as f:
            data = json.load(f)
            # 转换为便于查询的格式: {特征名称: [来源列表]}
            result = {}
            for feature_type in ["灵感点", "关键点", "目的点"]:
                if feature_type in data:
                    for item in data[feature_type]:
                        feature_name = item.get("特征名称")
                        if feature_name:
                            result[feature_name] = item.get("特征来源", [])
            return result
    except Exception as e:
        print(f"警告: 无法加载特征来源映射文件: {e}")
        return {}


def generate_match_results_html(how_steps: List[Dict], feature_idx: int, insp_idx: int, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None) -> str:
    """生成可折叠的匹配结果HTML（兼容新旧数据格式）"""
    if not how_steps or len(how_steps) == 0:
        return ""

    step = how_steps[0]
    features = step.get("特征列表", [])

    if feature_idx >= len(features):
        return ""

    feature_data = features[feature_idx]
    feature_name = feature_data.get("特征名称", "")
    match_results = feature_data.get("匹配结果", [])

    if category_mapping is None:
        category_mapping = {}

    # 检测数据格式（新格式使用中文字段"分数"，旧格式使用英文"score"）
    is_new_format = False
    if match_results and len(match_results) > 0:
        first_match = match_results[0].get("匹配结果", {})
        is_new_format = "分数" in first_match

    # 按分数排序（兼容新旧格式）
    if is_new_format:
        sorted_matches = sorted(match_results, key=lambda x: x.get("匹配结果", {}).get("分数", 0), reverse=True)
    else:
        sorted_matches = sorted(match_results, key=lambda x: x.get("匹配结果", {}).get("score", 0), reverse=True)

    # 统计信息（新格式不显示relation统计，只显示总数）
    if is_new_format:
        stats_html = f'<span class="stat-badge" style="background: #667eea;">总匹配数: {len(match_results)}</span>'
    else:
        # 旧格式：统计匹配类型
        relation_counts = {}
        for match in match_results:
            relation = match.get("匹配结果", {}).get("relation", "unrelated")
            relation_counts[relation] = relation_counts.get(relation, 0) + 1

        # 生成统计信息
        stats_items = []
        for relation, count in sorted(relation_counts.items(), key=lambda x: x[1], reverse=True):
            label = get_relation_label(relation)
            color = get_relation_color(relation)
            stats_items.append(f'<span class="stat-badge" style="background: {color};">{label}: {count}</span>')
        stats_html = "".join(stats_items)

    # 生成匹配项
    matches_html = ""
    for i, match in enumerate(sorted_matches):
        persona_name = match.get("人设特征名称", "")
        match_result = match.get("匹配结果", {})

        # 兼容新旧格式
        if is_new_format:
            score = match_result.get("分数", 0.0)
            explanation = match_result.get("说明", "")
            # 新格式根据分数设置颜色
            if score >= 0.7:
                color = "#10b981"  # 绿色 - 高相关
                label = "高相关"
            elif score >= 0.5:
                color = "#3b82f6"  # 蓝色 - 中相关
                label = "中相关"
            elif score >= 0.3:
                color = "#f59e0b"  # 橙色 - 低相关
                label = "低相关"
            else:
                color = "#9ca3af"  # 灰色 - 弱相关
                label = "弱相关"
        else:
            relation = match_result.get("relation", "unrelated")
            score = match_result.get("score", 0.0)
            explanation = match_result.get("explanation", "")
            color = get_relation_color(relation)
            label = get_relation_label(relation)

        match_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-match-{i}"

        # 获取该人设特征的分类信息
        # 需要在三个类型中查找该特征
        categories_html = ""
        if category_mapping and persona_name:
            found_categories = None
            # 依次在灵感点、关键点、目的点中查找
            for persona_type in ["灵感点", "关键点", "目的点"]:
                if persona_type in category_mapping:
                    type_mapping = category_mapping[persona_type]
                    if persona_name in type_mapping:
                        found_categories = type_mapping[persona_name].get("所属分类", [])
                        break

            if found_categories:
                # 简洁样式：[大类/中类/小类]
                categories_reversed = list(reversed(found_categories))
                categories_text = "/".join(categories_reversed)
                categories_html = f'<span class="category-simple">[{html_module.escape(categories_text)}]</span>'

        # 获取该人设特征的历史帖子来源
        historical_posts_html = ""
        if source_mapping and persona_name and persona_name in source_mapping:
            source_list = source_mapping[persona_name]
            if source_list:
                historical_cards = []
                for source_item in source_list:
                    post_detail = source_item.get("帖子详情", {})
                    if post_detail:
                        card_html = generate_historical_post_card_html(post_detail, source_item)
                        historical_cards.append(card_html)

                if historical_cards:
                    historical_posts_html = f'''
                    <div class="historical-posts-section">
                        <h4 class="historical-posts-title">历史帖子来源</h4>
                        <div class="historical-posts-grid">
                            {"".join(historical_cards)}
                        </div>
                    </div>
                    '''

        matches_html += f'''
        <div class="match-item-collapsible">
            <div class="match-header" onclick="toggleMatch('{match_id}')">
                <div class="match-header-left">
                    <span class="expand-icon" id="{match_id}-icon">▶</span>
                    <span class="persona-name">{categories_html} {html_module.escape(persona_name)}</span>
                    <span class="relation-badge" style="background: {color};">{label}</span>
                    <span class="score-badge">分数: {score:.2f}</span>
                </div>
            </div>
            <div class="match-content" id="{match_id}-content" style="display: none;">
                <div class="match-explanation">{html_module.escape(explanation)}</div>
                {historical_posts_html}
            </div>
        </div>
        '''

    section_id = f"post-{post_idx}-insp-{insp_idx}-feat-{feature_idx}-section"

    html = f'''
    <div class="match-results-section">
        <div class="match-section-header collapsible-header" onclick="toggleFeatureSection('{section_id}')">
            <div class="header-left">
                <span class="expand-icon" id="{section_id}-icon">▼</span>
                <h4>匹配结果: {html_module.escape(feature_name)}</h4>
            </div>
            <div class="match-stats">{stats_html}</div>
        </div>
        <div class="matches-list" id="{section_id}-content">
            {matches_html}
        </div>
    </div>
    '''
    return html


def generate_toc_html(post_data: Dict, post_idx: int) -> str:
    """生成目录导航HTML"""
    how_result = post_data.get("how解构结果", {})
    inspiration_list = how_result.get("灵感点列表", [])

    toc_items = []

    # 帖子详情
    toc_items.append(f'<div class="toc-item toc-level-1" onclick="scrollToSection(\'post-{post_idx}-detail\')"><span class="toc-badge toc-badge-post">帖子详情</span> 帖子信息</div>')

    # 灵感点
    for insp_idx, inspiration_point in enumerate(inspiration_list):
        name = inspiration_point.get("名称", f"灵感点 {insp_idx + 1}")
        name_short = name[:18] + "..." if len(name) > 18 else name

        toc_items.append(f'<div class="toc-item toc-level-1" onclick="scrollToSection(\'post-{post_idx}-insp-{insp_idx}\')"><span class="toc-badge toc-badge-inspiration">灵感点</span> {html_module.escape(name_short)}</div>')

        # 特征列表
        how_steps = inspiration_point.get("how步骤列表", [])
        if how_steps:
            features = how_steps[0].get("特征列表", [])
            for feat_idx, feature_data in enumerate(features):
                feature_name = feature_data.get("特征名称", f"特征 {feat_idx + 1}")
                toc_items.append(f'<div class="toc-item toc-level-2" onclick="scrollToSection(\'post-{post_idx}-feat-{insp_idx}-{feat_idx}\')"><span class="toc-badge toc-badge-feature">特征</span> {html_module.escape(feature_name)}</div>')

    return f'''
    <div class="toc-container">
        <div class="toc-header">目录导航</div>
        <div class="toc-content">
            {"".join(toc_items)}
        </div>
    </div>
    '''


def generate_post_content_html(post_data: Dict, post_idx: int, category_mapping: Dict = None, source_mapping: Dict = None) -> str:
    """生成单个帖子的完整内容HTML"""
    # 生成目录
    toc_html = generate_toc_html(post_data, post_idx)

    # 1. 帖子详情
    post_detail_html = generate_post_detail_html(post_data, post_idx)

    # 2. 灵感点详情和匹配结果
    how_result = post_data.get("how解构结果", {})
    inspiration_list = how_result.get("灵感点列表", [])

    # 生成所有灵感点的详情HTML（只包含灵感点详情，不包含匹配结果）
    inspirations_detail_html = ""
    for insp_idx, inspiration_point in enumerate(inspiration_list):
        inspiration_detail = generate_inspiration_detail_html(inspiration_point)
        inspirations_detail_html += f'''
        <div id="post-{post_idx}-insp-{insp_idx}" class="inspiration-detail-item content-section">
            {inspiration_detail}
        </div>
        '''

    # 生成所有匹配结果HTML，按照how步骤分组
    all_matches_html = ""
    for insp_idx, inspiration_point in enumerate(inspiration_list):
        inspiration_name = inspiration_point.get("名称", f"灵感点 {insp_idx + 1}")
        how_steps = inspiration_point.get("how步骤列表", [])

        if how_steps:
            # 为每个灵感点创建一个区域
            for step_idx, step in enumerate(how_steps):
                step_name = step.get("步骤名称", f"步骤 {step_idx + 1}")
                features = step.get("特征列表", [])

                # 生成该步骤下所有特征的匹配结果
                features_html = ""
                for feat_idx, feature_data in enumerate(features):
                    match_html = generate_match_results_html([step], feat_idx, insp_idx, post_idx, category_mapping, source_mapping)
                    features_html += f'<div id="post-{post_idx}-feat-{insp_idx}-{feat_idx}" class="feature-match-wrapper">{match_html}</div>'

                # 生成步骤区域（可折叠）
                step_section_id = f"post-{post_idx}-step-{insp_idx}-{step_idx}"
                all_matches_html += f'''
                <div class="step-section">
                    <div class="step-header collapsible-header" onclick="toggleStepSection('{step_section_id}')">
                        <div class="header-left">
                            <span class="expand-icon" id="{step_section_id}-icon">▼</span>
                            <h3 class="step-name">{html_module.escape(step_name)}</h3>
                        </div>
                        <span class="step-inspiration-name">来自: {html_module.escape(inspiration_name)}</span>
                    </div>
                    <div class="step-features-list" id="{step_section_id}-content">
                        {features_html}
                    </div>
                </div>
                '''

    html = f'''
    <div class="content-with-toc">
        {toc_html}
        <div class="main-content">
            <!-- 第一个框：左右分栏（帖子详情 + 灵感点详情） -->
            <div class="top-section-box">
                <div class="two-column-layout">
                    <div class="left-column">
                        <div id="post-{post_idx}-detail" class="post-detail-wrapper">
                            {post_detail_html}
                        </div>
                    </div>
                    <div class="right-column">
                        <div class="inspirations-detail-wrapper">
                            {inspirations_detail_html}
                        </div>
                    </div>
                </div>
            </div>

            <!-- 下面：所有匹配结果 -->
            <div class="matches-section">
                {all_matches_html}
            </div>
        </div>
    </div>
    '''
    return html


def generate_combined_html(posts_data: List[Dict], category_mapping: Dict = None, source_mapping: Dict = None) -> str:
    """生成包含所有帖子的单一HTML（带标签页）"""

    # 生成标签页按钮
    tabs_html = ""
    for i, post in enumerate(posts_data):
        post_detail = post.get("帖子详情", {})
        title = post_detail.get("title", "无标题")
        active_class = "active" if i == 0 else ""
        tabs_html += f'<button class="tab-button {active_class}" onclick="openTab(event, \'post-{i}\')">{html_module.escape(title)}</button>\n'

    # 生成标签页内容
    contents_html = ""
    for i, post in enumerate(posts_data):
        active_class = "active" if i == 0 else ""
        content = generate_post_content_html(post, i, category_mapping, source_mapping)
        contents_html += f'''
        <div id="post-{i}" class="tab-content {active_class}">
            {content}
        </div>
        '''

    html = f'''
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>How解构结果可视化</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: #f5f5f5;
                color: #333;
                line-height: 1.6;
            }}

            .container {{
                max-width: 1600px;
                margin: 0 auto;
                background: white;
                min-height: 100vh;
            }}

            .header {{
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 30px;
                text-align: center;
            }}

            .header h1 {{
                font-size: 32px;
                font-weight: bold;
                margin-bottom: 10px;
            }}

            .header p {{
                font-size: 16px;
                opacity: 0.9;
            }}

            /* 标签页样式 */
            .tabs-container {{
                display: flex;
                background: #f9fafb;
                border-bottom: 2px solid #e5e7eb;
                overflow-x: auto;
                position: sticky;
                top: 0;
                z-index: 100;
                box-shadow: 0 2px 4px rgba(0,0,0,0.05);
            }}

            .tab-button {{
                flex: 1;
                min-width: 200px;
                padding: 18px 30px;
                background: transparent;
                border: none;
                border-bottom: 3px solid transparent;
                cursor: pointer;
                font-size: 15px;
                font-weight: 500;
                color: #6b7280;
                transition: all 0.3s;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }}

            .tab-button:hover {{
                background: #f3f4f6;
                color: #374151;
            }}

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

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

            .tab-content.active {{
                display: block;
                animation: fadeIn 0.3s;
            }}

            @keyframes fadeIn {{
                from {{ opacity: 0; transform: translateY(10px); }}
                to {{ opacity: 1; transform: translateY(0); }}
            }}

            /* 目录和主内容布局 */
            .content-with-toc {{
                display: grid;
                grid-template-columns: 280px 1fr;
                gap: 30px;
                align-items: start;
            }}

            .toc-container {{
                position: sticky;
                top: 80px;
                background: white;
                border: 1px solid #e5e7eb;
                border-radius: 12px;
                overflow: hidden;
                max-height: calc(100vh - 100px);
                display: flex;
                flex-direction: column;
            }}

            .toc-header {{
                padding: 15px 20px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                font-weight: 600;
                font-size: 16px;
            }}

            .toc-content {{
                padding: 10px;
                overflow-y: auto;
                flex: 1;
            }}

            .toc-item {{
                padding: 10px 15px;
                margin: 4px 0;
                cursor: pointer;
                border-radius: 6px;
                font-size: 14px;
                transition: all 0.2s;
                user-select: none;
            }}

            .toc-item:hover {{
                background: #f3f4f6;
            }}

            .toc-item.active {{
                background: #e0e7ff;
                color: #667eea;
                font-weight: 600;
            }}

            .toc-level-1 {{
                color: #111827;
                font-weight: 500;
            }}

            .toc-level-2 {{
                color: #6b7280;
                font-size: 13px;
                padding-left: 30px;
            }}

            .toc-badge {{
                display: inline-block;
                padding: 2px 8px;
                border-radius: 10px;
                font-size: 11px;
                font-weight: 600;
                margin-right: 6px;
            }}

            .toc-badge-post {{
                background: #dbeafe;
                color: #1e40af;
            }}

            .toc-badge-inspiration {{
                background: #fef3c7;
                color: #92400e;
            }}

            .toc-badge-feature {{
                background: #e0e7ff;
                color: #3730a3;
            }}

            .main-content {{
                min-width: 0;
                display: flex;
                flex-direction: column;
                gap: 30px;
            }}

            /* 顶部框：包含帖子详情和灵感点详情 */
            .top-section-box {{
                background: white;
                border: 1px solid #e5e7eb;
                border-radius: 12px;
                padding: 25px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.05);
            }}

            /* 顶部框内的两栏布局 */
            .two-column-layout {{
                display: grid;
                grid-template-columns: 380px 1fr;
                gap: 25px;
                align-items: start;
            }}

            .left-column {{
                position: relative;
            }}

            .post-detail-wrapper {{
                /* 帖子详情区域 */
            }}

            .right-column {{
                min-width: 0;
            }}

            .inspirations-detail-wrapper {{
                display: flex;
                flex-direction: column;
                gap: 20px;
                max-height: 600px;
                overflow-y: auto;
                padding-right: 10px;
            }}

            .inspiration-detail-item {{
                /* 单个灵感点详情 */
            }}

            /* 匹配结果区域 */
            .matches-section {{
                display: flex;
                flex-direction: column;
                gap: 20px;
            }}

            /* 步骤区域 */
            .step-section {{
                background: white;
                border: 1px solid #e5e7eb;
                border-radius: 12px;
                overflow: hidden;
                box-shadow: 0 2px 8px rgba(0,0,0,0.05);
            }}

            .step-header {{
                padding: 20px 25px;
                background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
                border-bottom: 2px solid #e5e7eb;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }}

            .step-name {{
                font-size: 18px;
                color: #111827;
                font-weight: 600;
                margin: 0;
            }}

            .step-inspiration-name {{
                font-size: 14px;
                color: #6b7280;
                font-style: italic;
            }}

            .step-features-list {{
                padding: 20px;
                display: flex;
                flex-direction: column;
                gap: 15px;
            }}

            .feature-match-wrapper {{
                /* 特征匹配容器 */
            }}

            .content-section {{
                scroll-margin-top: 80px;
            }}

            /* 帖子详情卡片（紧凑样式） */
            .post-card-compact {{
                display: grid;
                grid-template-columns: 200px 1fr;
                gap: 20px;
                background: white;
                border: 1px solid #e5e7eb;
                border-radius: 12px;
                overflow: hidden;
                padding: 20px;
                cursor: pointer;
                transition: all 0.3s;
                margin-bottom: 30px;
            }}

            .post-card-compact:hover {{
                box-shadow: 0 4px 12px rgba(0,0,0,0.1);
                transform: translateY(-2px);
                border-color: #667eea;
            }}

            .post-card-image {{
                position: relative;
                display: flex;
                align-items: center;
                justify-content: center;
            }}

            .post-card-thumbnail {{
                width: 100%;
                height: 150px;
                object-fit: contain;
                border-radius: 8px;
                background: #f9fafb;
            }}

            .post-card-thumbnail-placeholder {{
                width: 100%;
                height: 150px;
                display: flex;
                align-items: center;
                justify-content: center;
                background: #f3f4f6;
                color: #9ca3af;
                font-size: 48px;
                border-radius: 8px;
            }}

            .post-card-image-count {{
                position: absolute;
                bottom: 8px;
                right: 8px;
                background: rgba(0, 0, 0, 0.7);
                color: white;
                padding: 4px 8px;
                border-radius: 12px;
                font-size: 11px;
                font-weight: 600;
            }}

            .post-card-content {{
                display: flex;
                flex-direction: column;
                gap: 12px;
            }}

            .post-card-title {{
                font-size: 18px;
                font-weight: 600;
                color: #111827;
                line-height: 1.4;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }}

            .post-card-preview {{
                font-size: 14px;
                color: #6b7280;
                line-height: 1.6;
                overflow: hidden;
                text-overflow: ellipsis;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }}

            .post-card-meta {{
                display: flex;
                gap: 15px;
                font-size: 13px;
                color: #9ca3af;
            }}

            .post-card-stats {{
                display: flex;
                gap: 15px;
                font-size: 13px;
                color: #6b7280;
                font-weight: 500;
            }}

            /* 灵感点详情卡片样式保持不变 */

            .inspiration-detail-card {{
                background: linear-gradient(135deg, #f9fafb 0%, #ffffff 100%);
                padding: 25px;
                border-bottom: 2px solid #e5e7eb;
            }}

            .inspiration-header {{
                margin-bottom: 15px;
                display: flex;
                align-items: center;
                gap: 10px;
            }}

            .inspiration-type-badge {{
                display: inline-block;
                padding: 4px 12px;
                background: #fef3c7;
                color: #92400e;
                border-radius: 12px;
                font-size: 12px;
                font-weight: 600;
            }}

            .inspiration-name {{
                font-size: 20px;
                color: #111827;
                font-weight: 600;
                margin: 0;
            }}

            .inspiration-description {{
                margin-bottom: 15px;
            }}

            .desc-label {{
                font-weight: 600;
                color: #6b7280;
                font-size: 13px;
                margin-bottom: 8px;
            }}

            .desc-text {{
                color: #4b5563;
                font-size: 14px;
                line-height: 1.7;
            }}

            .inspiration-features {{
                margin-top: 15px;
            }}

            .features-label {{
                font-weight: 600;
                color: #6b7280;
                font-size: 13px;
                margin-bottom: 8px;
            }}

            .features-tags {{
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
            }}

            .feature-tag {{
                padding: 5px 12px;
                background: #667eea;
                color: white;
                border-radius: 16px;
                font-size: 13px;
                font-weight: 500;
            }}

            /* 匹配结果部分 */
            .match-results-section {{
                padding: 25px;
                border-bottom: 1px solid #e5e7eb;
            }}

            .match-results-section:last-child {{
                border-bottom: none;
            }}

            .match-section-header {{
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 20px;
                padding-bottom: 15px;
                border-bottom: 2px solid #e5e7eb;
            }}

            .collapsible-header {{
                cursor: pointer;
                user-select: none;
                transition: background 0.2s;
                padding: 10px;
                margin: -10px;
                border-radius: 6px;
            }}

            .collapsible-header:hover {{
                background: #f9fafb;
            }}

            .header-left {{
                display: flex;
                align-items: center;
                gap: 10px;
            }}

            .match-section-header h4 {{
                font-size: 18px;
                color: #111827;
                margin: 0;
            }}

            .match-stats {{
                display: flex;
                gap: 10px;
                flex-wrap: wrap;
            }}

            .stat-badge {{
                padding: 4px 10px;
                border-radius: 12px;
                color: white;
                font-size: 12px;
                font-weight: 600;
            }}

            .matches-list {{
                display: flex;
                flex-direction: column;
                gap: 8px;
            }}

            .match-item-collapsible {{
                border: 1px solid #e5e7eb;
                border-radius: 8px;
                overflow: hidden;
                background: white;
                transition: box-shadow 0.2s;
            }}

            .match-item-collapsible:hover {{
                box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            }}

            .match-header {{
                padding: 12px 16px;
                cursor: pointer;
                user-select: none;
                transition: background 0.2s;
            }}

            .match-header:hover {{
                background: #f9fafb;
            }}

            .match-header-left {{
                display: flex;
                align-items: center;
                gap: 10px;
            }}

            .expand-icon {{
                color: #9ca3af;
                font-size: 12px;
                transition: transform 0.2s;
            }}

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

            .persona-name {{
                font-weight: 600;
                font-size: 14px;
                color: #111827;
            }}

            .relation-badge {{
                padding: 3px 10px;
                border-radius: 12px;
                color: white;
                font-size: 11px;
                font-weight: 600;
            }}

            .score-badge {{
                padding: 3px 10px;
                border-radius: 12px;
                background: #e5e7eb;
                color: #374151;
                font-size: 11px;
                font-weight: 600;
            }}

            .match-content {{
                padding: 16px;
                background: #f9fafb;
                border-top: 1px solid #e5e7eb;
            }}

            .match-explanation {{
                font-size: 13px;
                color: #4b5563;
                line-height: 1.7;
                margin-bottom: 16px;
            }}

            /* 历史帖子来源区域 */
            .historical-posts-section {{
                margin-top: 20px;
                padding-top: 20px;
                border-top: 2px solid #e5e7eb;
            }}

            .historical-posts-title {{
                font-size: 14px;
                font-weight: 600;
                color: #374151;
                margin-bottom: 12px;
            }}

            .historical-posts-grid {{
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
                gap: 16px;
            }}

            .historical-post-card {{
                display: grid;
                grid-template-columns: 120px 1fr;
                gap: 12px;
                background: white;
                border: 1px solid #e5e7eb;
                border-radius: 8px;
                padding: 12px;
                transition: all 0.2s;
                cursor: pointer;
            }}

            .historical-post-card:hover {{
                border-color: #667eea;
                box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
                transform: translateY(-2px);
            }}

            .historical-post-image {{
                position: relative;
            }}

            .historical-post-thumbnail {{
                width: 100%;
                height: 100px;
                object-fit: contain;
                border-radius: 6px;
                background: #f9fafb;
            }}

            .historical-post-content {{
                display: flex;
                flex-direction: column;
                gap: 8px;
            }}

            .historical-post-title {{
                font-size: 13px;
                font-weight: 600;
                color: #1f2937;
                line-height: 1.4;
                overflow: hidden;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }}

            .historical-inspiration-info {{
                display: flex;
                align-items: center;
                gap: 6px;
            }}

            .inspiration-type-badge-small {{
                padding: 2px 6px;
                border-radius: 4px;
                background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
                color: white;
                font-size: 10px;
                font-weight: 600;
            }}

            .inspiration-name-small {{
                font-size: 11px;
                color: #667eea;
                font-weight: 500;
            }}

            .historical-inspiration-desc {{
                font-size: 11px;
                color: #6b7280;
                line-height: 1.5;
                overflow: hidden;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
            }}

            .historical-post-meta {{
                margin: 4px 0;
            }}

            .historical-post-time {{
                font-size: 11px;
                color: #9ca3af;
            }}

            .historical-post-stats {{
                display: flex;
                align-items: center;
                gap: 10px;
                font-size: 11px;
                color: #9ca3af;
            }}

            .view-link {{
                margin-left: auto;
                color: #667eea;
                text-decoration: none;
                font-weight: 500;
            }}

            .view-link:hover {{
                color: #764ba2;
            }}

            .category-simple {{
                color: #9ca3af;
                font-size: 12px;
                font-weight: 400;
                margin-right: 6px;
            }}

            /* 帖子详情模态框 */
            .post-detail-modal {{
                display: none;
                position: fixed;
                z-index: 1000;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.7);
                overflow: auto;
                animation: fadeIn 0.3s;
            }}

            .post-detail-modal.active {{
                display: flex;
                align-items: center;
                justify-content: center;
                padding: 40px 20px;
            }}

            .post-detail-content {{
                position: relative;
                background: white;
                border-radius: 16px;
                max-width: 900px;
                width: 100%;
                max-height: 90vh;
                overflow-y: auto;
                box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            }}

            .post-detail-close {{
                position: sticky;
                top: 20px;
                right: 20px;
                float: right;
                font-size: 36px;
                font-weight: 300;
                color: #9ca3af;
                background: white;
                border: none;
                cursor: pointer;
                width: 40px;
                height: 40px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s;
                z-index: 10;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            }}

            .post-detail-close:hover {{
                color: #ef4444;
                background: #fee2e2;
                transform: rotate(90deg);
            }}

            .post-detail-header {{
                padding: 40px 40px 20px 40px;
                border-bottom: 2px solid #e5e7eb;
            }}

            .post-detail-title {{
                font-size: 28px;
                font-weight: bold;
                color: #111827;
                line-height: 1.4;
                margin-bottom: 15px;
            }}

            .post-detail-meta {{
                display: flex;
                justify-content: space-between;
                align-items: center;
                color: #6b7280;
                font-size: 14px;
                gap: 20px;
            }}

            .post-detail-author {{
                font-weight: 500;
            }}

            .post-detail-time {{
                color: #9ca3af;
            }}

            .post-detail-stats {{
                display: flex;
                gap: 15px;
                font-weight: 500;
            }}

            .post-detail-body {{
                padding: 30px 40px;
            }}

            .post-detail-desc {{
                font-size: 15px;
                color: #4b5563;
                line-height: 1.8;
                margin-bottom: 25px;
                white-space: pre-wrap;
            }}

            .post-detail-images {{
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
                gap: 15px;
                margin-top: 20px;
            }}

            .post-detail-image {{
                width: 100%;
                border-radius: 8px;
                object-fit: cover;
                cursor: pointer;
                transition: transform 0.2s;
            }}

            .post-detail-image:hover {{
                transform: scale(1.02);
            }}

            .post-detail-footer {{
                padding: 20px 40px 30px 40px;
                border-top: 1px solid #e5e7eb;
                text-align: center;
            }}

            .post-detail-link {{
                display: inline-block;
                padding: 12px 30px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                text-decoration: none;
                border-radius: 8px;
                font-weight: 500;
                transition: all 0.3s;
            }}

            .post-detail-link:hover {{
                transform: translateY(-2px);
                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
            }}

            /* 响应式 */
            @media (max-width: 1400px) {{
                .content-with-toc {{
                    grid-template-columns: 240px 1fr;
                }}

                .two-column-layout {{
                    grid-template-columns: 320px 1fr;
                }}
            }}

            @media (max-width: 1200px) {{
                .content-with-toc {{
                    grid-template-columns: 200px 1fr;
                }}

                .two-column-layout {{
                    grid-template-columns: 280px 1fr;
                }}
            }}

            @media (max-width: 1024px) {{
                .content-with-toc {{
                    grid-template-columns: 1fr;
                }}

                .toc-container {{
                    position: relative;
                    top: 0;
                    max-height: 300px;
                }}

                .two-column-layout {{
                    grid-template-columns: 1fr;
                }}

                .inspirations-detail-wrapper {{
                    max-height: none;
                }}

                .post-card-compact {{
                    grid-template-columns: 150px 1fr;
                }}
            }}

            @media (max-width: 768px) {{
                .header {{
                    padding: 20px;
                }}

                .header h1 {{
                    font-size: 24px;
                }}

                .tab-button {{
                    min-width: 150px;
                    padding: 15px 20px;
                }}

                .tab-content {{
                    padding: 15px;
                }}

                .post-info-section {{
                    padding: 20px;
                }}
            }}
        </style>
    </head>
    <body>
        <div class="container">
            <div class="header">
                <h1>How 解构结果可视化</h1>
                <p>灵感点特征匹配分析</p>
            </div>
            <div class="tabs-container">
                {tabs_html}
            </div>
            {contents_html}
        </div>

        <!-- 帖子详情模态框 -->
        <div id="postDetailModal" class="post-detail-modal" onclick="closePostDetail(event)"></div>

        <script>
            function openTab(evt, tabId) {{
                var tabContents = document.getElementsByClassName("tab-content");
                for (var i = 0; i < tabContents.length; i++) {{
                    tabContents[i].classList.remove("active");
                }}

                var tabButtons = document.getElementsByClassName("tab-button");
                for (var i = 0; i < tabButtons.length; i++) {{
                    tabButtons[i].classList.remove("active");
                }}

                document.getElementById(tabId).classList.add("active");
                evt.currentTarget.classList.add("active");
            }}

            function scrollToSection(sectionId) {{
                var element = document.getElementById(sectionId);
                if (element) {{
                    element.scrollIntoView({{ behavior: 'smooth', block: 'start' }});

                    // 更新目录项的active状态
                    var tocItems = document.querySelectorAll('.toc-item');
                    tocItems.forEach(function(item) {{
                        item.classList.remove('active');
                    }});
                    event.currentTarget.classList.add('active');
                }}
            }}

            function toggleMatch(matchId) {{
                var content = document.getElementById(matchId + '-content');
                var icon = document.getElementById(matchId + '-icon');

                if (content.style.display === 'none') {{
                    content.style.display = 'block';
                    icon.classList.add('expanded');
                }} else {{
                    content.style.display = 'none';
                    icon.classList.remove('expanded');
                }}
            }}

            function toggleFeatureSection(sectionId) {{
                var content = document.getElementById(sectionId + '-content');
                var icon = document.getElementById(sectionId + '-icon');

                if (content.style.display === 'none') {{
                    content.style.display = 'flex';
                    icon.textContent = '▼';
                }} else {{
                    content.style.display = 'none';
                    icon.textContent = '▶';
                }}
            }}

            function toggleStepSection(sectionId) {{
                var content = document.getElementById(sectionId + '-content');
                var icon = document.getElementById(sectionId + '-icon');

                if (content.style.display === 'none') {{
                    content.style.display = 'flex';
                    icon.textContent = '▼';
                }} else {{
                    content.style.display = 'none';
                    icon.textContent = '▶';
                }}
            }}

            function showPostDetail(element) {{
                const postDataStr = element.dataset.postData;
                if (!postDataStr) return;

                try {{
                    const postData = JSON.parse(postDataStr);

                    // 生成图片HTML
                    let imagesHtml = '';
                    if (postData.images && postData.images.length > 0) {{
                        imagesHtml = postData.images.map(img =>
                            `<img src="${{img}}" class="post-detail-image" alt="图片">`
                        ).join('');
                    }} else {{
                        imagesHtml = '<div style="text-align: center; color: #9ca3af; padding: 40px;">暂无图片</div>';
                    }}

                    const modalHtml = `
                        <div class="post-detail-content" onclick="event.stopPropagation()">
                            <button class="post-detail-close" onclick="closePostDetail()">×</button>
                            <div class="post-detail-header">
                                <div class="post-detail-title">${{postData.title || '无标题'}}</div>
                                <div class="post-detail-meta">
                                    <div>
                                        <span class="post-detail-author">👤 ${{postData.author}}</span>
                                        <span class="post-detail-time"> · 📅 ${{postData.publish_time}}</span>
                                    </div>
                                    <div class="post-detail-stats">
                                        <span>👍 ${{postData.like_count}}</span>
                                        <span>💬 ${{postData.comment_count}}</span>
                                        <span>⭐ ${{postData.collect_count}}</span>
                                    </div>
                                </div>
                            </div>
                            <div class="post-detail-body">
                                ${{postData.body_text ? `<div class="post-detail-desc">${{postData.body_text}}</div>` : '<div style="color: #9ca3af;">暂无正文</div>'}}
                                <div class="post-detail-images">
                                    ${{imagesHtml}}
                                </div>
                            </div>
                            <div class="post-detail-footer">
                                <a href="${{postData.link}}" target="_blank" class="post-detail-link">
                                    在小红书查看完整内容 →
                                </a>
                            </div>
                        </div>
                    `;

                    let modal = document.getElementById('postDetailModal');
                    if (!modal) {{
                        modal = document.createElement('div');
                        modal.id = 'postDetailModal';
                        modal.className = 'post-detail-modal';
                        modal.onclick = closePostDetail;
                        document.body.appendChild(modal);
                    }}

                    modal.innerHTML = modalHtml;
                    modal.classList.add('active');
                    document.body.style.overflow = 'hidden';
                }} catch (e) {{
                    console.error('解析帖子数据失败:', e);
                }}
            }}

            function closePostDetail(event) {{
                if (event && event.target !== event.currentTarget) return;

                const modal = document.getElementById('postDetailModal');
                if (modal) {{
                    modal.classList.remove('active');
                    document.body.style.overflow = '';
                }}
            }}

            function moveImage(postId, direction) {{
                var carousel = document.querySelector(`[data-post-id="${{postId}}"]`);
                var track = document.getElementById(postId + '-track');
                var totalImages = parseInt(carousel.getAttribute('data-total-images'));

                if (!carousel.currentIndex) {{
                    carousel.currentIndex = 0;
                }}

                carousel.currentIndex = (carousel.currentIndex + direction + totalImages) % totalImages;
                track.style.transform = `translateX(-${{carousel.currentIndex * 100}}%)`;

                updateIndicators(postId, carousel.currentIndex);
            }}

            function jumpToImage(postId, index) {{
                var carousel = document.querySelector(`[data-post-id="${{postId}}"]`);
                var track = document.getElementById(postId + '-track');

                carousel.currentIndex = index;
                track.style.transform = `translateX(-${{index * 100}}%)`;

                updateIndicators(postId, index);
            }}

            function updateIndicators(postId, activeIndex) {{
                var indicators = document.querySelectorAll(`#${{postId}}-indicators .indicator`);
                indicators.forEach((indicator, i) => {{
                    if (i === activeIndex) {{
                        indicator.classList.add('active');
                    }} else {{
                        indicator.classList.remove('active');
                    }}
                }});
            }}

            // 目录激活状态追踪
            function updateTocActiveState() {{
                const mainContent = document.querySelector('.main-content');
                if (!mainContent) return;

                const sections = document.querySelectorAll('.post-detail-wrapper, .inspiration-detail-card, .step-section');
                const tocItems = document.querySelectorAll('.toc-item');

                let currentActive = null;
                const scrollTop = mainContent.scrollTop;
                const windowHeight = window.innerHeight;

                // 找到当前在视口中的section
                sections.forEach(section => {{
                    const rect = section.getBoundingClientRect();
                    const sectionTop = rect.top;
                    const sectionBottom = rect.bottom;

                    // 如果section的顶部在视口上半部分，认为它是当前激活的
                    if (sectionTop < windowHeight / 2 && sectionBottom > 0) {{
                        currentActive = section.id;
                    }}
                }});

                // 更新目录项的激活状态
                tocItems.forEach(item => {{
                    const targetId = item.getAttribute('onclick')?.match(/'([^']+)'/)?.[1];
                    if (targetId === currentActive) {{
                        item.classList.add('active');
                    }} else {{
                        item.classList.remove('active');
                    }}
                }});
            }}

            // 监听滚动事件
            document.addEventListener('DOMContentLoaded', function() {{
                const mainContent = document.querySelector('.main-content');
                if (mainContent) {{
                    mainContent.addEventListener('scroll', function() {{
                        // 使用节流避免频繁触发
                        if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
                        this.scrollTimeout = setTimeout(updateTocActiveState, 50);
                    }});

                    // 初始化时更新一次
                    updateTocActiveState();
                }}
            }});
        </script>
    </body>
    </html>
    '''
    return html


def main():
    """主函数"""
    script_dir = Path(__file__).parent
    project_root = script_dir.parent.parent
    data_dir = project_root / "data" / "data_1118"

    # V3: 更新输入目录为 当前帖子_how解构结果_v2
    input_dir = data_dir / "当前帖子_how解构结果_v2"
    output_file = data_dir / "当前帖子_how解构结果_v2_可视化.html"

    print(f"读取 how 解构结果: {input_dir}")

    # 加载特征分类映射
    print(f"加载特征分类映射...")
    category_mapping = load_feature_category_mapping()
    print(f"已加载 {sum(len(v) for v in category_mapping.values())} 个特征分类")

    # 加载特征来源映射
    print(f"加载特征来源映射...")
    source_mapping = load_feature_source_mapping()
    print(f"已加载 {len(source_mapping)} 个特征的来源信息")

    json_files = list(input_dir.glob("*_how_v2_*.json"))
    print(f"找到 {len(json_files)} 个文件\n")

    posts_data = []
    for i, file_path in enumerate(json_files, 1):
        print(f"读取文件 [{i}/{len(json_files)}]: {file_path.name}")
        with open(file_path, "r", encoding="utf-8") as f:
            post_data = json.load(f)
            posts_data.append(post_data)

    print(f"\n生成合并的 HTML...")
    html_content = generate_combined_html(posts_data, category_mapping, source_mapping)

    print(f"保存到: {output_file}")
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(html_content)

    print(f"\n完成! 可视化文件已保存")
    print(f"请在浏览器中打开: {output_file}")


if __name__ == "__main__":
    main()
