在你是一位资深的前端程序员，你将设计并生成一段即将生产环境使用（因此，你需要审慎地生成你的代码）的、完整的，并非是示例的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>

下面的代码有一个问题，点选线程关系节点后，没有显示对应的日志，且线程关系只显示了被点选的节点并无法再次展开节点
<!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;
            display: flex;
            flex-direction: column;
        }
        header {
            padding: 10px;
            background-color: #2c3e50;
            color: white;
            text-align: center;
            flex: none;
        }
        #controls {
            padding: 10px;
            background-color: #ecf0f1;
            overflow: auto;
            flex: none;
            display: flex;
            flex-direction: column;
        }
        #controls textarea {
            width: 100%;
            height: 100px;
            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;
            align-self: flex-start;
        }
        #controls button:hover {
            background-color: #3498db;
        }
        #toggle-sidebar {
            margin-top: 10px;
            padding: 5px 10px;
            font-size: 14px;
            background-color: #95a5a6;
            color: white;
            border: none;
            cursor: pointer;
            border-radius: 4px;
            align-self: flex-start;
        }
        #toggle-sidebar:hover {
            background-color: #7f8c8d;
        }
        #main {
            display: flex;
            flex: 1;
            overflow: hidden;
        }
        .container {
            position: relative;
            border: 1px solid #bdc3c7;
            overflow: auto;
            transition: width 0.3s;
        }
        .container.collapsed {
            width: 0 !important;
            min-width: 0 !important;
            border: none;
        }
        .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;
        }
        #thread-container {
            width: 30%;
            min-width: 200px;
        }
        #log-container {
            flex: 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);
        }
        /* 树节点样式 */
        .node circle {
            fill: #4FC3F7;
            stroke: #0288D1;
            stroke-width: 2px;
        }
        .node text {
            font: 12px sans-serif;
            pointer-events: none;
        }
        .link {
            fill: none;
            stroke: #ccc;
            stroke-width: 2px;
        }
    </style>
</head>
<body>
    <header>
        <h1>日志可视化工具</h1>
    </header>
    <div id="controls">
        <textarea id="log-input" placeholder="在此处粘贴日志内容..."></textarea>
        <button id="parse-button">解析并可视化</button>
        <button id="toggle-sidebar">隐藏线程关系</button>
    </div>
    <div id="main">
        <div class="container" id="thread-container" data-title="线程关系">
            <svg id="thread-tree" class="tree-container"></svg>
        </div>
        <div class="container" id="log-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 threadStartTimes = {}; // 新增存储线程启动时间

        // 存储已标记的重要节点
        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();
        });

        // 侧边栏折叠按钮
        const toggleSidebarButton = document.getElementById('toggle-sidebar');
        toggleSidebarButton.addEventListener('click', function() {
            const threadContainer = document.getElementById('thread-container');
            const logContainer = document.getElementById('log-container');
            const isCollapsed = threadContainer.classList.toggle('collapsed');
            toggleSidebarButton.textContent = isCollapsed ? '显示线程关系' : '隐藏线程关系';
            // 调整日志容器宽度
            logContainer.style.width = isCollapsed ? '100%' : 'auto';
        });

        /**
         * 解析日志文本
         * @param {string} text - 日志文本
         */
        function parseLogs(text) {
            const lines = text.split('\n').filter(line => line.trim() !== '');
            parsedLogs = {};
            threadParents = {};
            threadStartTimes = {}; // 重置线程启动时间

            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] = [];
                        // 记录线程的启动时间为其第一条日志的时间
                        threadStartTimes[threadId] = new Date(`2023-10-26T${convertTimestamp(timestamp)}Z`);
                    }
                    parsedLogs[threadId].push(log);
                }
            }

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

        /**
         * 推断线程之间的关系
         * 简化假设：一个线程的启动时间在另一个线程的启动时间之后，则视为子线程
         * @param {Array} logEntries - 所有日志条目
         */
        function inferThreadRelationships(logEntries) {
            // 获取每个线程的最早时间
            // threadStartTimes 已在 parseLogs 中赋值

            // 排序线程按开始时间
            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) {
            // 将 '10-26 10:33:23.194' 转换为 '10-26T10:33:23.194Z'
            return timestamp.replace(' ', 'T') + 'Z';
        }

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

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

            const margin = { top: 20, right: 90, bottom: 20, left: 90 },
                  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 treemap = d3.tree().size([height, width]);

            // 创建层级结构
            const root = d3.hierarchy(treeData, d => d.children);

            // 初始化所有节点为展开状态
            root.descendants().forEach(d => {
                d._children = d.children;
                if (d.depth === 0) d.children = d.children;
                else d.children = null;
            });

            // 创建缩放和拖动
            const zoom = d3.zoom()
                .scaleExtent([0.5, 2])
                .on("zoom", (event) => {
                    svg.attr("transform", event.transform);
                });
            d3.select("#thread-tree").call(zoom);

            updateThreadTree(root, svg, treemap);
        }

        /**
         * 更新线程树的显示
         * @param {Object} source - 根节点
         * @param {Object} svg - SVG元素
         * @param {Function} treemap - 树布局
         */
        function updateThreadTree(source, svg, treemap) {
            treemap(source);

            // Links
            const link = svg.selectAll(".link")
                .data(source.descendants().slice(1), d => d.data.id);

            // Enter new links
            const linkEnter = link.enter().append("path")
                .attr("class", "link")
                .attr("d", d => {
                    const o = {x: source.x0 || source.x, y: source.y0 || source.y};
                    return diagonal(o, o);
                });

            // Update links
            const linkUpdate = linkEnter.merge(link);
            linkUpdate.transition()
                .duration(500)
                .attr("d", d => diagonal(d, d.parent));

            // Remove old links
            const linkExit = link.exit().transition()
                .duration(500)
                .attr("d", d => {
                    const o = {x: source.x, y: source.y};
                    return diagonal(o, o);
                })
                .remove();

            // Nodes
            const node = svg.selectAll(".node")
                .data(source.descendants(), d => d.data.id);

            // Enter new nodes
            const nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${source.y0 || source.y},${source.x0 || source.x})`)
                .on("click", (event, d) => {
                    toggleChildren(d);
                    updateThreadTree(d, svg, treemap);
                })
                .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);
                    }
                });

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .attr("fill", d => markedNodes.has(d.data.id) ? "#FFA726" : "#4FC3F7");

            nodeEnter.append("text")
                .attr("dy", ".35em")
                .attr("x", d => d.children || d._children ? -13 : 13)
                .attr("text-anchor", d => d.children || d._children ? "end" : "start")
                .text(d => `线程 ${d.data.id}`)
                .style("fill-opacity", 1e-6);

            // Update nodes
            const nodeUpdate = nodeEnter.merge(node);

            nodeUpdate.transition()
                .duration(500)
                .attr("transform", d => `translate(${d.y},${d.x})`);

            nodeUpdate.select("circle")
                .attr("r", 10)
                .attr("fill", d => markedNodes.has(d.data.id) ? "#FFA726" : (d._children ? "#6D9DC5" : "#4FC3F7"));

            nodeUpdate.select("text")
                .style("fill-opacity", 1);

            // Remove old nodes
            const nodeExit = node.exit().transition()
                .duration(500)
                .attr("transform", d => `translate(${source.y},${source.x})`)
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Store old positions for transition
            source.x0 = source.x;
            source.y0 = source.y;

            // Tooltip显示
            svg.selectAll(".node")
                .on("mouseover", (event, d) => {
                    tooltip.transition().duration(200).style("opacity", .9);
                    tooltip.html(`线程ID: ${d.data.id}<br/>启动时间: ${formatDate(d.data.startTime)}`)
                        .style("left", (event.pageX + 10) + "px")
                        .style("top", (event.pageY - 28) + "px");
                })
                .on("mouseout", () => {
                    tooltip.transition().duration(500).style("opacity", 0);
                });
        }

        /**
         * 构建线程树结构，包括虚拟根
         * @param {string} rootId - 虚拟根ID
         * @param {Object} parents - 线程父子关系
         * @returns {Object} - 树形结构
         */
        function buildThreadTree(rootId, parents) {
            if (rootId !== 'root') {
                // 对于非虚拟根节点，构建标准树
                const node = { id: rootId, children: [], startTime: threadStartTimes[rootId] };
                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;
            }
        }

        /**
         * 对角线生成器，用于连接节点
         * @param {Object} s - 源节点
         * @param {Object} d - 目标节点
         * @returns {string} - SVG路径字符串
         */
        function diagonal(s, d) {
            const path = `M ${s.y} ${s.x}
                          C ${(s.y + d.y) / 2} ${s.x},
                            ${(s.y + d.y) / 2} ${d.x},
                            ${d.y} ${d.x}`;
            return path;
        }

        /**
         * 切换节点的子节点显示
         * @param {Object} d - 节点
         */
        function toggleChildren(d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else {
                d.children = d._children;
                d._children = null;
            }
        }

        /**
         * 格式化日期
         * @param {Date} date - Date对象
         * @returns {string} - 格式化后的日期字符串
         */
        function formatDate(date) {
            if (!(date instanceof Date) || isNaN(date)) {
                return '未知时间';
            }
            return date.toISOString().replace('T', ' ').split('.')[0];
        }

        /**
         * 清除日志树
         */
        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: 90, bottom: 20, left: 90 },
                  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 treemap = d3.tree().size([height, width]);

            // 创建层级结构
            const root = d3.hierarchy(treeData, d => d.children);

            // 初始化所有节点为展开状态
            root.descendants().forEach(d => {
                d._children = d.children;
                d.children = null;
            });

            updateLogTree(root, svg, treemap);
        }

        /**
         * 更新日志树的显示
         * @param {Object} source - 根节点
         * @param {Object} svg - SVG元素
         * @param {Function} treemap - 树布局
         */
        function updateLogTree(source, svg, treemap) {
            treemap(source);

            // Links
            const link = svg.selectAll(".link")
                .data(source.descendants().slice(1), d => d.data.id);

            // Enter new links
            const linkEnter = link.enter().append("path")
                .attr("class", "link")
                .attr("d", d => {
                    const o = {x: source.x0 || source.x, y: source.y0 || source.y};
                    return diagonal(o, o);
                });

            // Update links
            const linkUpdate = linkEnter.merge(link);
            linkUpdate.transition()
                .duration(500)
                .attr("d", d => diagonal(d, d.parent));

            // Remove old links
            const linkExit = link.exit().transition()
                .duration(500)
                .attr("d", d => {
                    const o = {x: source.x, y: source.y};
                    return diagonal(o, o);
                })
                .remove();

            // Nodes
            const node = svg.selectAll(".node")
                .data(source.descendants(), d => d.data.id);

            // Enter new nodes
            const nodeEnter = node.enter().append("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${source.y0 || source.y},${source.x0 || source.x})`)
                .on("click", (event, d) => {
                    toggleChildren(d);
                    updateLogTree(d, svg, treemap);
                })
                .on("contextmenu", (event, d) => {
                    event.preventDefault();
                    if (d.data.id !== 0) { // 排除根节点
                        selectedNode = { type: 'log', id: d.data.id, data: d.data, tree: 'log' };
                        showContextMenu(event.pageX, event.pageY);
                    }
                });

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .attr("fill", d => markedNodes.has(d.data.id) ? "#FFA726" : getColorByLevel(d.data.level));

            nodeEnter.append("text")
                .attr("dy", ".35em")
                .attr("x", d => d.children || d._children ? -13 : 13)
                .attr("text-anchor", d => d.children || d._children ? "end" : "start")
                .text(d => d.data.content.length > 20 ? d.data.content.substring(0, 20) + '...' : d.data.content)
                .style("fill-opacity", 1e-6);

            // Update nodes
            const nodeUpdate = nodeEnter.merge(node);

            nodeUpdate.transition()
                .duration(500)
                .attr("transform", d => `translate(${d.y},${d.x})`);

            nodeUpdate.select("circle")
                .attr("r", 10)
                .attr("fill", d => markedNodes.has(d.data.id) ? "#FFA726" : (d._children ? "#6D9DC5" : getColorByLevel(d.data.level)));

            nodeUpdate.select("text")
                .style("fill-opacity", 1);

            // Remove old nodes
            const nodeExit = node.exit().transition()
                .duration(500)
                .attr("transform", d => `translate(${source.y},${source.x})`)
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Store old positions for transition
            source.x0 = source.x;
            source.y0 = source.y;

            // Tooltip显示
            svg.selectAll(".node")
                .on("mouseover", (event, d) => {
                    tooltip.transition().duration(200).style("opacity", .9);
                    tooltip.html(`<strong>时间:</strong> ${d.data.timestamp}<br/>
                                  <strong>级别:</strong> ${d.data.level}<br/>
                                  <strong>内容:</strong> ${d.data.content}`)
                        .style("left", (event.pageX + 10) + "px")
                        .style("top", (event.pageY - 28) + "px");
                })
                .on("mouseout", () => {
                    tooltip.transition().duration(500).style("opacity", 0);
                });
        }

        /**
         * 构建日志树结构
         * @param {Array} logs - 日志数组
         * @returns {Object} - 树形结构
         */
        function buildLogTree(logs) {
            const root = { id: 0, content: "根", children: [], level: "I", timestamp: "" };
            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.replace(/ Entered\.\.\.$/, ''),
                        timestamp: log.timestamp,
                        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,
                        level: log.level,
                        children: []
                    };
                    currentNode.children.push(newNode);
                }
            }

            return root;
        }

        /**
         * 获取节点颜色根据日志级别
         * @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.tree === 'log') {
                // 更新日志树节点的颜色
                d3.selectAll("#log-tree .node").filter(d => d.data.id === selectedNode.id)
                    .select("circle")
                    .transition()
                    .duration(300)
                    .attr("fill", markedNodes.has(selectedNode.id) ? "#FFA726" : getColorByLevel(selectedNode.data.level));
            } else if (selectedNode.tree === 'thread') {
                // 更新线程树节点的颜色
                d3.selectAll("#thread-tree .node").filter(d => d.data.id === selectedNode.id)
                    .select("circle")
                    .transition()
                    .duration(300)
                    .attr("fill", markedNodes.has(selectedNode.id) ? "#FFA726" : (d => d._children ? "#6D9DC5" : "#4FC3F7"));
            }
            hideContextMenu();
        }

        /**
         * 生成PlantUML时序图代码并复制到剪贴板
         */
        function generateSequenceDiagram() {
            let plantUMLCode = "@startuml\n";
            if (selectedNode.tree === 'log') {
                const logNode = selectedNode.data;
                // 简单示例：创建一个参与者并显示日志内容
                plantUMLCode += `participant ${sanitizePlantUML(logNode.content.replace(/::/g, '.'))}\n`;
                plantUMLCode += `Note right of ${sanitizePlantUML(logNode.content.replace(/::/g, '.'))}: ${sanitizePlantUML(logNode.content)}\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 {Array} logs - 日志数组
         * @returns {Object} - 树形结构
         */
        function buildLogTree(logs) {
            const root = { id: 0, content: "根", children: [], level: "I", timestamp: "" };
            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.replace(/ Entered\.\.\.$/, ''),
                        timestamp: log.timestamp,
                        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,
                        level: log.level,
                        children: []
                    };
                    currentNode.children.push(newNode);
                }
            }

            return root;
        }

        /**
         * 获取节点颜色根据日志级别
         * @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 };
        }

        /**
         * 安全化PlantUML命名
         * @param {string} text - 原始文本
         * @returns {string} - 安全化后的文本
         */
        function sanitizePlantUML(text) {
            return text.replace(/[^a-zA-Z0-9_]/g, '_');
        }
    </script>
</body>
</html>