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

还需要修复下面的功能：
1、时序图生成器需要修复；
2、节点右键菜单添加“备注”功能，可以在指定节点编辑备注信息，并可以选择备注节点的颜色（只能在7种预设的颜色中选择）
3、当节点的内容超出了显示范围时，应该把超出部分隐藏
4、把输入日志部分、线程关系部分做成可折叠的
5、支持将日志跟踪、线程跟踪按图片导出，支持导入导出功能（备注也要支持导入导出）
需要反思如何把这些功能做好。

<!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 treemap = d3.tree()
                .nodeSize([100, 300]); // [垂直间距, 水平间距]

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

            // 生成树布局
            treemap(root);

            // Links
            const link = svg.selectAll(".link")
                .data(root.links())
              .enter().append("path")
                .attr("class", "link")
                .attr("d", d3.linkHorizontal()
                    .x(d => d.y)
                    .y(d => d.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);
                    }
                });

            // Node rectangles
            node.append("rect")
                .attr("width", 150)
                .attr("height", 40)
                .attr("x", -75)
                .attr("y", -20)
                .attr("rx", 5)
                .attr("ry", 5)
                .attr("fill", d => d.data.id === 'root' ? "#ffffff00" : "#4FC3F7")
                .attr("stroke", d => d.data.id === 'root' ? "none" : "#0288D1")
                .attr("stroke-width", 2)
                .attr("opacity", d => markedNodes.has(d.data.id) ? 0.5 : 1.0);

            // Node labels
            node.append("text")
                .attr("dy", 5)
                .attr("text-anchor", "middle")
                .attr("fill", "#ffffff")
                .style("pointer-events", "none")
                .text(d => d.data.id === 'root' ? '' : `线程 ${d.data.id}`);

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

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

        /**
         * 构建线程树结构，包括虚拟根
         * @param {string} rootId - 虚拟根ID
         * @param {Object} parents - 线程父子关系
         * @returns {Object} - 树形结构
         */
        function buildThreadTree(rootId, parents) {
            const node = { id: rootId, children: [] };
            for (const child in parents) {
                if (parents[child] === rootId && child !== 'root') {
                    node.children.push(buildThreadTree(child, parents));
                }
            }
            return node;
        }

        /**
         * 清除日志树
         */
        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 treemap = d3.tree()
                .nodeSize([80, 400]); // [垂直间距, 水平间距]

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

            // 生成树布局
            treemap(root);

            // Links
            const link = svg.selectAll(".link")
                .data(root.links())
              .enter().append("path")
                .attr("class", "link")
                .attr("d", d3.linkHorizontal()
                    .x(d => d.y)
                    .y(d => d.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);
                });

            // Node rectangles
            node.append("rect")
                .attr("width", 250)
                .attr("height", 60)
                .attr("x", -125)
                .attr("y", -30)
                .attr("rx", 5)
                .attr("ry", 5)
                .attr("fill", d => getColorByLevel(d.data.level))
                .attr("stroke", "#424242")
                .attr("stroke-width", 1)
                .attr("opacity", d => markedNodes.has(d.data.id) ? 0.5 : 1.0);

            // Node labels
            node.append("text")
                .attr("dy", -5)
                .attr("text-anchor", "middle")
                .attr("fill", "#ffffff")
                .style("font-weight", "bold")
                .text(d => d.data.content.split(',')[0]); // 显示主要内容

            node.append("text")
                .attr("dy", 15)
                .attr("text-anchor", "middle")
                .attr("fill", "#d1d1d1")
                .style("font-size", "10px")
                .text(d => `[${d.data.timestamp}] | 线程:${d.data.threadId}`);

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

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

        /**
         * 构建日志树结构
         * @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.replace(/ Entered\.\.\.$/, ''),
                        timestamp: log.timestamp,
                        threadId: log.threadId,
                        level: log.level,
                        children: []
                    };
                    currentNode.children.push(newNode);
                    stack.push(newNode);
                    currentNode = newNode;
                } else if (isExit) {
                    if (stack.length > 1) {
                        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;
        }

        /**
         * 格式化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} 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;
            if (markedNodes.has(selectedNode.id)) {
                markedNodes.delete(selectedNode.id);
            } else {
                markedNodes.add(selectedNode.id);
            }

            if (selectedNode.type === 'log') {
                // 更新日志树节点的透明度
                d3.selectAll("#log-tree .node").filter(d => d.data.id === selectedNode.id)
                    .select("rect")
                    .transition()
                    .duration(300)
                    .attr("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("rect")
                    .transition()
                    .duration(300)
                    .attr("opacity", markedNodes.has(selectedNode.id) ? 0.5 : 1.0);
            }
            hideContextMenu();
        }

        /**
         * 生成PlantUML时序图代码并复制到剪贴板
         */
        function generateSequenceDiagram() {
            if (!selectedNode.id) return;
            let plantUMLCode = "@startuml\n";
            if (selectedNode.tree === 'log') {
                const logNode = selectedNode.data;
                const callStack = getCallStack(logNode);

                callStack.forEach((node, index) => {
                    plantUMLCode += `participant ${sanitizeParticipantName(node.content)}\n`;
                    if (index > 0) {
                        plantUMLCode += `${sanitizeParticipantName(callStack[index - 1].content)} -> ${sanitizeParticipantName(node.content)} : 调用\n`;
                    }
                });

                plantUMLCode += "@enduml";

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

        /**
         * 获取调用栈
         * @param {Object} node - 当前节点数据
         * @returns {Array} - 调用栈数组
         */
        function getCallStack(node) {
            const stack = [];
            let current = node;
            while (current.parent) {
                stack.push(current);
                current = current.parent.data;
            }
            return stack;
        }

        /**
         * 清理和格式化参与者名称
         * @param {string} name - 原始名称
         * @returns {string} - 清理后的名称
         */
        function sanitizeParticipantName(name) {
            return name.replace(/[^a-zA-Z0-9_\-]/g, '_');
        }

        /**
         * 复制文本到剪贴板
         * @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) { // 非右键点击
                hideContextMenu();
            }
        });
    </script>
</body>
</html>