在你是一位资深的前端程序员，你将设计并生成一段即将生产环境使用（因此，你需要审慎地生成你的代码）的、完整的，并非是示例的HTML程序源代码（注：使用适合的思维导图外部库，将html/css/js写在同一个文件，并只用写在同一个代码框中，代码不能太简单，要满足下面所有的需求），用于将特定格式的日志进行节点化的可视化展示和基于结构化、思维导图化的精简化处理，对标Xmind。该程序的主要功能是解析并展示以下格式的日志信息：

部分示例日志：
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiInfoCache::getCustomKpi,retsize=255,netype=-9,solutionVer=3100
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: getKPIFormula use cache
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiMgrComponentImpl::getKPIFormula Exited...
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiMgrComponentImpl::getRawItemFormula Entered...
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiInfoCache::getCustomKpi,retsize=255
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: getRawItemFormula use cache neTypeId:295 versionId:1586450140
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiMgrComponentImpl::getRawItemFormula Exited...
I001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: KpiMgrComponentImpl::getFormulaByVersion_i Exited...
E001:10-26 10:33:23.194(2355912|2854059)Sum.Rpt: mapCounterParseInfo number:1854
日志解析规则
日志等级：日志开头字符表示日志等级，I表示信息日志，E表示错误日志。
时间戳：日志格式中的10-26 10:33:23.192部分表示日志发生的时间。
进程和线程号：(2355912|2400359)分别表示进程号和线程号。
日志内容：日志正文内容在其后的部分。
如果内容以Entered...或Exited...结尾，表示函数的进入或退出;
如果内容并不以上述内容结尾，则这只是一般的日志。

可视化需求
输入方式：日志文件通过
文本区域输入。</p>
<p>日志按线程分类：根据线程号将日志按部分分组。<br>界面分为左右两个部分</p>
<p>左部分：<br>线程关系组件：显示线程间关系结构，每个线程部分用可交互的节点表示，支持收缩和展开操作。<br>当发现一个线程在另一个线程的执行过程中启动，则该线程被视为从该线程生成，并呈现为嵌套关系。</p>
<p>右部分：<br>日志跟踪组件：当用户点击线程关系组件中的某个线程节点时，日志跟踪组件显示对应线程的详细日志解析结果。日志按照函数调用的执行顺序和嵌套层级（Entered...和Exited...），以可逐级收缩展开的层级节点展示。对于一般的日志，直接呈现在同一个函数层级即可。<br>层级节点组织：<br>按日志的时间顺序和线程嵌套关系，将日志整理为层级结构。</p>
<p>可视化组件：<br>节点显示要求：<br>每个节点按日志格式突出显示发生的时间和日志等级，主要显示日志内容。节点不应该只是简单的一行文字，而是像思维导图一样的呈现，节点之间通过优美的线条去连接，而且又能通过节点整体的缩进体现层级关系。<br>支持鼠标右键标记/去标记不重要的节点，使其变为半透明显示，便于用户聚焦关键日志信息。</p>
<p>注意事项<br>线程关系展示：线程关系组件中，重点是展示线程执行层级和嵌套关系，能够伸缩展开，体现线程间的创建关系。<br>代码优化：优化程序性能，确保日志节点的标记和取消标记功能流畅，且能快速高效地更新节点透明度。</p>

以下代码的报错：
Uncaught TypeError: Cannot read properties of undefined (reading 'split')
    at getNodeWidth (generator.html:623:32)
    at SVGForeignObjectElement.<anonymous> (generator.html:326:37)
    at SVGForeignObjectElement.<anonymous> (d3.v7.min.js:2:18187)
    at Vn.each (d3.v7.min.js:2:25660)
    at Vn.attr (d3.v7.min.js:2:25847)
    at renderThreadTree (generator.html:326:18)
    at HTMLButtonElement.<anonymous> (generator.html:168:13)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>日志可视化工具</title>
    <!-- D3.js CDN -->
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <!-- 简单的重置样式 -->
    <style>
        body, html {
            margin: 0;
            padding: 0;
            height: 100%;
            font-family: Arial, sans-serif;
            overflow: hidden;
        }
        header {
            padding: 10px;
            background-color: #2c3e50;
            color: white;
            text-align: center;
        }
        #controls {
            padding: 10px;
            background-color: #ecf0f1;
            overflow: auto;
        }
        #controls textarea {
            width: 100%;
            height: 150px;
            resize: vertical;
            padding: 10px;
            box-sizing: border-box;
            font-family: monospace;
            font-size: 14px;
        }
        #controls button {
            margin-top: 10px;
            padding: 10px 20px;
            font-size: 16px;
            background-color: #2980b9;
            color: white;
            border: none;
            cursor: pointer;
            border-radius: 4px;
        }
        #controls button:hover {
            background-color: #3498db;
        }
        #main {
            display: flex;
            height: calc(100% - 220px);
            overflow: hidden;
        }
        .container {
            flex: 1;
            position: relative;
            border: 1px solid #bdc3c7;
            overflow: hidden;
        }
        .container::after {
            content: attr(data-title);
            position: absolute;
            top: 5px;
            left: 10px;
            background: rgba(255,255,255,0.8);
            padding: 2px 5px;
            border-radius: 3px;
            font-weight: bold;
            z-index: 1;
        }
        .tree-container {
            width: 100%;
            height: 100%;
        }
        /* 自定义右键菜单样式 */
        .custom-context-menu {
            position: absolute;
            z-index: 1000;
            width: 200px;
            background: white;
            border: 1px solid #bdc3c7;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
            display: none;
        }
        .custom-context-menu ul {
            list-style: none;
            margin: 0;
            padding: 5px 0;
        }
        .custom-context-menu ul li {
            padding: 8px 12px;
            cursor: pointer;
        }
        .custom-context-menu ul li:hover {
            background-color: #ecf0f1;
        }
        /* Tooltip样式 */
        .tooltip {
            position: absolute;
            text-align: left;
            width: auto;
            padding: 10px;
            font: 12px sans-serif;
            background: rgba(255, 255, 255, 0.9);
            border: 1px solid #bdc3c7;
            border-radius: 4px;
            pointer-events: none;
            box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
        }
    </style>
</head>
<body>
    <header>
        <h1>日志可视化工具</h1>
    </header>
    <div id="controls">
        <textarea id="log-input" placeholder="在此处粘贴日志内容..."></textarea>
        <button id="parse-button">解析并可视化</button>
    </div>
    <div id="main">
        <div class="container" data-title="线程关系">
            <svg id="thread-tree" class="tree-container"></svg>
        </div>
        <div class="container" data-title="日志跟踪">
            <svg id="log-tree" class="tree-container"></svg>
        </div>
    </div>

    <!-- 自定义右键菜单 -->
    <div id="context-menu" class="custom-context-menu">
        <ul>
            <li id="toggle-importance">标记/去标记重要性</li>
            <li id="generate-sequence">生成时序图</li>
        </ul>
    </div>

    <!-- Tooltip -->
    <div id="tooltip" class="tooltip" style="opacity:0;"></div>

    <script>
        // 全局变量存储解析后的日志数据
        let parsedLogs = {};
        let threadParents = {};

        // 存储已标记的重要节点
        let markedNodes = new Set();

        // 当前选中的节点信息
        let selectedNode = {
            type: null, // 'thread' 或 'log'
            id: null,
            data: null,
            tree: null
        };

        // 右键菜单元素
        const contextMenu = document.getElementById('context-menu');
        const toggleImportanceMenu = document.getElementById('toggle-importance');
        const generateSequenceMenu = document.getElementById('generate-sequence');

        // Tooltip元素
        const tooltip = d3.select("#tooltip");

        document.getElementById('parse-button').addEventListener('click', function() {
            const logText = document.getElementById('log-input').value;
            parseLogs(logText);
            renderThreadTree();
            clearLogTree();
        });

        /**
         * 解析日志文本
         * @param {string} text - 日志文本
         */
        function parseLogs(text) {
            const lines = text.split('\n').filter(line => line.trim() !== '');
            parsedLogs = {};
            threadParents = {};

            const logEntries = [];

            const regex = /^(I|E)\d{3}:(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\((\d+)\|(\d+)\)Sum\.Rpt: (.+)$/;

            for (const line of lines) {
                const match = line.match(regex);
                if (match) {
                    const [_, level, timestamp, processId, threadId, content] = match;
                    const log = { level, timestamp, processId, threadId, content, raw: line };
                    logEntries.push(log);
                    if (!parsedLogs[threadId]) {
                        parsedLogs[threadId] = [];
                    }
                    parsedLogs[threadId].push(log);
                }
            }

            // 处理线程关系
            inferThreadRelationships(logEntries);
        }

        /**
         * 推断线程之间的关系
         * 简化假设：一个线程的启动时间在另一个线程的启动时间之后，则视为子线程
         * @param {Array} logEntries - 所有日志条目
         */
        function inferThreadRelationships(logEntries) {
            // 获取每个线程的最早时间
            const threadStartTimes = {};
            for (const threadId in parsedLogs) {
                const firstLog = parsedLogs[threadId][0];
                // 假设日期为10-26，年份为2023
                const date = new Date(`2023-10-26T${convertTimestamp(firstLog.timestamp)}Z`);
                threadStartTimes[threadId] = date;
            }

            // 排序线程按开始时间
            const sortedThreads = Object.keys(threadStartTimes).sort((a, b) => threadStartTimes[a] - threadStartTimes[b]);

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

            // 使用虚拟根节点
            threadParents['root'] = null; // 虚拟根

            // 第一线程作为根线程的子线程
            for (let i = 0; i < sortedThreads.length; i++) {
                const childThread = sortedThreads[i];
                if (i === 0) {
                    threadParents[childThread] = 'root';
                } else {
                    // 根据启动时间找到最近的一个父线程
                    let parentThread = 'root';
                    for (let j = i - 1; j >= 0; j--) {
                        const potentialParent = sortedThreads[j];
                        if (threadStartTimes[potentialParent] < threadStartTimes[childThread]) {
                            parentThread = potentialParent;
                            break;
                        }
                    }
                    threadParents[childThread] = parentThread;
                }
            }
        }

        /**
         * 转换时间戳为ISO格式
         * @param {string} timestamp - 原始时间戳（MM-DD HH:MM:SS.mmm）
         * @returns {string} - 转换后的时间戳（HH:MM:SS.mmm）
         */
        function convertTimestamp(timestamp) {
            return timestamp.replace(' ', 'T');
        }

        /**
         * 渲染线程关系树
         */
        function renderThreadTree() {
            // 清旧的SVG
            d3.select("#thread-tree").selectAll("*").remove();

            // 构建树形结构数据，包括虚拟根
            const treeData = buildThreadTree('root', threadParents);

            const margin = { top: 20, right: 200, bottom: 20, left: 200 },
                  width = document.getElementById('thread-tree').clientWidth - margin.left - margin.right,
                  height = document.getElementById('thread-tree').clientHeight - margin.top - margin.bottom;

            const svg = d3.select("#thread-tree")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
              .append("g")
                .attr("transform", `translate(${margin.left},${margin.top})`);

            // 添加缩放和平移
            const zoom = d3.zoom()
                .scaleExtent([0.1, 10])
                .on("zoom", (event) => {
                    svg.attr("transform", event.transform);
                });

            d3.select("#thread-tree").call(zoom);

            const treemap = d3.tree().size([height, width]);

            const root = d3.hierarchy(treeData, d => d.children);
            treemap(root);

            // Links
            const link = svg.selectAll(".link")
                .data(root.descendants().slice(1))
              .enter().append("path")
                .attr("class", "link")
                .attr("d", function(d) {
                    return `M${d.y},${d.x}C${(d.parent.y + d.y) / 2},${d.x} ${(d.parent.y + d.y) / 2},${d.parent.x} ${d.parent.y},${d.parent.x}`;
                })
                .attr("fill", "none")
                .attr("stroke", "#ccc")
                .attr("stroke-width", 2);

            // Nodes
            const node = svg.selectAll(".node")
                .data(root.descendants())
              .enter().append("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${d.y},${d.x})`)
                .on("click", (event, d) => {
                    if (d.data.id !== 'root') { // 排除虚拟根
                        const threadId = d.data.id;
                        renderLogTree(threadId);
                    }
                })
                .on("contextmenu", (event, d) => {
                    event.preventDefault();
                    if (d.data.id !== 'root') { // 排除虚拟根
                        selectedNode = { type: 'thread', id: d.data.id, data: d.data, tree: 'thread' };
                        showContextMenu(event.pageX, event.pageY);
                    }
                })
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended));

            // Node rectangles with adaptive sizes using foreignObject
            node.append("foreignObject")
                .attr("width", d => getNodeWidth(d.data))
                .attr("height", d => getNodeHeight(d.data))
                .attr("x", d => - getNodeWidth(d.data) / 2)
                .attr("y", d => - getNodeHeight(d.data) / 2)
                .append("xhtml:div")
                .style("width", d => getNodeWidth(d.data) + "px")
                .style("height", d => getNodeHeight(d.data) + "px")
                .style("background", d => d.data.id === 'root' ? "#ffffff00" : "#4FC3F7")
                .style("border", d => d.data.id === 'root' ? "none" : "2px solid #0288D1")
                .style("border-radius", "5px")
                .style("color", "#ffffff")
                .style("font-size", "12px")
                .style("padding", "5px")
                .style("box-sizing", "border-box")
                .style("cursor", d => d.data.id === 'root' ? "default" : "pointer")
                .style("opacity", d => markedNodes.has(d.data.id) ? 0.5 : 1.0)
                .html(d => {
                    if (d.data.id === 'root') return '';
                    return `线程 ${d.data.id}`;
                });

            // Zoom Behavior
            function dragstarted(event, d) {
                if (!event.active) d3.select(this).raise();
                d.fx = d.x;
                d.fy = d.y;
            }

            function dragged(event, d) {
                d.fx = event.x;
                d.fy = event.y;
                d3.select(this).attr("transform", `translate(${d.fx},${d.fy})`);
            }

            function dragended(event, d) {
                if (!event.active) {
                    d.fx = null;
                    d.fy = null;
                }
            }
        }

        /**
         * 构建线程树结构，包括虚拟根
         * @param {string} rootId - 虚拟根ID
         * @param {Object} parents - 线程父子关系
         * @returns {Object} - 树形结构
         */
        function buildThreadTree(rootId, parents) {
            if (rootId !== 'root') {
                // 对于非虚拟根节点，构建标准树
                const node = { id: rootId, children: [] };
                for (const child in parents) {
                    if (parents[child] === rootId) {
                        node.children.push(buildThreadTree(child, parents));
                    }
                }
                return node;
            } else {
                // 对于虚拟根，收集所有直接子线程
                const tree = { id: 'root', children: [] };
                for (const child in parents) {
                    if (parents[child] === null || parents[child] === 'root') {
                        tree.children.push(buildThreadTree(child, parents));
                    }
                }
                return tree;
            }
        }

        /**
         * 清除日志树
         */
        function clearLogTree() {
            d3.select("#log-tree").selectAll("*").remove();
            const svg = d3.select("#log-tree");
            svg.append("text")
                .attr("x", "50%")
                .attr("y", "50%")
                .attr("text-anchor", "middle")
                .attr("fill", "#7f8c8d")
                .text("请选择左侧的线程以查看日志");
        }

        /**
         * 渲染指定线程的日志树
         * @param {string} threadId - 线程ID
         */
        function renderLogTree(threadId) {
            const logs = parsedLogs[threadId];
            if (!logs) {
                alert('没有找到该线程的日志。');
                return;
            }

            // 构建层级结构
            const treeData = buildLogTree(logs);

            // 清旧的SVG
            d3.select("#log-tree").selectAll("*").remove();

            const margin = { top: 20, right: 200, bottom: 20, left: 200 },
                  width = document.getElementById('log-tree').clientWidth - margin.left - margin.right,
                  height = document.getElementById('log-tree').clientHeight - margin.top - margin.bottom;

            const svg = d3.select("#log-tree")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
              .append("g")
                .attr("transform", `translate(${margin.left},${margin.top})`);

            // 添加缩放和平移
            const zoom = d3.zoom()
                .scaleExtent([0.1, 10])
                .on("zoom", (event) => {
                    svg.attr("transform", event.transform);
                });

            d3.select("#log-tree").call(zoom);

            const treemap = d3.tree().size([height, width]);

            const root = d3.hierarchy(treeData, d => d.children);
            treemap(root);

            // Links
            const link = svg.selectAll(".link")
                .data(root.descendants().slice(1))
              .enter().append("path")
                .attr("class", "link")
                .attr("d", function(d) {
                    return `M${d.y},${d.x}C${(d.parent.y + d.y) / 2},${d.x} ${(d.parent.y + d.y) / 2},${d.parent.x} ${d.parent.y},${d.parent.x}`;
                })
                .attr("fill", "none")
                .attr("stroke", "#ccc")
                .attr("stroke-width", 2);

            // Nodes
            const node = svg.selectAll(".node")
                .data(root.descendants())
              .enter().append("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${d.y},${d.x})`)
                .on("contextmenu", (event, d) => {
                    event.preventDefault();
                    selectedNode = { type: 'log', id: d.data.id, data: d.data, tree: 'log' };
                    showContextMenu(event.pageX, event.pageY);
                })
                .on("mouseover", (event, d) => {
                    tooltip.transition().duration(200).style("opacity", .9);
                    tooltip.html(formatTooltip(d.data))
                        .style("left", (event.pageX + 10) + "px")
                        .style("top", (event.pageY - 28) + "px");
                })
                .on("mouseout", () => {
                    tooltip.transition().duration(500).style("opacity", 0);
                })
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended));

            // Node rectangles with adaptive sizes using foreignObject
            node.append("foreignObject")
                .attr("width", d => getNodeWidth(d.data))
                .attr("height", d => getNodeHeight(d.data))
                .attr("x", d => - getNodeWidth(d.data) / 2)
                .attr("y", d => - getNodeHeight(d.data) / 2)
                .append("xhtml:div")
                .style("width", d => getNodeWidth(d.data) + "px")
                .style("height", d => getNodeHeight(d.data) + "px")
                .style("background", d => getColorByLevel(d.data.level))
                .style("border", "1px solid #424242")
                .style("border-radius", "5px")
                .style("color", "#ffffff")
                .style("font-size", "12px")
                .style("padding", "5px")
                .style("box-sizing", "border-box")
                .style("cursor", "pointer")
                .style("opacity", d => markedNodes.has(d.data.id) ? 0.5 : 1.0)
                .html(d => formatLabel(d.data));

            // Zoom Behavior
            function dragstarted(event, d) {
                if (!event.active) d3.select(this).raise();
                d.fx = d.x;
                d.fy = d.y;
            }

            function dragged(event, d) {
                d.fx = event.x;
                d.fy = event.y;
                d3.select(this).attr("transform", `translate(${d.fx},${d.fy})`);
            }

            function dragended(event, d) {
                if (!event.active) {
                    d.fx = null;
                    d.fy = null;
                }
            }
        }

        /**
         * 构建日志树结构
         * @param {Array} logs - 日志数组
         * @returns {Object} - 树形结构
         */
        function buildLogTree(logs) {
            const root = { id: 0, content: "根", children: [], level: "I" };
            let currentNode = root;
            let stack = [root];
            let nodeId = 1;

            for (const log of logs) {
                const isEntry = /Entered\.\.\.$/.test(log.content);
                const isExit = /Exited\.\.\.$/.test(log.content);

                if (isEntry) {
                    const newNode = {
                        id: nodeId++,
                        content: log.content,
                        timestamp: log.timestamp,
                        threadId: log.threadId,
                        level: log.level,
                        children: []
                    };
                    currentNode.children.push(newNode);
                    stack.push(newNode);
                    currentNode = newNode;
                } else if (isExit) {
                    stack.pop();
                    currentNode = stack[stack.length - 1];
                } else {
                    const newNode = {
                        id: nodeId++,
                        content: log.content,
                        timestamp: log.timestamp,
                        threadId: log.threadId,
                        level: log.level,
                        children: []
                    };
                    currentNode.children.push(newNode);
                }
            }

            return root;
        }

        /**
         * 格式化节点标签
         * @param {Object} data - 节点数据
         * @returns {string} - 格式化后的HTML字符串
         */
        function formatLabel(data) {
            // 主体内容
            let mainContent = data.content;
            // 附加信息
            const additionalInfo = `<br/><span style="font-size:10px; color:#d1d1d1;">[${data.timestamp}] | 线程:${data.threadId}</span>`;
            return mainContent + additionalInfo;
        }

        /**
         * 格式化Tooltip内容
         * @param {Object} data - 节点数据
         * @returns {string} - Tooltip的HTML内容
         */
        function formatTooltip(data) {
            return `<strong>时间:</strong> ${data.timestamp}<br/>
                    <strong>级别:</strong> ${data.level}<br/>
                    <strong>内容:</strong> ${data.content}`;
        }

        /**
         * 获取字体宽度
         * @param {string} text - 文本
         * @param {string} font - 字体样式
         * @returns {number} - 文本宽度
         */
        function getTextWidth(text, font) {
            const canvas = document.createElement("canvas");
            const context = canvas.getContext("2d");
            context.font = font;
            const metrics = context.measureText(text);
            return metrics.width;
        }

        /**
         * 获取节点内容的宽度
         * @param {Object} data - 节点数据
         * @returns {number}
         */
        function getNodeWidth(data) {
            // 设置最大宽度，超出则换行
            const maxWidth = 200;
            const text = data.content;
            const font = "12px Arial";
            const words = text.split(' ');
            let line = '';
            let maxLineWidth = 0;

            for (let i = 0; i < words.length; i++) {
                const testLine = line + words[i] + ' ';
                const testWidth = getTextWidth(testLine, font);
                if (testWidth > maxWidth && i > 0) {
                    if (getTextWidth(line, font) > maxLineWidth) {
                        maxLineWidth = getTextWidth(line, font);
                    }
                    line = words[i] + ' ';
                } else {
                    line = testLine;
                }
            }
            if (getTextWidth(line, font) > maxLineWidth) {
                maxLineWidth = getTextWidth(line, font);
            }
            // 附加信息宽度
            const additionalWidth = getTextWidth(`[${data.timestamp}] | 线程:${data.threadId}`, font);
            return Math.max(maxWidth, additionalWidth) + 20;
        }

        /**
         * 获取节点内容的高度
         * @param {Object} data - 节点数据
         * @returns {number}
         */
        function getNodeHeight(data) {
            const maxWidth = 200;
            const text = data.content;
            const font = "12px Arial";
            const words = text.split(' ');
            let line = '';
            let lines = 1;

            for (let i = 0; i < words.length; i++) {
                const testLine = line + words[i] + ' ';
                const testWidth = getTextWidth(testLine, font);
                if (testWidth > maxWidth && i > 0) {
                    lines++;
                    line = words[i] + ' ';
                } else {
                    line = testLine;
                }
            }
            const additionalInfoHeight = 14; // For timestamp and thread info
            return lines * 14 + additionalInfoHeight + 10; // lineHeight * lines + additional padding
        }

        /**
         * 获取节点颜色根据日志级别
         * @param {string} level - 日志级别
         * @returns {string} - 颜色代码
         */
        function getColorByLevel(level) {
            if (level === 'E') return '#e57373';
            if (level === 'I') return '#64b5f6';
            return '#A1887F';
        }

        /**
         * 显示自定义右键菜单
         * @param {number} x - 鼠标点击的X坐标
         * @param {number} y - 鼠标点击的Y坐标
         */
        function showContextMenu(x, y) {
            contextMenu.style.left = `${x}px`;
            contextMenu.style.top = `${y}px`;
            contextMenu.style.display = 'block';
        }

        /**
         * 隐藏自定义右键菜单
         */
        function hideContextMenu() {
            contextMenu.style.display = 'none';
            selectedNode = { type: null, id: null, data: null, tree: null };
        }

        /**
         * 切换节点的重要性（透明度）
         */
        function toggleNodeImportance() {
            if (!selectedNode.id) return;
            markedNodes.has(selectedNode.id) ? markedNodes.delete(selectedNode.id) : markedNodes.add(selectedNode.id);
            if (selectedNode.type === 'log') {
                // 更新日志树节点的透明度
                d3.selectAll("#log-tree .node").filter(d => d.data.id === selectedNode.id)
                    .select("foreignObject")
                    .transition()
                    .duration(300)
                    .style("opacity", markedNodes.has(selectedNode.id) ? 0.5 : 1.0);
            } else if (selectedNode.type === 'thread') {
                // 更新线程树节点的透明度
                d3.selectAll("#thread-tree .node").filter(d => d.data.id === selectedNode.id)
                    .select("foreignObject")
                    .transition()
                    .duration(300)
                    .style("opacity", markedNodes.has(selectedNode.id) ? 0.5 : 1.0);
            }
            hideContextMenu();
        }

        /**
         * 生成PlantUML时序图代码并复制到剪贴板
         */
        function generateSequenceDiagram() {
            let plantUMLCode = "@startuml\nparticipant 调用\n";
            if (selectedNode.tree === 'log') {
                const logNode = selectedNode.data;
                const calls = [];

                // Traverse up the tree to get call stack
                let current = logNode;
                const stack = [];
                while (current.parent) {
                    stack.push(current);
                    current = current.parent;
                }

                for (let i = stack.length - 1; i >= 0; i--) {
                    const node = stack[i];
                    const entryMatch = node.data.content.match(/(.+) Entered\.\.\.$/);
                    if (entryMatch) {
                        const functionName = entryMatch[1];
                        plantUMLCode += `participant ${functionName}\n`;
                        plantUMLCode += `"调用" -> "${functionName}" : 调用\n`;
                    }
                }
                plantUMLCode += "@enduml";

                // 复制到剪贴板
                copyToClipboard(plantUMLCode);
                alert('PlantUML时序图代码已复制到剪贴板。');
            } else if (selectedNode.tree === 'thread') {
                // 线程级别的时序图生成（可根据需求扩展）
                alert('线程级别的时序图生成尚未实现。');
            }
            hideContextMenu();
        }

        /**
         * 复制文本到剪贴板
         * @param {string} text - 要复制的文本
         */
        function copyToClipboard(text) {
            const textarea = document.createElement('textarea');
            textarea.value = text;
            document.body.appendChild(textarea);
            textarea.select();
            try {
                document.execCommand('copy');
            } catch (err) {
                console.error('复制失败', err);
            }
            document.body.removeChild(textarea);
        }

        // 右键菜单选项事件
        toggleImportanceMenu.addEventListener('click', toggleNodeImportance);
        generateSequenceMenu.addEventListener('click', generateSequenceDiagram);

        // 点击其他地方隐藏右键菜单
        document.addEventListener('click', function(e) {
            if (e.button !== 2) { // Not right-click
                hideContextMenu();
            }
        });

        /**
         * 构建线程树结构，包括虚拟根
         * @param {string} rootId - 虚拟根ID
         * @param {Object} parents - 线程父子关系
         * @returns {Object} - 树形结构
         */
        function buildThreadTree(rootId, parents) {
            if (rootId !== 'root') {
                // 对于非虚拟根节点，构建标准树
                const node = { id: rootId, children: [] };
                for (const child in parents) {
                    if (parents[child] === rootId && child !== 'root') {
                        node.children.push(buildThreadTree(child, parents));
                    }
                }
                return node;
            } else {
                // 对于虚拟根，收集所有直接子线程
                const tree = { id: 'root', children: [] };
                for (const child in parents) {
                    if (parents[child] === 'root') {
                        tree.children.push(buildThreadTree(child, parents));
                    }
                }
                return tree;
            }
        }
    </script>
</body>
</html>