File size: 4,486 Bytes
342e4c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import io
import re
import streamlit as st
import plotly.io as pio
from utils.sanitize_code import sanitize_code
import base64

def write_html(agents):
    report_agent = agents[-1]
    report_obj = report_agent.load_report()  # Reportcore

    # 图像分析列表
    analysis_list = agents[2].summary_fig_analysis_list()

    # 给 heading 加唯一 id
    heading_counter = {"count": 0}
    def _gen_id(text):
        heading_counter["count"] += 1
        return f"sec-{heading_counter['count']}"

    # 遍历树 → 正文 & TOC
    toc_items, content_items = [], []

    def _process_node(node):
        if node.type == "heading":
            sec_id = _gen_id(node.text)
            toc_items.append((sec_id, node.text, node.level))
            content_items.append(
                f"<h{node.level} id='{sec_id}' class='font-bold text-gray-800 mt-8 mb-4 text-{max(6-node.level,1)}xl'>{node.text}</h{node.level}>"
            )
            for ch in node.children:
                _process_node(ch)

        elif node.type == "paragraph":
            parts = re.split(r'(\[FIG:\d+\])', node.text)
            html_parts = []
            for part in parts:
                part = part.strip()
                if not part:
                    continue
                if part.startswith("[FIG:") and part.endswith("]"):
                    idx = int(part[5:-1])
                    fig_html = ""
                    if 0 <= idx < len(analysis_list):
                        fig_obj = analysis_list[idx].get("figure")
                        try:
                            buf = io.BytesIO()
                            pio.write_image(fig_obj, buf, format="png")
                            data = buf.getvalue()
                            b64 = base64.b64encode(data).decode("utf-8")
                            fig_html = f"<div class='flex justify-center my-6'><img src='data:image/png;base64,{b64}' class='rounded-xl shadow-md max-w-3xl w-full'/></div>"
                        except Exception as e:
                            fig_html = f"<p class='text-red-500'>[图像插入失败: {e}]</p>"
                    html_parts.append(fig_html)
                else:
                    html_parts.append(f"<p class='text-gray-700 leading-relaxed mb-4'>{part}</p>")
            content_items.append("".join(html_parts))

        else:  # root
            for ch in node.children:
                _process_node(ch)

    _process_node(report_obj.root)

    # TOC HTML
    toc_html = ["<nav class='space-y-2'>"]
    prev_level = -1
    for sec_id, text, level in toc_items:
        indent = "ml-" + str(level * 4)
        toc_html.append(f"<a href='#{sec_id}' class='block {indent} text-gray-600 hover:text-blue-600 transition-colors'>{text}</a>")
    toc_html.append("</nav>")

    # 拼接完整 HTML
    html_content = f"""
    <html>
    <head>
        <meta charset="utf-8">
        <script src="https://cdn.tailwindcss.com"></script>
        <script>
        document.addEventListener("DOMContentLoaded", function() {{
            const sections = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
            const navLinks = document.querySelectorAll("nav a");

            function onScroll() {{
                let scrollPos = document.documentElement.scrollTop || document.body.scrollTop;
                let currentId = "";
                sections.forEach(sec => {{
                    if (sec.offsetTop - 80 <= scrollPos) {{
                        currentId = sec.id;
                    }}
                }});
                navLinks.forEach(link => {{
                    link.classList.remove("font-bold", "text-blue-600");
                    if (link.getAttribute("href") === "#" + currentId) {{
                        link.classList.add("font-bold", "text-blue-600");
                    }}
                }});
            }}
            window.addEventListener("scroll", onScroll);
            onScroll();
        }});
        </script>
    </head>
    <body class="flex font-sans">
        <aside class="fixed top-0 left-0 h-screen w-64 bg-gray-100 border-r border-gray-300 p-6 overflow-y-auto">
            <h2 class="text-xl font-bold mb-4">目录</h2>
            {''.join(toc_html)}
        </aside>
        <main class="ml-64 p-10 w-full max-w-5xl">
            {''.join(content_items)}
        </main>
    </body>
    </html>
    """

    report_agent.save_html(html_content)
    st.success("HTML 报告 (Tailwind 风格) 生成成功 ✅")