{
  "name": "WeChat-Daily-Digest-AI-Cost-Optimized",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "id": "b0efabdd-948a-4e01-b233-68cd5f1906e8",
      "name": "Run every day at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        20,
        -540
      ],
      "timezone": "Asia/Shanghai"
    },
    {
      "parameters": {
        "url": "http://host.docker.internal:5030/api/v1/chatlog",
        "sendQuery": true,
        "specifyQuery": "json",
        "jsonQuery": "={\n  \"time\": \"{{ $json.date }}~{{ $json.date }}\",\n  \"talker\": \"{{ $json.group_name }}\"\n}",
        "options": {}
      },
      "id": "7341c497-4022-40e3-b8b2-0048700ccd10",
      "name": "Fetch Yesterday's Chatlog",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        60,
        -100
      ]
    },
    {
      "parameters": {
        "jsCode": "// Node: Parse & Structure Data (Repaired)\n// Input: Raw chat log text from the previous node.\n// Output: A single, structured JSON object with correctly parsed messages.\n\nconst rawLog = $json.data;\nconst talkerName = $('ConfigureChatParameters').first().json.group_name;\n\nif (!rawLog) {\n  console.log(\"\u8f93\u5165\u6570\u636e\u4e3a\u7a7a\uff0c\u8bf7\u68c0\u67e5\u4e0a\u6e38\u8282\u70b9\u3002\");\n  return { error: \"Input data is empty or not found.\" };\n}\n\n// \u83b7\u53d6\u6628\u5929\u7684\u65e5\u671f\u4f5c\u4e3a\u6240\u6709\u6d88\u606f\u7684\u65e5\u671f\u524d\u7f00\nconst datePrefix = $('ConfigureChatParameters').first().json.date;\n\n\n// \u6309\u4e00\u4e2a\u6216\u591a\u4e2a\u7a7a\u884c\u6765\u5206\u5272\u6d88\u606f\u5757\uff0c\u8fd9\u66f4\u53ef\u9760\nconst messageBlocks = rawLog.trim().split(/\\n\\s*\\n/);\n\nconst messages = [];\nfor (const block of messageBlocks) {\n  if (block.trim() === \"\") continue;\n\n  const lines = block.trim().split('\\n');\n  const header = lines.shift(); // \u7b2c\u4e00\u884c\u662f \"\u53d1\u9001\u8005 HH:MM:SS\"\n\n  // \u6b63\u5219\u8868\u8fbe\u5f0f\uff0c\u7528\u4e8e\u4ece header \u4e2d\u6355\u83b7\u53d1\u9001\u8005\u548c\u65f6\u95f4\n  const match = header.match(/(.+) (\\d{2}:\\d{2}:\\d{2})$/);\n\n  // \u5982\u679c\u7b2c\u4e00\u884c\u4e0d\u7b26\u5408 \"\u53d1\u9001\u8005 \u65f6\u95f4\" \u7684\u683c\u5f0f\uff0c\u5219\u8df3\u8fc7\u8fd9\u4e2a\u5757\n  if (!match) {\n    console.log(\"\u8df3\u8fc7\u65e0\u6548\u7684\u6d88\u606f\u5757:\", block);\n    continue;\n  }\n\n  const senderName = match[1].trim();\n  const timeStr = match[2];\n  const content = lines.join('\\n').trim();\n\n  // \u7ec4\u5408\u65e5\u671f\u548c\u65f6\u95f4\uff0c\u521b\u5efa\u5b8c\u6574\u7684ISO\u65f6\u95f4\u6233\n  const isoTime = `${datePrefix}T${timeStr}+08:00`;\n\n  messages.push({\n    content: content,\n    contents: null,\n    sender_name: senderName,\n    talker_name: talkerName,\n    time: isoTime,\n    seq: messages.length + 1\n  });\n}\n\nconst now = new Date();\nconst result = {\n  step: \"1_structured_data\",\n  description: \"Parsed and structured data from raw text\",\n  date: $('ConfigureChatParameters').first().json.date,\n  timestamp: now.toISOString(),\n  sources: {\n    [talkerName]: {\n      message_count: messages.length,\n      messages: messages\n    }\n  },\n  statistics: {\n    total_sources: 1,\n    total_messages: messages.length,\n    sources_summary: {\n      [talkerName]: messages.length\n    }\n  }\n};\n\nconsole.log(\"\u6210\u529f\u83b7\u53d6 \" + messages.length +\" \u6761\u6d88\u606f from \" + talkerName)\n\nreturn [{ json: result }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        -100
      ],
      "id": "02d16e25-f519-4fdb-b7f1-b97edcbf4f6a",
      "name": "Parse & Structure Data"
    },
    {
      "parameters": {
        "jsCode": "// Node: Clean & Segment by Time (Repaired with Logs and Validation - Fixed)\n// Input: The structured JSON object from the \"Parse & Structure Data\" node.\n// Output: An array of time segments, with correct message counts.\n\nconst structuredData = $('Parse & Structure Data').first().json;\nconst sourceName = Object.keys(structuredData.sources)[0];\nconst allMessages = structuredData.sources[sourceName].messages;\n\nconsole.log(`\\n[blue]\u5f00\u59cb\u65f6\u95f4\u6bb5\u5206\u5272\u548c\u6e05\u6d17...[/blue]`);\nconsole.log(`[cyan]\u539f\u59cb\u6d88\u606f\u603b\u6570: ${allMessages.length} \u6761[/cyan]`);\n\nif (!allMessages || allMessages.length === 0) {\n  console.log(\"\u6ca1\u6709\u6d88\u606f\u9700\u8981\u5904\u7406\u3002\");\n  return [];\n}\n\n// 1. \u6e05\u6d17\u6d88\u606f\nconst cleanedMessages = allMessages.filter(msg => {\n  const hasSender = msg.sender_name && msg.sender_name.trim() !== \"\";\n  const hasContent = (msg.content && msg.content.trim() !== \"\") || (msg.contents && msg.contents !== null);\n  \n  // \u5f3a\u5316\u7cfb\u7edf\u6d88\u606f\u8fc7\u6ee4\u6761\u4ef6\n  const isSystemMessage = (msg.sender_name === \"\" || msg.sender_name === \"\u7cfb\u7edf\u6d88\u606f\") || \n                          (msg.content && (\n                            msg.content.includes(\"\u52a0\u5165\u4e86\u7fa4\u804a\") ||\n                            msg.content.includes(\"\u64a4\u56de\u4e86\u4e00\u6761\u6d88\u606f\") ||\n                            msg.content.includes(\"\u62cd\u4e86\u62cd\") ||\n                            msg.content.includes(\"\u4e0e\u7fa4\u91cc\u5176\u4ed6\u4eba\u90fd\u4e0d\u662f\u670b\u53cb\u5173\u7cfb\")\n                          ));\n\n  return hasSender && hasContent && !isSystemMessage;\n});\n\nconsole.log(`[cyan]\u6e05\u6d17\u540e\u6709\u6548\u6d88\u606f\u603b\u6570: ${cleanedMessages.length} \u6761 (\u8fc7\u6ee4\u6389\u7cfb\u7edf\u6d88\u606f\u7b49)[/cyan]`);\n\n// 2. \u5b9a\u4e49\u65f6\u95f4\u6bb5\nconst segmentsConfig = [\n  { name: \"\u51cc\u6668\", start_hour: 0, end_hour: 6, messages: [] },\n  { name: \"\u4e0a\u5348\", start_hour: 6, end_hour: 12, messages: [] },\n  { name: \"\u4e0b\u5348\", start_hour: 12, end_hour: 18, messages: [] },\n  { name: \"\u665a\u4e0a\", start_hour: 18, end_hour: 24, messages: [] }\n];\n\n// 3. \u5c06\u6e05\u6d17\u540e\u7684\u6d88\u606f\u653e\u5165\u65f6\u95f4\u6bb5\nfor (const message of cleanedMessages) {\n  try {\n    const msgHour = parseInt(message.time.substring(11, 13), 10);\n\n    if (isNaN(msgHour)) {\n        console.log(`[yellow]\u8b66\u544a: \u65e0\u6cd5\u89e3\u6790\u6d88\u606f\u65f6\u95f4\uff0c\u8df3\u8fc7: ${message.time}[/yellow]`);\n        continue; \n    }\n\n    let assigned = false;\n    for (const segment of segmentsConfig) {\n      if (msgHour >= segment.start_hour && msgHour < segment.end_hour) {\n        segment.messages.push(message);\n        assigned = true;\n        break;\n      }\n    }\n    if (!assigned) {\n        console.log(`[yellow]\u8b66\u544a: \u6d88\u606f\u672a\u5206\u914d\u5230\u4efb\u4f55\u65f6\u95f4\u6bb5\uff0c\u8df3\u8fc7: ${message.time} - ${message.sender_name}: ${message.content.substring(0, 50)}...[/yellow]`);\n    }\n  } catch (e) {\n    console.log(`[red]\u9519\u8bef: \u5904\u7406\u6d88\u606f\u65f6\u53d1\u751f\u5f02\u5e38: ${message.time} - ${e.message}[/red]`);\n  }\n}\n\n// 4. \u683c\u5f0f\u5316\u6700\u7ec8\u8f93\u51fa\nconst result = segmentsConfig\n  .filter(s => s.messages.length > 0)\n  .map(s => ({\n    json: {\n      name: s.name,\n      start_hour: s.start_hour,\n      end_hour: s.end_hour,\n      messages_log: s.messages.map(m => `${m.sender_name}: ${m.content}`).join('\\n---\\n'),\n      message_count: s.messages.length\n    }\n  }));\n\n// 5. \u6253\u5370\u6bcf\u4e2a\u65f6\u95f4\u6bb5\u7684\u65e5\u5fd7\nconsole.log(`\\n[blue]\u65f6\u95f4\u6bb5\u5206\u5272\u7ed3\u679c:[/blue]`);\nlet totalSegmentedMessages = 0;\nsegmentsConfig.forEach(s => {\n    if (s.messages.length > 0) {\n        console.log(`  \ud83d\udcc5 ${s.name} (${s.start_hour}-${s.end_hour}\u65f6): ${s.messages.length} \u6761\u6d88\u606f`);\n        totalSegmentedMessages += s.messages.length;\n    }\n});\n\n// 6. \u6570\u636e\u9a8c\u8bc1\nconsole.log(`\\n[blue]\u6570\u636e\u9a8c\u8bc1:[/blue]`);\nconsole.log(`  \u6e05\u6d17\u540e\u6d88\u606f\u603b\u6570: ${cleanedMessages.length} \u6761`);\nconsole.log(`  \u5206\u6bb5\u540e\u6d88\u606f\u603b\u6570: ${totalSegmentedMessages} \u6761`);\n\nif (cleanedMessages.length === totalSegmentedMessages) {\n  console.log(`[green]\u6570\u636e\u9a8c\u8bc1\u901a\u8fc7: \u6240\u6709\u6e05\u6d17\u540e\u7684\u6d88\u606f\u90fd\u5df2\u6210\u529f\u5206\u6bb5\u3002[/green]`);\n} else {\n  console.log(`[red]\u6570\u636e\u9a8c\u8bc1\u5931\u8d25: \u6e05\u6d17\u540e\u6d88\u606f (${cleanedMessages.length}) \u4e0e\u5206\u6bb5\u540e\u6d88\u606f (${totalSegmentedMessages}) \u6570\u91cf\u4e0d\u5339\u914d\u3002[/red]`);\n  // \u53ef\u4ee5\u9009\u62e9\u629b\u51fa\u9519\u8bef\u6765\u4e2d\u65ad\u5de5\u4f5c\u6d41\uff0c\u6216\u8005\u7ee7\u7eed\u6267\u884c\n  // throw new Error(\"\u6d88\u606f\u6570\u91cf\u4e0d\u5339\u914d\uff0c\u6570\u636e\u5904\u7406\u5f02\u5e38\u3002\");\n}\n\nconsole.log(`[green]\u65f6\u95f4\u6bb5\u5206\u5272\u548c\u6e05\u6d17\u5b8c\u6210[/green]`);\n\nreturn result;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        -100
      ],
      "id": "b9d5acd4-0559-47af-8419-dae86fb52fc7",
      "name": "Clean & Segment by Time"
    },
    {
      "parameters": {
        "content": "## \u7ed3\u6784\u5316\u521d\u59cb\u6570\u636e\n \n\u5929\u6570\u636e\u83b7\u53d6\u4e0e\u9884\u5904\u7406\uff08\u5305\u542b\u89e3\u6790\u3001\u6e05\u6d17\u3001\u5206\u6bb5\u3001\u94fe\u63a5\u63d0\u53d6\u3001\u6d3b\u8dc3\u7528\u6237\u7edf\u8ba1\u3001\u6d88\u606f\u6837\u672c\u51c6\u5907\uff09",
        "height": 260,
        "width": 860,
        "color": 5
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -200
      ],
      "id": "00d281ec-7a40-4921-a4e4-a034d921d47e",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## \u804a\u5929\u6570\u636e\u65f6\u95f4\u5206\u6bb5\n",
        "height": 260,
        "color": 2
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        860,
        -200
      ],
      "id": "3cef8b1c-c532-45c4-8c12-b28340a07cfc",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## \u8bdd\u9898\u5206\u6790\n   * \u5206\u800c\u6cbb\u4e4b: \u5c06\u957f\u65e5\u5fd7\u5206\u5272\u6210\u5c0f\u5757\uff0c\u9010\u4e2a\u5904\u7406\u3002\n   * \u6210\u672c\u63a7\u5236: \u5728\u7c97\u52a0\u5de5\u9636\u6bb5\u4f7f\u7528\u5ec9\u4ef7\u6a21\u578b\u3002\n   * \u7ed3\u6784\u5316\u8f93\u51fa: \u5f3a\u5236 AI \u8fd4\u56de JSON \u683c\u5f0f\uff0c\u4fbf\u4e8e\u540e\u7eed\u5904\u7406\u3002\n   * \u8bdd\u9898\u805a\u5408: \u5c06\u6240\u6709\u65f6\u95f4\u6bb5\u7684\u8bdd\u9898\u5408\u5e76\uff0c\u4e3a\u6700\u7ec8\u62a5\u544a\u505a\u51c6\u5907\u3002\n",
        "height": 420,
        "width": 1140,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        120
      ],
      "id": "ae96b3b5-25af-4379-b108-b83905e1c5b8",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "batchSize": 1,
        "options": {}
      },
      "id": "65cd41dc-69c0-47bb-b334-299a04eafc44",
      "name": "Split in Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 2,
      "position": [
        20,
        260
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=\u4f60\u662f\u4e13\u4e1a\u7684\u804a\u5929\u8bb0\u5f55\u5206\u6790\u4e13\u5bb6\u3002\u4f60\u7684\u4efb\u52a1\u662f\u5206\u6790\u7ed9\u5b9a\u7684\u804a\u5929\u8bb0\u5f55\u7247\u6bb5\uff0c\u5e76\u4e25\u683c\u6309\u7167\u8981\u6c42\u63d0\u53d6\u6838\u5fc3\u8bdd\u9898\u3002\n\n### \u6838\u5fc3\u5206\u6790\u539f\u5219 (\u5fc5\u987b\u4e25\u683c\u9075\u5b88)\n\n1.  **\u8bdd\u9898\u8bc6\u522b\u6807\u51c6**:\n    *   **\u6570\u91cf\u95e8\u69db**: \u81f3\u5c11\u9700\u89813\u6761\u76f8\u5173\u7684\u6d88\u606f\u624d\u80fd\u6784\u6210\u4e00\u4e2a\u72ec\u7acb\u7684\u8bdd\u9898\u3002\n    *   **\u5185\u5bb9\u4ef7\u503c**: \u8bdd\u9898\u5185\u5bb9\u5fc5\u987b\u6709\u660e\u786e\u7684\u8ba8\u8bba\u4e3b\u9898\u548c\u4ef7\u503c\u3002\n    *   **\u65e0\u6548\u5185\u5bb9\u8fc7\u6ee4**: \u4e25\u7981\u5c06\u65e0\u610f\u4e49\u7684\u95ee\u5019\uff08\u5982\u201c\u65e9\u201d\u3001\u201c\u5728\u5417\u201d\uff09\u3001\u8868\u60c5\u7b26\u53f7\u3001\u6216\u7b80\u5355\u7684\u786e\u8ba4\uff08\u5982\u201c\u597d\u7684\u201d\u3001\u201c\u6536\u5230\u201d\uff09\u4f5c\u4e3a\u72ec\u7acb\u8bdd\u9898\u3002\n\n2.  **\u8bdd\u9898\u8fb9\u754c\u5224\u65ad**:\n    *   **\u4e3b\u9898\u5ef6\u7eed**: \u5982\u679c\u6709\u65b0\u6210\u5458\u52a0\u5165\u8ba8\u8bba\uff0c\u4f46\u8ba8\u8bba\u7684\u8fd8\u662f\u540c\u4e00\u4e2a\u6838\u5fc3\u4e3b\u9898\uff0c\u8fd9\u5c5e\u4e8e**\u540c\u4e00\u8bdd\u9898**\u3002\n    *   **\u4e3b\u9898\u8f6c\u53d8**: \u5f53\u8ba8\u8bba\u7684\u7126\u70b9\u53d1\u751f\u660e\u663e\u3001\u4e0d\u76f8\u5173\u7684\u8f6c\u53d8\u65f6\uff0c\u5fc5\u987b\u521b\u5efa**\u65b0\u8bdd\u9898**\u3002\n    *   **\u65f6\u95f4\u95f4\u9694**: \u5982\u679c\u4e24\u6761\u6d88\u606f\u4e4b\u95f4\u6ca1\u6709\u76f4\u63a5\u5173\u8054\uff0c\u4e14\u65f6\u95f4\u95f4\u9694\u8d85\u8fc7 **30\u5206\u949f**\uff0c\u5e94\u89c6\u4e3a**\u65b0\u8bdd\u9898**\u3002\n\n### \u5185\u5bb9\u751f\u6210\u8981\u6c42\n\n1.  **\u8bdd\u9898\u6807\u9898 (`topic_title`)**: \u751f\u6210\u4e00\u4e2a\u7b80\u6d01\u3001\u7cbe\u51c6\u3001\u80fd\u6982\u62ec\u8bdd\u9898\u6838\u5fc3\u7684\u6807\u9898\u3002\n2.  **\u8bdd\u9898\u63cf\u8ff0 (`topic_description`)**:\n    *   **\u98ce\u683c**: \u4f7f\u7528\u751f\u52a8\u3001\u63a5\u5730\u6c14\u3001\u7565\u5e26\u201c\u9a9a\u8bdd\u201d\u98ce\u683c\u7684\u5e74\u8f7b\u4eba\u7f51\u7edc\u7528\u8bed\u3002\n    *   **\u5185\u5bb9**: \u63cf\u8ff0\u5fc5\u987b\u51c6\u786e\u53cd\u6620\u8ba8\u8bba\u7684\u5b9e\u9645\u5185\u5bb9\u3001\u5173\u952e\u4fe1\u606f\u548c\u4e3b\u8981\u89c2\u70b9\u3002\n    *   **\u957f\u5ea6**: \u4e25\u683c\u63a7\u5236\u5728 100 \u5230 150 \u5b57\u4e4b\u95f4\u3002\n\n### \u7edd\u5bf9\u8f93\u51fa\u8981\u6c42 (\u6700\u91cd\u8981)\n\n1.  **\u7eafJSON**: **\u53ea\u5141\u8bb8**\u8f93\u51fa\u6709\u6548\u7684JSON\u5bf9\u8c61\u3002\n2.  **\u7981\u6b62Markdown**: **\u4e25\u7981**\u5728\u8f93\u51fa\u7684\u4efb\u4f55\u4f4d\u7f6e\u4f7f\u7528 ` ```json ` \u6216 ` ``` ` \u8fdb\u884c\u5305\u88f9\u3002\n3.  **\u7981\u6b62\u89e3\u91ca**: **\u4e25\u7981**\u5728JSON\u5bf9\u8c61\u524d\u540e\u6dfb\u52a0\u4efb\u4f55\u5f62\u5f0f\u7684\u89e3\u91ca\u6027\u6587\u5b57\u6216\u6ce8\u91ca\u3002\n4.  **\u7fa4\u7ec4\u540d**: {{ $('ConfigureChatParameters').item.json.group_name }}\n\n---\n### \u8f93\u51faJSON\u683c\u5f0f (\u4e25\u683c\u9075\u5b88\u6b64\u7ed3\u6784)\n\n1. \u5fc5\u987b\u8fd4\u56de\u6709\u6548\u7684 JSON \u683c\u5f0f\n2. \u4e0d\u8981\u5305\u542b\u4efb\u4f55\u89e3\u91ca\u6587\u5b57\u6216 markdown \u683c\u5f0f\n3. \u4e0d\u8981\u4f7f\u7528 ```json``` \u4ee3\u7801\u5757\n4. \u4e25\u683c\u6309\u7167\u4ee5\u4e0b\u683c\u5f0f\u8fd4\u56de\n\nJSON \u683c\u5f0f\u793a\u4f8b\uff1a\n{\n  \"analysis_id\": \"\u7fa4\u7ec4\u540d_\u65e5\u671f_\u65f6\u95f4\u6bb5_analysis\",\n  \"meta\": {\n    \"source\": \"\u7fa4\u7ec4\u540d\",\n    \"date\": \"\u65e5\u671f\", \n    \"segment\": \"\u65f6\u95f4\u6bb5\",\n    \"total_messages\": 50\n  },\n  \"topics\": [\n    {\n      \"topic_id\": \"\u8bdd\u98981\u7684ID\",\n      \"topic_title\": \"\u8bdd\u98981\u7684\u6807\u9898\",\n      \"topic_description\": \"\u8bdd\u98981\u7684\u8be6\u7ec6\u63cf\u8ff0\uff0c\u5305\u542b\u5173\u952e\u8bcd\u548c\u4e3b\u8981\u5185\u5bb9\uff0c\u81f3\u5c1150\u5b57\",\n      \"is_off_topic\": false,\n      \"topic_start_time\": \"\u8bdd\u9898\u5f00\u59cb\u65f6\u95f4 (ISO\u683c\u5f0f\uff0c\u4f8b\u5982: 2023-07-03T14:30:00+08:00)\",\n      \"topic_end_time\": \"\u8bdd\u9898\u7ed3\u675f\u65f6\u95f4 (ISO\u683c\u5f0f\uff0c\u4f8b\u5982: 2023-07-03T15:00:00+08:00)\",\n      \"message_seqs\": [\"\u6784\u6210\u8be5\u8bdd\u9898\u7684\u6240\u6709\u6d88\u606f\u7684seq\u5217\u8868\uff0c\u4f8b\u5982: 1, 5, 8\"]\n    }\n  ]\n}\n\n\u91cd\u8981\uff1a\u53ea\u8fd4\u56de JSON\uff0c\u4e0d\u8981\u4efb\u4f55\u5176\u4ed6\u5185\u5bb9\u3002\n\n---\n### \u5f85\u5206\u6790\u7684\u804a\u5929\u8bb0\u5f55\n\n{{ JSON.stringify($json) }}",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        560,
        240
      ],
      "id": "0c08e2ee-bb11-45c9-80ee-836b061bb6ef",
      "name": "Extract Topics (Low Cost AI)"
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"type\": \"object\",\n  \"required\": [\"analysis_id\", \"meta\", \"topics\"],\n  \"properties\": {\n    \"analysis_id\": {\n      \"type\": \"string\",\n      \"description\": \"\u552f\u4e00\u5206\u6790\u6807\u8bc6\u7b26\uff0c\u683c\u5f0f\u4e3a\u7fa4\u7ec4\u540d_\u65e5\u671f_\u65f6\u95f4\u6bb5_analysis\",\n      \"pattern\": \"^.+_.+_.+_analysis$\"\n    },\n    \"meta\": {\n      \"type\": \"object\",\n      \"required\": [\"source\", \"date\", \"segment\", \"total_messages\"],\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\",\n          \"description\": \"\u6570\u636e\u6765\u6e90\u7684\u7fa4\u7ec4\u540d\u79f0\"\n        },\n        \"date\": {\n          \"type\": \"string\",\n          \"description\": \"\u5206\u6790\u65e5\u671f\",\n          \"format\": \"date\"\n        },\n        \"segment\": {\n          \"type\": \"string\",\n          \"description\": \"\u65f6\u95f4\u6bb5\u6807\u8bc6\"\n        },\n        \"total_messages\": {\n          \"type\": \"integer\",\n          \"description\": \"\u6d88\u606f\u603b\u6570\u7edf\u8ba1\",\n          \"minimum\": 0\n        }\n      }\n    },\n    \"topics\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"required\": [\"topic_id\", \"topic_title\", \"topic_description\", \"is_off_topic\", \"topic_start_time\", \"topic_end_time\", \"message_seqs\"],\n        \"properties\": {\n          \"topic_id\": {\n            \"type\": \"string\",\n            \"description\": \"\u8bdd\u9898\u552f\u4e00\u6807\u8bc6\u7b26\"\n          },\n          \"topic_title\": {\n            \"type\": \"string\",\n            \"description\": \"\u8bdd\u9898\u6807\u9898\"\n          },\n          \"topic_description\": {\n            \"type\": \"string\",\n            \"description\": \"\u8bdd\u9898\u8be6\u7ec6\u63cf\u8ff0\uff0c\u5305\u542b\u5173\u952e\u8bcd\u548c\u4e3b\u8981\u5185\u5bb9\uff0c\u81f3\u5c1150\u5b57\",\n            \"minLength\": 50\n          },\n          \"is_off_topic\": {\n            \"type\": \"boolean\",\n            \"description\": \"\u6807\u8bb0\u662f\u5426\u504f\u79bb\u4e3b\u9898\"\n          },\n          \"topic_start_time\": {\n            \"type\": \"string\",\n            \"description\": \"\u8bdd\u9898\u5f00\u59cb\u65f6\u95f4\",\n            \"format\": \"date-time\"\n          },\n          \"topic_end_time\": {\n            \"type\": \"string\",\n            \"description\": \"\u8bdd\u9898\u7ed3\u675f\u65f6\u95f4\",\n            \"format\": \"date-time\"\n          },\n          \"message_seqs\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\",\n              \"description\": \"\u6d88\u606f\u5e8f\u5217\u53f7\"\n            },\n            \"minItems\": 1\n          }\n        }\n      },\n      \"minItems\": 1\n    }\n  }\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        720,
        420
      ],
      "id": "3c1ff1c6-50d3-4b17-a925-56a188f4ba44",
      "name": "Structured Output Parser"
    },
    {
      "parameters": {
        "jsCode": "// Author: Gemini\n// Date: 2025-07-04\n// Description: This version corrects the data access path within the .map() function.\n// It now correctly reads `item.json.output` based on the actual output from the LLM node,\n// resolving the 'Cannot read properties of undefined' error.\n\n/**\n * Robustly maps a segment string to a Chinese time segment name.\n * @param {string} segmentInput - The segment string, e.g., \"19:30-20:00\" or \"\u508d\u665a\".\n * @returns {string} The corresponding Chinese segment name, e.g., \"\u665a\u4e0a\".\n */\nfunction mapTimeToChineseSegment(segmentInput) {\n  if (!segmentInput || typeof segmentInput !== 'string') {\n    return \"\u672a\u77e5\u65f6\u6bb5\";\n  }\n  const startHour = parseInt(segmentInput.split(':')[0], 10);\n  if (isNaN(startHour)) {\n    return segmentInput;\n  }\n  if (startHour >= 0 && startHour < 6) return \"\u6df1\u591c\";\n  if (startHour >= 6 && startHour < 9) return \"\u65e9\u6668\";\n  if (startHour >= 9 && startHour < 12) return \"\u4e0a\u5348\";\n  if (startHour >= 12 && startHour < 14) return \"\u4e2d\u5348\";\n  if (startHour >= 14 && startHour < 18) return \"\u4e0b\u5348\";\n  if (startHour >= 18 && startHour < 22) return \"\u665a\u4e0a\";\n  if (startHour >= 22 && startHour <= 23) return \"\u6df1\u591c\";\n  return \"\u672a\u77e5\u65f6\u6bb5\";\n}\n\n// --- Main Logic (Rebuilt) ---\n\n// **THE ONLY FIX IS ON THIS LINE**\n// Correctly access the data from the actual LLM output structure.\nconst analysisResults = items.map(item => item.json.output);\n\nif (!analysisResults || analysisResults.length === 0) {\n  return { json: { error: \"Input data is empty or invalid.\" } };\n}\n\nconst all_topics = [];\nconst sources_info = {};\nconst segments_info = {};\nlet topic_counter = 1;\n\n// A single, robust loop to process all inputs and aggregate correctly.\nfor (const result of analysisResults) {\n  if (!result || !result.topics || !Array.isArray(result.topics)) {\n    continue;\n  }\n\n  const source_name = result.meta.source;\n  const chinese_segment_name = mapTimeToChineseSegment(result.meta.segment);\n  const created_at = result.created_at || new Date().toISOString();\n  const segment_key = `${source_name}_${chinese_segment_name}`;\n\n  // --- Step 1: Aggregate Metadata in Real-time ---\n  if (!segments_info[segment_key]) {\n    segments_info[segment_key] = {\n      source_name: source_name,\n      segment_name: chinese_segment_name,\n      analysis_id: `${source_name}_${result.meta.date}_${chinese_segment_name}_analysis`,\n      created_at: created_at,\n      meta: {\n        source: source_name,\n        date: result.meta.date,\n        segment: chinese_segment_name,\n        total_messages: 0,\n      },\n      topics_count: 0,\n    };\n  }\n  segments_info[segment_key].topics_count += result.topics.length;\n  segments_info[segment_key].meta.total_messages += (result.meta.total_messages || 0);\n\n  // --- Step 2: Process Topics and Link to the *Correct* Aggregated Metadata ---\n  for (const topic of result.topics) {\n    const topic_seq = topic.topic_seq || [];\n    const message_count = topic_seq.length;\n\n    const topic_data = {\n      physical_id: `topic_${String(topic_counter).padStart(3, '0')}`,\n      original_topic_id: topic.topic_id,\n      topic_title: topic.topic_title,\n      topic_description: topic.topic_description,\n      is_off_topic: topic.is_off_topic,\n      topic_seq: topic_seq,\n      message_count: (topic.message_seqs || []).length,\n      chat_count:  (topic.message_seqs || []).length,\n      topic_start_time: topic.topic_start_time || new Date().toISOString(),\n      topic_end_time: topic.topic_end_time || new Date().toISOString(),\n      source_name: source_name,\n      segment_name: chinese_segment_name,\n      analysis_id: segments_info[segment_key].analysis_id,\n      created_at: created_at,\n      analysis_meta: segments_info[segment_key].meta,\n    };\n\n    all_topics.push(topic_data);\n    topic_counter++;\n  }\n}\n\n// --- Step 3: Final Aggregation for sources_metadata (can be done after the loop) ---\nall_topics.forEach(topic => {\n    if (!sources_info[topic.source_name]) {\n        sources_info[topic.source_name] = { source_name: topic.source_name, segments: [], total_topics: 0 };\n    }\n    if (!sources_info[topic.source_name].segments.includes(topic.segment_name)) {\n        sources_info[topic.source_name].segments.push(topic.segment_name);\n    }\n});\nObject.keys(sources_info).forEach(sourceName => {\n    sources_info[sourceName].total_topics = all_topics.filter(t => t.source_name === sourceName).length;\n});\n\n\n// --- Final Assembly ---\nconst on_topic_count = all_topics.filter(t => !t.is_off_topic).length;\nconst off_topic_count = all_topics.filter(t => t.is_off_topic).length;\nconst total_message_seqs = all_topics.reduce((sum, t) => sum + t.message_count, 0);\nconst total_original_messages = Object.values(segments_info).reduce((sum, s) => sum + s.meta.total_messages, 0);\nconst topics_by_source = Object.entries(sources_info).reduce((acc, [key, value]) => {\n  acc[key] = value.total_topics;\n  return acc;\n}, {});\n\nconst physical_merged = {\n  step: \"2_topic_merged\",\n  description: \"\u65f6\u95f4\u5206\u6bb5\u5206\u6790\u540e\u7684\u6240\u6709\u8bdd\u9898\u7269\u7406\u5408\u5e76\uff0c\u672a\u8fdb\u884c\u8bed\u4e49\u5408\u5e76\",\n  merge_timestamp: new Date().toISOString(),\n  merge_date: analysisResults[0]?.meta?.date || new Date().toISOString().split('T')[0],\n  merge_type: \"physical\",\n  total_topics: all_topics.length,\n  total_sources: Object.keys(sources_info).length,\n  total_segments: Object.keys(segments_info).length,\n  all_topics: all_topics,\n  sources_metadata: sources_info,\n  segments_metadata: segments_info,\n  statistics: {\n    on_topic_count,\n    off_topic_count,\n    total_message_seqs,\n    total_original_messages,\n    topics_by_source\n  }\n};\n\nreturn { json: physical_merged };"
      },
      "id": "1ea63cbf-cd4f-4f95-8876-99fbae4b4bd8",
      "name": "Merge & Deduplicate Topics",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        940,
        280
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=\u4f60\u662f\u804a\u5929\u8bdd\u9898\u667a\u80fd\u5408\u5e76\u4e13\u5bb6\u3002\u4f60\u7684\u6838\u5fc3\u4efb\u52a1\u662f\u5206\u6790\u5df2\u7269\u7406\u5408\u5e76\u7684\u8bdd\u9898\u5217\u8868\uff0c\u627e\u51fa\u8bed\u4e49\u76f8\u5173\u7684\u8bdd\u9898\uff0c\u5e76\u8f93\u51fa\u4e00\u4e2a\u5305\u542b\u5408\u5e76\u5173\u7cfb\u548cAI\u603b\u7ed3\u5185\u5bb9\u7684\u65b0JSON\u3002\n\n### \u6838\u5fc3\u5408\u5e76\u539f\u5219 (\u5fc5\u987b\u4e25\u683c\u9075\u5b88)\n\n1.  **\u8bed\u4e49\u4e3a\u738b**: \u5fc5\u987b\u57fa\u4e8e\u8bdd\u9898\u63cf\u8ff0 (`topic_description`) \u7684**\u6df1\u5c42\u8bed\u4e49**\u8fdb\u884c\u5408\u5e76\uff0c\u800c\u4e0d\u662f\u7b80\u5355\u7684\u5173\u952e\u8bcd\u5339\u914d\u3002\u4f8b\u5982\uff0c\u201cClaude\u8fde\u4e0d\u4e0a\u201d\u548c\u201c\u6211\u7684Claude\u4eca\u5929\u54cd\u5e94\u5f88\u6162\u201d\u5e94\u8be5\u5408\u5e76\uff0c\u4f46\u201cClaude\u4f7f\u7528\u6280\u5de7\u201d\u548c\u201cGemini\u4f7f\u7528\u6280\u5de7\u201d\u5219**\u7edd\u5bf9\u4e0d\u80fd**\u5408\u5e76\u3002\n2. **\u5408\u5e76\u6761\u4ef6**\uff1a\n   - \u8bed\u4e49\u76f8\u4f3c\u5ea6 > 0.7\n   - \u53c2\u4e0e\u8005\u91cd\u53e0\u5ea6 > 0.3\n   - \u65f6\u95f4\u8de8\u5ea6\u5408\u7406\uff08\u4e0d\u8d85\u8fc74\u5c0f\u65f6\uff09\n3.  **\u4fdd\u5b88\u7b56\u7565**: \n    - \u5b81\u53ef\u4fdd\u7559\u66f4\u591a\u72ec\u7acb\u7684\u3001\u6709\u4ef7\u503c\u7684\u5c0f\u8bdd\u9898\uff0c\u4e5f\u7edd\u4e0d\u9519\u8bef\u5730\u5c06\u4e0d\u76f8\u5173\u7684\u8bdd\u9898\u5408\u5e76\u6210\u4e00\u4e2a\u6a21\u7cca\u7684\u5927\u6742\u70e9\u3002\n    - \u5982\u679c\u4f60\u6ca1\u670970%\u4ee5\u4e0a\u7684\u628a\u63e1\uff0c\u5c31\u9009\u62e9\u4e0d\u5408\u5e76\u3002\n4.  **\u8d28\u91cf\u63d0\u5347**: \n    - \u5408\u5e76\u540e\u8bdd\u9898\u5e94\u8be5\u6bd4\u539f\u8bdd\u9898\u66f4\u6709\u4ef7\u503c\uff0c\u5176\u6807\u9898\u548c\u63cf\u8ff0\u5fc5\u987b\u6bd4\u539f\u59cb\u8bdd\u9898\u66f4\u5177\u4fe1\u606f\u91cf\u3001\u66f4\u7cbe\u70bc\u3001\u66f4\u80fd\u4f53\u73b0\u8ba8\u8bba\u7684\u7cbe\u534e\n    - \u907f\u514d\u4e3a\u4e86\u5408\u5e76\u800c\u5408\u5e76\uff0c\u4fdd\u6301\u8bdd\u9898\u7684\u72ec\u7acb\u6027\n    - \u5408\u5e76\u6570\u91cf\u63a7\u5236\u57282-4\u4e2a\u8bdd\u9898\u4e4b\u95f4\n\n### \u5185\u5bb9\u751f\u6210\u8981\u6c42\n\n1.  **\u5408\u5e76\u540e\u6807\u9898 (`merged_title`)**: \u751f\u6210\u4e00\u4e2a\u5168\u65b0\u7684\u3001\u9ad8\u5ea6\u6982\u62ec\u7684\u3001\u80fd\u4f53\u73b0\u8ba8\u8bba\u6838\u5fc3\u7684\u7cbe\u786e\u6807\u9898\u3002\n2.  **\u5408\u5e76\u540e\u63cf\u8ff0 (`merged_description`)**: **\u4e25\u683c\u57fa\u4e8e**\u6240\u6709\u88ab\u5408\u5e76\u8bdd\u9898\u7684**\u539f\u59cb\u5185\u5bb9**\u8fdb\u884c\u603b\u7ed3\u3002\u5fc5\u987b\u6e05\u6670\u5730\u8bf4\u660e\u8ba8\u8bba\u7684\u5b8c\u6574\u8109\u7edc\u3001\u6838\u5fc3\u89c2\u70b9\u3001\u4e3b\u8981\u95ee\u9898\u548c\u7ed3\u8bba\u3002**\u4e25\u7981**\u7f16\u9020\u3001\u81c6\u6d4b\u6216\u6dfb\u52a0\u4efb\u4f55\u539f\u59cb\u5bf9\u8bdd\u4e2d\u4e0d\u5b58\u5728\u7684\u4fe1\u606f\u3002**\u4e25\u7981**\u51fa\u73b0\u201c\u5408\u5e76\u4e86X\u4e2a\u8bdd\u9898\u201d\u8fd9\u79cd\u7a7a\u6d1e\u7684\u683c\u5f0f\u5316\u63cf\u8ff0\u3002\n3.  **\u8bdd\u9898\u6807\u7b7e (`topic_tags`)**: \u5229\u7528\u4f60\u6700\u65b0\u7684\u77e5\u8bc6\u5e93\uff0c\u4e3a\u6bcf\u4e2a\u5408\u5e76\u540e\u7684\u8bdd\u9898\u751f\u62102-4\u4e2a\u4e0e\u65f6\u4ff1\u8fdb\u7684\u3001\u4e1a\u5185\u8ba4\u53ef\u7684\u6807\u7b7e\u3002\u4f8b\u5982\uff1a`RAG`, `Fine-tuning`, `AI Agent`, `Claude 3.5 Sonnet`, `n8n` \u7b49\u3002\n\n### \u7edd\u5bf9\u8f93\u51fa\u8981\u6c42 (\u6700\u91cd\u8981)\n\n1.  **\u7eafJSON**: **\u53ea\u5141\u8bb8**\u8f93\u51fa\u6709\u6548\u7684JSON\u5bf9\u8c61\u3002\n2.  **\u7981\u6b62Markdown**: **\u4e25\u7981**\u5728\u8f93\u51fa\u7684\u4efb\u4f55\u4f4d\u7f6e\u4f7f\u7528 ` ```json ` \u6216 ` ``` ` \u8fdb\u884c\u5305\u88f9\u3002\n3.  **\u7981\u6b62\u89e3\u91ca**: **\u4e25\u7981**\u5728JSON\u5bf9\u8c61\u524d\u540e\u6dfb\u52a0\u4efb\u4f55\u5f62\u5f0f\u7684\u89e3\u91ca\u6027\u6587\u5b57\u6216\u6ce8\u91ca\u3002\n\n---\n### \u8f93\u51faJSON\u683c\u5f0f (\u4e25\u683c\u9075\u5b88\u6b64\u7ed3\u6784)\n\n\u4f60\u7684\u54cd\u5e94\u5fc5\u987b\u662f\u4e00\u4e2a\u6709\u6548\u7684 JSON \u5bf9\u8c61\uff0c\u4e14\u53ea\u80fd\u662f JSON \u5bf9\u8c61\u3002\u4e0d\u8981\u5305\u542b\u4efb\u4f55\u989d\u5916\u7684\u6587\u672c\u3001\u89e3\u91ca\u6216 Markdown \u4ee3\u7801\u5757\uff08\u4f8b\u5982\uff0c```json\uff09\u3002\n\n```json\n{\n  \"merged_topics\": [\n    {\n      \"semantic_topic_id\": \"merged_001\",\n      \"merged_title\": \"\u57fa\u4e8e\u8bed\u4e49\u5206\u6790\u7684\u7cbe\u786e\u6807\u9898\",\n      \"merged_description\": \"\u7efc\u5408\u63cf\u8ff0\uff1a\u8be6\u7ec6\u8bf4\u660e\u5408\u5e76\u540e\u8bdd\u9898\u7684\u5b8c\u6574\u5185\u5bb9\u8109\u7edc\u3001\u6838\u5fc3\u89c2\u70b9\u3001\u8ba8\u8bba\u8303\u56f4\u548c\u4e3b\u8981\u7ed3\u8bba\u3002\",\n      \"is_off_topic\": false,\n      \"semantic_category\": \"\u8ba8\u8bba\u7c7b\u578b (\u4f8b\u5982: '\u95ee\u9898\u6c42\u52a9', '\u7ecf\u9a8c\u5206\u4eab', '\u6280\u672f\u63a2\u8ba8')\",\n      \"topic_tags\": [\"AI\u751f\u6210\u6807\u7b7e1\", \"AI\u751f\u6210\u6807\u7b7e2\"],\n      \"merged_from\": [\n        {\n          \"physical_id\": \"\u88ab\u5408\u5e76\u7684\u539f\u59cb\u8bdd\u9898ID (\u4f8b\u5982: topic_001)\",\n          \"original_title\": \"\u88ab\u5408\u5e76\u7684\u539f\u59cb\u8bdd\u9898\u7684\u6807\u9898\"\n        }\n      ],\n      \"relevance_score\": 0.85,\n      \"confidence_level\": \"high\"\n    }\n  ],\n  \"merge_metadata\": {\n    \"merge_algorithm\": \"deep_semantic_analysis\",\n    \"total_merges_performed\": 1,\n    \"compression_achieved\": \"50%\"\n  }\n}\n```\n\n---\n### \u5f85\u5206\u6790\u7684\u7269\u7406\u5408\u5e76\u540e\u7684\u8bdd\u9898\u6570\u636e\n\n{{ JSON.stringify($json) }}",
        "hasOutputParser": true,
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1220,
        80
      ],
      "id": "f091a273-6475-40a5-b7df-99d4fb12c1f1",
      "name": "AI Semantic Merge"
    },
    {
      "parameters": {
        "jsonSchemaExample": "{\n  \"merged_topics\": [\n    {\n      \"semantic_topic_id\": \"merged_001\",\n      \"merged_title\": \"\u57fa\u4e8e\u8bed\u4e49\u5206\u6790\u7684\u7cbe\u786e\u6807\u9898\",\n      \"merged_description\": \"\u7efc\u5408\u63cf\u8ff0\uff1a\u8be6\u7ec6\u8bf4\u660e\u5408\u5e76\u540e\u8bdd\u9898\u7684\u5b8c\u6574\u5185\u5bb9\u8109\u7edc\u3001\u6838\u5fc3\u89c2\u70b9\u3001\u8ba8\u8bba\u8303\u56f4\u548c\u4e3b\u8981\u7ed3\u8bba\u3002\",\n      \"is_off_topic\": false,\n      \"semantic_category\": \"\u8ba8\u8bba\u7c7b\u578b (\u4f8b\u5982: '\u95ee\u9898\u6c42\u52a9', '\u7ecf\u9a8c\u5206\u4eab', '\u6280\u672f\u63a2\u8ba8')\",\n      \"topic_tags\": [\"AI\u751f\u6210\u6807\u7b7e1\", \"AI\u751f\u6210\u6807\u7b7e2\"],\n      \"merged_from\": [\n        {\n          \"physical_id\": \"\u88ab\u5408\u5e76\u7684\u539f\u59cb\u8bdd\u9898ID (\u4f8b\u5982: topic_001)\",\n          \"original_title\": \"\u88ab\u5408\u5e76\u7684\u539f\u59cb\u8bdd\u9898\u7684\u6807\u9898\"\n        }\n      ],\n      \"relevance_score\": 0.85,\n      \"confidence_level\": \"high\"\n    }\n  ],\n  \"merge_metadata\": {\n    \"merge_algorithm\": \"deep_semantic_analysis\",\n    \"total_merges_performed\": 1,\n    \"compression_achieved\": \"50%\"\n  }\n}"
      },
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        1420,
        400
      ],
      "id": "00c38755-66ab-4e1d-99f4-e096f8c1e2d7",
      "name": "Structured Output Parser1"
    },
    {
      "parameters": {
        "content": "## \u667a\u80fd\u8bdd\u9898\u5f52\u5e76\n\n- \u5c06\u76f8\u4f3c\u6216\u76f8\u5173\u7684\u8bdd\u9898\u8fdb\u884c\u667a\u80fd\u5408\u5e76\uff0c\u5f62\u6210\u66f4\u5b8f\u89c2\u3001\u6709\u610f\u4e49\u7684\u8bdd\u9898\u3002\n- \u5c06\u6240\u6709\u5904\u7406\u8fc7\u7684\u8bdd\u9898\u6570\u636e\u7ec4\u88c5\u6210\u6700\u7ec8\u62a5\u544a\u6240\u9700\u7684\u7ed3\u6784\u6216\u5185\u5bb9\u3002\n",
        "height": 740,
        "width": 620,
        "color": 4
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1160,
        -200
      ],
      "id": "dc05f681-cd26-4761-8823-41101dd488f9",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "jsCode": "// Author: Gemini\n// Date: 2025-07-05\n// Description: This version integrates all_links, active_users, and raw_message_sample\n// from upstream nodes into the final report structure.\n\n/**\n * Robustly maps a segment string to a Chinese time segment name.\n * This function is likely from an upstream node, but included for completeness if needed.\n * @param {string} segmentInput - The segment string, e.g., \"19:30-20:00\" or \"\u508d\u665a\".\n * @returns {string} The corresponding Chinese segment name, e.g., \"\u665a\u4e0a\".\n */\nfunction mapTimeToChineseSegment(segmentInput) {\n  if (!segmentInput || typeof segmentInput !== 'string') {\n    return \"\u672a\u77e5\u65f6\u6bb5\";\n  }\n  const startHour = parseInt(segmentInput.split(':')[0], 10);\n  if (isNaN(startHour)) {\n    return segmentInput;\n  }\n  if (startHour >= 0 && startHour < 6) return \"\u6df1\u591c\";\n  if (startHour >= 6 && startHour < 9) return \"\u65e9\u6668\";\n  if (startHour >= 9 && startHour < 12) return \"\u4e0a\u5348\";\n  if (startHour >= 12 && startHour < 14) return \"\u4e2d\u5348\";\n  if (startHour >= 14 && startHour < 18) return \"\u4e0b\u5348\";\n  if (startHour >= 18 && startHour < 22) return \"\u665a\u4e0a\";\n  if (startHour >= 22 && startHour <= 23) return \"\u6df1\u591c\";\n  return \"\u672a\u77e5\u65f6\u6bb5\";\n}\n\n// --- Main Logic ---\n\n// Step 1: Get the required data sources\n// $input.first() refers to the first input connected to this node (likely \"AI Semantic Merge\")\nconst aiMergeResult = $input.first().json.output;\n// $('Node Name') refers to data from a specific node by its name\nconst physicalMergeResult = $('Merge & Deduplicate Topics').first().json;\n\n// Get data from the newly added nodes\n// Ensure 'Extract Links & Active Users' is the exact name of your node in n8n\nconst linksAndUsersData = $('Extract Links & Active Users').first().json;\nconst all_links = linksAndUsersData.all_links;\nconst active_users = linksAndUsersData.active_users;\n\n// Ensure 'Prepare Raw Message Sample' is the exact name of your node in n8n\nconst rawSampleData = $('Prepare Raw Message Sample').first().json;\nconst raw_message_sample = rawSampleData.raw_message_sample;\n\n\nif (!aiMergeResult || !physicalMergeResult || !linksAndUsersData || !rawSampleData) {\n  return { json: { error: \"Missing one or more required inputs. Check node connections and names.\" } };\n}\n\n// Step 2: Create a fast lookup map for all original topics\nconst allTopicsMap = new Map(physicalMergeResult.all_topics.map(t => [t.physical_id, t]));\n\n// Step 3: Iterate through the AI's decisions and assemble the enriched topics\nconst enhanced_merged_topics = [];\nfor (const aiTopic of aiMergeResult.merged_topics) {\n  const combined_seqs_nested = [];\n  const seq_source_mapping = {};\n\n  for (const source of aiTopic.merged_from) {\n    const physical_id = source.physical_id;\n    const originalTopic = allTopicsMap.get(physical_id);\n\n    if (originalTopic) {\n      combined_seqs_nested.push(originalTopic.topic_seq);\n      for (const seq of originalTopic.topic_seq) {\n        if (!seq_source_mapping[seq]) {\n          seq_source_mapping[seq] = {\n            source_name: originalTopic.source_name,\n            segment_name: originalTopic.segment_name,\n            analysis_id: originalTopic.analysis_id,\n            topic_start_time: originalTopic.topic_start_time,\n            topic_end_time: originalTopic.topic_end_time\n          };\n        }\n      }\n    }\n  }\n\n  // Flatten combined_seqs_nested and remove duplicates\n  const combined_message_seqs = [...new Set(combined_seqs_nested.flat())].sort((a, b) => a - b);\n\n  // Calculate total_combined_messages and chat_count\n  const total_combined_messages = combined_message_seqs.length;\n  const chat_count = total_combined_messages; // Assuming chat_count is same as message count for now\n\n  // Determine message_time_range\n  let earliest_seq_time = null;\n  let latest_seq_time = null;\n\n  if (combined_message_seqs.length > 0) {\n    // Find the earliest and latest time from the original messages based on seq\n    // This requires access to the original messages from \"Parse & Structure Data\"\n    // For simplicity, we'll use the topic_start_time and topic_end_time from originalTopic\n    // as provided by \"Merge & Deduplicate Topics\" node.\n    // If more granular message-level time is needed, you'd need to pass the full\n    // original messages array or a lookup map from \"Parse & Structure Data\" node.\n\n    // For now, we'll use the min/max of the original topics' start/end times\n    let minTime = Infinity;\n    let maxTime = -Infinity;\n\n    for (const source of aiTopic.merged_from) {\n      const originalTopic = allTopicsMap.get(source.physical_id);\n      if (originalTopic) {\n        const startTime = new Date(originalTopic.topic_start_time).getTime();\n        const endTime = new Date(originalTopic.topic_end_time).getTime();\n        if (startTime < minTime) minTime = startTime;\n        if (endTime > maxTime) maxTime = endTime;\n      }\n    }\n    earliest_seq_time = minTime !== Infinity ? new Date(minTime).toISOString() : null;\n    latest_seq_time = maxTime !== -Infinity ? new Date(maxTime).toISOString() : null;\n  }\n\n\n  enhanced_merged_topics.push({\n    semantic_topic_id: aiTopic.semantic_topic_id,\n    merged_title: aiTopic.merged_title,\n    merged_description: aiTopic.merged_description,\n    is_off_topic: aiTopic.is_off_topic,\n    semantic_category: aiTopic.semantic_category,\n    topic_tags: aiTopic.topic_tags,\n    merged_from: aiTopic.merged_from,\n    relevance_score: aiTopic.relevance_score,\n    confidence_level: aiTopic.confidence_level,\n    combined_message_seqs: combined_message_seqs,\n    total_combined_messages: total_combined_messages,\n    chat_count: chat_count,\n    seq_source_mapping: seq_source_mapping,\n    message_time_range: {\n      earliest_seq: combined_message_seqs.length > 0 ? combined_message_seqs[0] : null,\n      latest_seq: combined_message_seqs.length > 0 ? combined_message_seqs[combined_message_seqs.length - 1] : null,\n      seq_count: combined_message_seqs.length,\n      earliest_time: earliest_seq_time, // Use calculated earliest time\n      latest_time: latest_seq_time // Use calculated latest time\n    }\n  });\n}\n\n// Prepare the final output\nconst now = new Date();\nreturn [{\n  json: {\n    merge_timestamp: now.toISOString(),\n    merge_date: now.toISOString().split('T')[0], // Just the date part\n    merge_type: \"semantic\",\n    original_topics_count: physicalMergeResult.all_topics.length,\n    merged_topics_count: enhanced_merged_topics.length,\n    compression_ratio: (1 - enhanced_merged_topics.length / physicalMergeResult.all_topics.length) * 100,\n    merged_topics: enhanced_merged_topics,\n    merge_metadata: aiMergeResult.merge_metadata,\n    original_statistics: physicalMergeResult.statistics,\n    sources_metadata: physicalMergeResult.sources_metadata,\n    segments_metadata: physicalMergeResult.segments_metadata,\n    global_seq_index: physicalMergeResult.global_seq_index, // Ensure this field exists and is passed\n\n    // Newly added fields\n    all_links: all_links,\n    active_users: active_users,\n    raw_message_sample: raw_message_sample\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1600,
        80
      ],
      "id": "0299a2da-cfe5-47ff-9b49-57c387020ae2",
      "name": "Assemble Final Report"
    },
    {
      "parameters": {
        "jsCode": "// \u83b7\u53d6AI Agent\u7684\u8f93\u51fa\nlet aiOutput;\ntry {\n  // \u9996\u5148\u68c0\u67e5\u8f93\u5165\u6570\u636e\u7ed3\u6784\n  const inputData = $input.first().json;\n  console.log('Input data structure:', JSON.stringify(inputData, null, 2));\n  \n  // \u5c1d\u8bd5\u4e0d\u540c\u7684\u6570\u636e\u7ed3\u6784\u8def\u5f84\n  if (inputData.choices && inputData.choices[0] && inputData.choices[0].message) {\n    // OpenAI API \u6807\u51c6\u683c\u5f0f\n    aiOutput = inputData.choices[0].message.content;\n  } else if (inputData.message && inputData.message.content) {\n    // \u7b80\u5316\u7684\u6d88\u606f\u683c\u5f0f\n    aiOutput = inputData.message.content;\n  } else if (inputData.content) {\n    // \u76f4\u63a5\u5185\u5bb9\u683c\u5f0f\n    aiOutput = inputData.content;\n  } else if (inputData.output) {\n    // \u8f93\u51fa\u5b57\u6bb5\u683c\u5f0f\n    aiOutput = inputData.output;\n  } else if (typeof inputData === 'string') {\n    // \u7eaf\u5b57\u7b26\u4e32\u683c\u5f0f\n    aiOutput = inputData;\n  } else {\n    // \u5982\u679c\u90fd\u627e\u4e0d\u5230\uff0c\u5c1d\u8bd5\u627e\u5230\u7b2c\u4e00\u4e2a\u5b57\u7b26\u4e32\u503c\n    const findStringValue = (obj) => {\n      if (typeof obj === 'string') return obj;\n      if (Array.isArray(obj)) {\n        for (const item of obj) {\n          const result = findStringValue(item);\n          if (result) return result;\n        }\n      } else if (typeof obj === 'object' && obj !== null) {\n        for (const key in obj) {\n          const result = findStringValue(obj[key]);\n          if (result) return result;\n        }\n      }\n      return null;\n    };\n    \n    aiOutput = findStringValue(inputData);\n    \n    if (!aiOutput) {\n      throw new Error('\u65e0\u6cd5\u5728\u8f93\u5165\u6570\u636e\u4e2d\u627e\u5230AI\u8f93\u51fa\u5185\u5bb9');\n    }\n  }\n  \n  console.log('Found AI output:', aiOutput.substring(0, 200) + '...');\n  \n} catch (error) {\n  console.error('\u83b7\u53d6AI\u8f93\u51fa\u65f6\u51fa\u9519:', error);\n  return [{\n    json: {\n      error: '\u65e0\u6cd5\u83b7\u53d6AI\u8f93\u51fa: ' + error.message,\n      inputData: $input.first().json,\n      status: 'error'\n    }\n  }];\n}\n\n// \u6e05\u7406\u53ef\u80fd\u7684markdown\u6807\u8bb0\nlet htmlContent = aiOutput;\nif (htmlContent.includes('```html')) {\n  htmlContent = htmlContent.replace(/```html\\n?/g, '').replace(/\\n?```/g, '');\n}\nif (htmlContent.includes('```')) {\n  htmlContent = htmlContent.replace(/```\\n?/g, '').replace(/\\n?```/g, '');\n}\n\n// \u83b7\u53d6\u7fa4\u804a\u540d\u79f0\u548c\u65e5\u671f\uff08\u4ece\u5de5\u4f5c\u6d41\u8f93\u5165\u6216\u8bbe\u7f6e\u9ed8\u8ba4\u503c\uff09\nconst groupName = $('ConfigureChatParameters').first().json.group_name || 'unknown';\nconst date = $('ConfigureChatParameters').first().json.date || new Date().toISOString().split('T')[0];\n\nconsole.log('\u7fa4\u804a\u4fe1\u606f:', { groupName, date });\n\n// \ud83d\udd25 \u6539\u8fdb\uff1a\u667a\u80fdHTML\u5185\u5bb9\u6e05\u7406 - \u4fdd\u62a4\u56fe\u8868\u548c\u811a\u672c\u5185\u5bb9\nconsole.log('\u5f00\u59cbHTML\u6e05\u7406\uff0c\u539f\u59cb\u957f\u5ea6:', htmlContent.length);\n\n// \u667a\u80fd\u6e05\u7406HTML\u5185\u5bb9\uff0c\u4fdd\u62a4\u91cd\u8981\u7684\u4ee3\u7801\u533a\u57df\nconst smartCleanHtmlForMCP = (html) => {\n  // 1. \u9996\u5148\u4fdd\u62a4\u91cd\u8981\u7684\u4ee3\u7801\u533a\u57df\n  const protectedSections = [];\n  let cleanedHtml = html;\n  \n  // \u4fdd\u62a4 script \u6807\u7b7e\u5185\u5bb9\uff08\u5305\u62ec mermaid, chart.js \u7b49\uff09\n  cleanedHtml = cleanedHtml.replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, (match, offset) => {\n    const placeholder = `__PROTECTED_SCRIPT_${protectedSections.length}__`;\n    protectedSections.push(match);\n    return placeholder;\n  });\n  \n  // \u4fdd\u62a4 style \u6807\u7b7e\u5185\u5bb9\n  cleanedHtml = cleanedHtml.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, (match, offset) => {\n    const placeholder = `__PROTECTED_STYLE_${protectedSections.length}__`;\n    protectedSections.push(match);\n    return placeholder;\n  });\n  \n  // \u4fdd\u62a4 pre \u6807\u7b7e\u5185\u5bb9\uff08\u53ef\u80fd\u5305\u542b\u56fe\u8868\u5b9a\u4e49\uff09\n  cleanedHtml = cleanedHtml.replace(/<pre[^>]*>[\\s\\S]*?<\\/pre>/gi, (match, offset) => {\n    const placeholder = `__PROTECTED_PRE_${protectedSections.length}__`;\n    protectedSections.push(match);\n    return placeholder;\n  });\n  \n  // \u4fdd\u62a4\u5177\u6709\u7279\u6b8aclass\u7684div\uff08\u5982mermaid\u56fe\u8868\u5bb9\u5668\uff09\n  cleanedHtml = cleanedHtml.replace(/<div[^>]*class=\"[^\"]*(?:mermaid|chart|graph|diagram)[^\"]*\"[^>]*>[\\s\\S]*?<\\/div>/gi, (match, offset) => {\n    const placeholder = `__PROTECTED_CHART_${protectedSections.length}__`;\n    protectedSections.push(match);\n    return placeholder;\n  });\n  \n  // 2. \u5bf9\u5176\u4f59\u5185\u5bb9\u8fdb\u884c\u6e05\u7406\n  cleanedHtml = cleanedHtml\n    // \u53ea\u6e05\u7406\u666e\u901a\u7684\u6362\u884c\u7b26\u548c\u5236\u8868\u7b26\uff0c\u4f46\u4fdd\u7559\u5fc5\u8981\u7684\u683c\u5f0f\n    .replace(/[\\r]/g, '')           // \u79fb\u9664\u56de\u8f66\u7b26\n    .replace(/\\n\\s*\\n/g, '\\n')      // \u5c06\u591a\u4e2a\u6362\u884c\u5408\u5e76\u4e3a\u4e00\u4e2a\n    .replace(/\\t/g, '  ')           // \u5c06\u5236\u8868\u7b26\u8f6c\u6362\u4e3a\u4e24\u4e2a\u7a7a\u683c\n    // \u6e05\u7406HTML\u6807\u7b7e\u95f4\u7684\u591a\u4f59\u7a7a\u683c\uff0c\u4f46\u4e0d\u5f71\u54cd\u6587\u672c\u5185\u5bb9\n    .replace(/>\\s+</g, '><')\n    // \u6e05\u7406\u884c\u9996\u884c\u5c3e\u7a7a\u683c\n    .replace(/^\\s+|\\s+$/gm, '')\n    // \u5c06\u591a\u4e2a\u8fde\u7eed\u7a7a\u683c\u5408\u5e76\uff0c\u4f46\u4fdd\u7559\u5355\u4e2a\u6362\u884c\n    .replace(/ +/g, ' ');\n  \n  // 3. \u6062\u590d\u53d7\u4fdd\u62a4\u7684\u5185\u5bb9\n  protectedSections.forEach((section, index) => {\n    const scriptPlaceholder = `__PROTECTED_SCRIPT_${index}__`;\n    const stylePlaceholder = `__PROTECTED_STYLE_${index}__`;\n    const prePlaceholder = `__PROTECTED_PRE_${index}__`;\n    const chartPlaceholder = `__PROTECTED_CHART_${index}__`;\n    \n    cleanedHtml = cleanedHtml.replace(scriptPlaceholder, section);\n    cleanedHtml = cleanedHtml.replace(stylePlaceholder, section);\n    cleanedHtml = cleanedHtml.replace(prePlaceholder, section);\n    cleanedHtml = cleanedHtml.replace(chartPlaceholder, section);\n  });\n  \n  // 4. \u6700\u540e\u53ea\u5bf9JSON\u4f20\u8f93\u505a\u5fc5\u8981\u7684\u8f6c\u4e49\n  return cleanedHtml\n    .replace(/\\\\/g, '\\\\\\\\')    // \u8f6c\u4e49\u53cd\u659c\u6760\n    .replace(/\"/g, '\\\\\"');     // \u8f6c\u4e49\u53cc\u5f15\u53f7\n};\n\n// \u5e94\u7528\u667a\u80fd\u6e05\u7406\nconst originalHtmlContent = htmlContent;\nhtmlContent = smartCleanHtmlForMCP(htmlContent);\n\nconsole.log('\u667a\u80fdHTML\u6e05\u7406\u5b8c\u6210');\nconsole.log('- \u539f\u59cb\u957f\u5ea6:', originalHtmlContent.length);\nconsole.log('- \u6e05\u7406\u540e\u957f\u5ea6:', htmlContent.length);\nconsole.log('- \u8282\u7701\u7a7a\u95f4:', originalHtmlContent.length - htmlContent.length, 'bytes');\n\n// \u68c0\u67e5\u662f\u5426\u5305\u542b\u56fe\u8868\u76f8\u5173\u5185\u5bb9\nconst hasCharts = originalHtmlContent.match(/(mermaid|chart\\.js|echarts|d3\\.js|plotly|canvas|svg)/i);\nif (hasCharts) {\n  console.log('\u2705 \u68c0\u6d4b\u5230\u56fe\u8868\u5185\u5bb9\uff0c\u5df2\u5e94\u7528\u4fdd\u62a4\u6027\u6e05\u7406');\n} else {\n  console.log('\u2139\ufe0f \u672a\u68c0\u6d4b\u5230\u56fe\u8868\u5185\u5bb9');\n}\n\n// \u9a8c\u8bc1HTML\u6587\u6863\u5b8c\u6574\u6027\nif (!htmlContent.includes('<!DOCTYPE html>')) {\n  console.error('AI\u8f93\u51fa\u4e0d\u662f\u5b8c\u6574\u7684HTML\u6587\u6863');\n  return [{\n    json: {\n      error: 'AI\u8f93\u51fa\u4e0d\u662f\u5b8c\u6574\u7684HTML\u6587\u6863',\n      receivedContent: htmlContent.substring(0, 500),\n      status: 'error'\n    }\n  }];\n}\n\n// \u989d\u5916\u7684HTML\u5185\u5bb9\u9a8c\u8bc1\nconst validateHtml = (html) => {\n  const checks = {\n    hasDoctype: html.includes('<!DOCTYPE html>'),\n    hasHtmlTag: html.includes('<html') && html.includes('</html>'),\n    hasHeadTag: html.includes('<head') && html.includes('</head>'),\n    hasBodyTag: html.includes('<body') && html.includes('</body>'),\n    hasTitle: html.includes('<title'),\n    isNotEmpty: html.length > 100,\n    hasChartLibraries: /(?:mermaid|chart\\.js|echarts|d3\\.js|plotly)/i.test(html)\n  };\n  \n  const passed = Object.values(checks).filter(Boolean).length;\n  const total = Object.keys(checks).length;\n  \n  console.log('HTML\u9a8c\u8bc1\u7ed3\u679c:', checks);\n  console.log(`HTML\u8d28\u91cf\u8bc4\u5206: ${passed}/${total}`);\n  \n  return { checks, score: passed / total };\n};\n\nconst validation = validateHtml(htmlContent);\n\n// \u751f\u6210\u6587\u4ef6\u540d - \u4f7f\u7528\u7fa4\u804a\u540d\u79f0\u548c\u65e5\u671f\nconst filename = `\u7fa4\u804a\u65e5\u62a5-${groupName}-${date}.html`;\nconst safeFilename = filename.replace(/[^a-z0-9\\u4e00-\\u9fa5_\\-\\.]/gi, '_');\n\nconsole.log('\u6587\u4ef6\u540d\u751f\u6210:', { \n  original: filename, \n  safe: safeFilename,\n  groupName: groupName,\n  date: date \n});\n\n// \u751f\u6210\u6e05\u7406\u540e\u7684HTML\u7528\u4e8eMCP\u5de5\u5177\nconst mcpReadyHtml = htmlContent;\n\n// \u8ba1\u7b97\u5904\u7406\u65f6\u957f\nlet processDuration = null;\ntry {\n  const startTime = $('\u9a8c\u8bc1\u8f93\u5165\u53c2\u6570').item.json.process_start_time;\n  if (startTime) {\n    const startDate = new Date(startTime);\n    const endDate = new Date();\n    // \u8ba1\u7b97\u79d2\u6570\u5dee\n    processDuration = Math.round((endDate - startDate) / 1000);\n  }\n} catch (e) {\n  console.log('\u8ba1\u7b97\u5904\u7406\u65f6\u957f\u5931\u8d25:', e.message);\n  processDuration = null;\n}\n\n\n// \u8fd4\u56de\u5904\u7406\u7ed3\u679c - \u5305\u542b\u4e24\u4e2a\u7248\u672c\u7684HTML\nreturn [{\n  json: {\n    // \u539f\u59cbHTML\uff08\u7528\u4e8e\u6587\u4ef6\u4fdd\u5b58\uff09\n    htmlContent: originalHtmlContent,\n    // \u6e05\u7406\u540e\u7684HTML\uff08\u7528\u4e8eMCP\u5de5\u5177\u8c03\u7528\uff09\n    mcpHtmlContent: mcpReadyHtml,\n    // \u6587\u4ef6\u4fe1\u606f - \u4f7f\u7528\u7fa4\u804a\u540d\u79f0\n    filename: safeFilename,\n    originalFilename: filename,\n    groupName: groupName,\n    date: date,\n    process_end_time: $now.toISO(),\n    process_duration: processDuration,\n    timestamp: new Date().toISOString(),\n    status: 'success',\n    // \u7edf\u8ba1\u4fe1\u606f\n    originalContentLength: originalHtmlContent.length,\n    cleanedContentLength: mcpReadyHtml.length,\n    compressionRatio: ((originalHtmlContent.length - mcpReadyHtml.length) / originalHtmlContent.length * 100).toFixed(2) + '%',\n    // HTML\u8d28\u91cf\u4fe1\u606f\n    htmlValidation: validation,\n    hasChartContent: hasCharts !== null,\n    // \u8c03\u8bd5\u4fe1\u606f\n    processingSteps: [\n      '\u2705 AI\u8f93\u51fa\u89e3\u6790\u6210\u529f',\n      '\u2705 Markdown\u6e05\u7406\u5b8c\u6210', \n      '\u2705 \u667a\u80fdHTML\u6e05\u7406\u5b8c\u6210\uff08\u4fdd\u62a4\u56fe\u8868\uff09',\n      '\u2705 MCP\u683c\u5f0f\u4f18\u5316\u5b8c\u6210',\n      `\u2705 HTML\u9a8c\u8bc1\u5b8c\u6210 (${(validation.score * 100).toFixed(1)}%)`,\n      `\u2705 \u6587\u4ef6\u540d\u751f\u6210\u5b8c\u6210: ${safeFilename}`\n    ]\n  },\n  binary: {\n    data: {\n      data: Buffer.from(originalHtmlContent, 'utf8').toString('base64'),\n      mimeType: 'text/html',\n      fileName: safeFilename,\n      fileExtension: 'html'\n    }\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        80
      ],
      "id": "699fd2d2-fdc8-4bd7-9a14-1db30edd2229",
      "name": "\u5904\u7406AI\u8f93\u51fa"
    },
    {
      "parameters": {
        "content": "## \u6700\u7ec8HTML\u5185\u5bb9\u5904\u7406\n",
        "height": 740,
        "width": 780,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1820,
        -200
      ],
      "id": "59446f38-5c4e-4778-a707-95c396cccc60",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "jsCode": "// Node: Extract Links & Active Users\n// Input: Output from \"Parse & Structure Data\" node (single JSON object with sources.messages)\n// Output: JSON with all_links array (objects with url, sender, time, context) and active_users array\n\nconst inputData = $input.first().json; // Get the single item from \"Parse & Structure Data\"\n\n\nif (!inputData || !inputData.sources) {\n  console.log(\"Input data is missing structuredData or sources field.\");\n  return { json: { all_links: [], active_users: [] } };\n}\n\n// Assuming only one source, get its messages list\nconst sourceName = Object.keys(inputData.sources)[0];\nconst allMessages = inputData.sources[sourceName].messages;\n\nif (!allMessages || allMessages.length === 0) {\n  console.log(\"No messages found in structured data.\");\n  return { json: { all_links: [], active_users: [] } };\n}\n\nconst allLinks = []; // Now an array of objects\nconst senderMessageCounts = {}; // Store message counts for active users\n\n// Regex for raw URLs (http or https)\nconst rawUrlRegex = /(https?:\\/\\/[^\\s]+)/g;\n// Regex for [\u94fe\u63a5|URL] format\nconst formattedLinkRegex = /\\[\u94fe\u63a5\\|(https?:\\/\\/[^\\]]+)\\]/g;\n\nconsole.log(`Processing ${allMessages.length} messages.`);\n\nfor (const message of allMessages) {\n  const senderName = message.sender_name;\n  const messageTime = message.time;\n  const messageContent = message.content;\n\n  //console.log(`--- Processing Message ---`);\n  //console.log(`Raw senderName: '${senderName}'`);\n  //console.log(`Message content: '${messageContent}'`);\n\n  // Check senderName validity for active users\n  const isValidSender = senderName && senderName.trim() !== '' && senderName !== '\u7cfb\u7edf\u6d88\u606f';\n  \n\n  if (messageContent) {\n    const extractedUrls = []; // Temporarily store all URLs found in this message\n    let linkMatch;\n    \n    // Extract raw URLs\n    rawUrlRegex.lastIndex = 0; // Reset regex lastIndex for global regex\n    while ((linkMatch = rawUrlRegex.exec(messageContent)) !== null) {\n      extractedUrls.push(linkMatch[0]);\n    }\n\n    // Extract formatted links [\u94fe\u63a5|URL]\n    formattedLinkRegex.lastIndex = 0; // Reset regex lastIndex for global regex\n    while ((linkMatch = formattedLinkRegex.exec(messageContent)) !== null) {\n      extractedUrls.push(linkMatch[1]);\n    }\n\n    for (const url of extractedUrls) {\n      // Check if this URL is part of an image or video markdown\n      const isImageOrVideoLink = messageContent.includes(`![\u56fe\u7247]`) || messageContent.includes(`![\u89c6\u9891]`);\n      if (!isImageOrVideoLink) {\n        //console.log(`Found valid link: ${url}`);\n        allLinks.push({\n          url: url,\n          sender: senderName,\n          time: messageTime,\n          context: messageContent\n        });\n      } else {\n        console.log(`Excluded image/video link: ${url}`);\n      }\n    }\n  }\n\n  // Count active users (only for valid senders)\n  if (isValidSender) {\n    //console.log(`Counting message for sender: ${senderName}`);\n    senderMessageCounts[senderName] = (senderMessageCounts[senderName] || 0) + 1;\n  }\n}\n\nconsole.log(`Final allLinks count: ${allLinks.length}`);\nconsole.log(`Final activeUsers count: ${Object.keys(senderMessageCounts).length}`);\n\n// Convert senderMessageCounts to active_users array and sort\nconst activeUsers = Object.entries(senderMessageCounts)\n  .map(([sender_name, message_count]) => ({ sender_name, message_count }))\n  .sort((a, b) => b.message_count - a.message_count); // Sort in descending order by message_count\n\nreturn [{\n  json: {\n    all_links: allLinks,\n    active_users: activeUsers\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        -100
      ],
      "id": "63f36a48-ba52-4392-9a16-e66a0d41ce6c",
      "name": "Extract Links & Active Users"
    },
    {
      "parameters": {
        "jsCode": "// Node: Prepare Raw Message Sample\n// Input: Output from \"Parse & Structure Data\" node (\u5355\u4e2a JSON \u5bf9\u8c61\uff0c\u5305\u542b sources.messages)\n// Output: JSON with raw_message_sample \u6570\u7ec4\n\nconst structuredData = $('Parse & Structure Data').first().json; // \u83b7\u53d6\u4e0a\u6e38\u8282\u70b9\u7684\u5355\u4e2a\u6570\u636e\u9879\n\nif (!structuredData || !structuredData.sources) {\n  console.log(\"\u8f93\u5165\u6570\u636e\u7f3a\u5c11 structuredData \u6216 sources \u5b57\u6bb5\u3002\");\n  return { json: { raw_message_sample: [] } };\n}\n\n// \u5047\u8bbe\u53ea\u6709\u4e00\u4e2a source\uff0c\u83b7\u53d6\u5176\u6d88\u606f\u5217\u8868\nconst sourceName = Object.keys(structuredData.sources)[0];\nconst allMessages = structuredData.sources[sourceName].messages;\n\nif (!allMessages || allMessages.length === 0) {\n  console.log(\"\u7ed3\u6784\u5316\u6570\u636e\u4e2d\u672a\u627e\u5230\u6d88\u606f\u3002\");\n  return { json: { raw_message_sample: [] } };\n}\n\n// \u63d0\u53d6\u6d88\u606f\u6837\u672c\u3002\n// \u4f60\u53ef\u4ee5\u6839\u636e\u9700\u8981\u8c03\u6574\u91c7\u6837\u903b\u8f91\uff08\u4f8b\u5982\uff0c\u968f\u673a\u91c7\u6837\uff0c\u66f4\u591a\u6d88\u606f\uff09\u3002\nconst rawMessageSample = allMessages.map(msg => ({\n  sender_name: msg.sender_name,\n  time: msg.time,\n  content: msg.content\n}));\n\nreturn [{\n  json: {\n    raw_message_sample: rawMessageSample\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        -100
      ],
      "id": "46a817d3-6fb4-4d3e-b991-3b098d0bb005",
      "name": "Prepare Raw Message Sample"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6dab17d2-2e5e-40e3-b5d9-0abcdac0dde8",
              "name": "date",
              "value": "={{ $now.minus({days: 1}).format('yyyy-MM-dd') }}",
              "type": "string"
            },
            {
              "id": "c2a2eec2-4ead-4544-83db-73aba917f553",
              "name": "group_name",
              "value": "n8n\u81ea\u52a8\u5316\u5b9e\u6218\u4ea4\u6d41\u7fa4",
              "type": "string"
            },
            {
              "id": "0bcb4528-e474-4fe9-ada8-ebc125a809be",
              "name": "group_owner",
              "value": "LQ",
              "type": "string"
            },
            {
              "id": "d98a1539-81e4-45d3-8602-e81815516a7c",
              "name": "web_style_template",
              "value": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>[\u5728\u6b64\u5904\u586b\u5199\u62a5\u544a\u6807\u9898] - [\u65e5\u671f]</title>\n    <link rel=\"stylesheet\" href=\"https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/tailwindcss/2.2.19/tailwind.min.css\">\n    <link rel=\"stylesheet\" href=\"https://lf6-cdn-tos.bytecdntp.com/cdn/expire-100-M/font-awesome/6.0.0/css/all.min.css\">\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js\" integrity=\"sha512-BNaRQnYJYiPSqHHDb58B0yaPfCu+Wgds8Gp/gU33kqBtgNS4tSPHuGibyoVBL5rLesXWW/sGuLhYFChxgYnz2Q==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\" onerror=\"this.onerror=null;this.src='https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js';\"></script>\n    <script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/npm/kity@2.0.4/dist/kity.min.js\"></script>\n    <script type=\"text/javascript\" src=\"https://cdn.jsdelivr.net/npm/kityminder-core@1.4.50/dist/kityminder.core.min.js\"></script>\n    <style>\n        /* CSS\u53d8\u91cf\u5b9a\u4e49 */\n        :root {\n            /* \u4e3b\u9898\u989c\u8272\u914d\u7f6e */\n            --bg-primary: #F5F5EE; /* \u4e3b\u80cc\u666f\u8272 - \u7c73\u767d\u8272 */\n            --bg-secondary: #faf9f6; /* \u6b21\u80cc\u666f\u8272 - \u5361\u7247\u80cc\u666f */\n            --bg-tertiary: #f1f3f5; /* \u7b2c\u4e09\u80cc\u666f\u8272 - \u6807\u7b7e\u7b49 */\n            --text-primary: #212529; /* \u4e3b\u8981\u6587\u5b57\u989c\u8272 */\n            --text-secondary: #495057; /* \u6b21\u8981\u6587\u5b57\u989c\u8272 */\n            --accent-primary: #FB651E; /* \u4e3b\u8981\u5f3a\u8c03\u8272 - \u6a59\u8272 */\n            --accent-secondary: #ff8906; /* \u6b21\u8981\u5f3a\u8c03\u8272 */\n            --accent-tertiary: #e8590c; /* \u7b2c\u4e09\u5f3a\u8c03\u8272 */\n            --accent-blue: #007EFF;     /* \u84dd\u8272\u5f3a\u8c03 */\n            --accent-purple: #7048e8; /* \u7d2b\u8272\u5f3a\u8c03 */\n            --accent-cyan: #10B981;     /* \u9752\u8272\u5f3a\u8c03 */\n            --highlight-keyword-bg: #ffe8cc; /* \u5173\u952e\u8bcd\u9ad8\u4eae\u80cc\u666f */\n            --highlight-name-color: #7048e8; /* \u4eba\u540d\u9ad8\u4eae\u989c\u8272 */\n            --highlight-group-name-bg: var(--highlight-keyword-bg); /* AI\u4ea7\u54c1\u8757\u866b\u56e2\u9ad8\u4eae\u80cc\u666f\u8272 - Updated */\n\n\n            /* \u5e03\u5c40\u4e0e\u6837\u5f0f\u53d8\u91cf */\n            --card-padding: 24px; /* \u5361\u7247\u5185\u8fb9\u8ddd */\n            --grid-gap: 16px; /* \u7f51\u683c\u95f4\u8ddd */\n            --card-radius: 12px; /* \u5361\u7247\u5706\u89d2 */\n            --card-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); /* \u5361\u7247\u9634\u5f71 */\n            --font-family: 'Inter', 'SF Pro Display', 'Segoe UI', sans-serif; /* \u5b57\u4f53\u6808 */\n\n            /* KityMinder\u81ea\u5b9a\u4e49\u4e3b\u9898\u989c\u8272 */\n            --km-orange: #FB651E;     /* KityMinder\u6a59\u8272\uff0c\u7528\u4e8e\u4e00\u7ea7\u8282\u70b9 */\n            --km-blue: #007EFF;       /* KityMinder\u84dd\u8272\uff0c\u7528\u4e8e\u4e8c\u7ea7\u8282\u70b9 */\n            --km-lightblue: #10B981;  /* KityMinder\u6d45\u84dd\u8272\uff0c\u7528\u4e8e\u4e09\u7ea7\u8282\u70b9 */\n            --km-root-bg: #f0f0f0;     /* KityMinder\u6839\u8282\u70b9\u80cc\u666f */\n            --km-root-stroke: var(--km-root-bg); /* KityMinder\u6839\u8282\u70b9\u8fb9\u6846 */\n            --km-root-text: #333333;   /* KityMinder\u6839\u8282\u70b9\u6587\u5b57\u989c\u8272 */\n            --km-node-text: #ffffff;   /* KityMinder\u666e\u901a\u8282\u70b9\u6587\u5b57\u989c\u8272 */\n            --km-connect-color: #777777; /* KityMinder\u8fde\u63a5\u7ebf\u989c\u8272 */\n        }\n\n        /* \u5168\u5c40\u91cd\u7f6e\u548c\u57fa\u7840\u6837\u5f0f */\n        * {\n            margin: 0; /* \u6e05\u9664\u5916\u8fb9\u8ddd */\n            padding: 0; /* \u6e05\u9664\u5185\u8fb9\u8ddd */\n            box-sizing: border-box; /* \u4f7f\u7528border-box\u76d2\u6a21\u578b */\n        }\n\n        body {\n            font-family: var(--font-family); /* \u5e94\u7528\u5b9a\u4e49\u7684\u5b57\u4f53\u6808 */\n            background-color: var(--bg-primary); /* \u8bbe\u7f6e\u9875\u9762\u80cc\u666f\u8272 */\n            color: var(--text-primary); /* \u8bbe\u7f6e\u4e3b\u8981\u6587\u5b57\u989c\u8272 */\n            line-height: 1.6; /* \u8bbe\u7f6e\u884c\u9ad8 */\n            font-size: 16px; /* \u8bbe\u7f6e\u57fa\u7840\u5b57\u53f7 */\n            width: 100%; /* \u9875\u9762\u5bbd\u5ea6\u5360\u6ee1\u7236\u5bb9\u5668 */\n            max-width: 1000px; /* \u5185\u5bb9\u6700\u5927\u5bbd\u5ea6 */\n            margin: 0 auto; /* \u5185\u5bb9\u5c45\u4e2d\u663e\u793a */\n            padding: 20px; /* \u9875\u9762\u5185\u8fb9\u8ddd */\n        }\n        body.modal-active { /* \u5f53\u6a21\u6001\u6846\u6fc0\u6d3b\u65f6\uff0c\u7981\u6b62\u9875\u9762\u6eda\u52a8 */\n            overflow: hidden;\n        }\n\n        /* \u6807\u9898\u6837\u5f0f */\n        h1, h2, h3, h4 {\n            font-weight: 600; /* \u6807\u9898\u5b57\u91cd */\n            letter-spacing: 0.5px; /* \u5b57\u7b26\u95f4\u8ddd */\n        }\n\n        h1 { /* \u4e3b\u6807\u9898\u6837\u5f0f */\n            font-size: 2.5rem;\n            margin-bottom: 0.5rem;\n            color: var(--accent-primary);\n        }\n\n        h2 { /* \u4e8c\u7ea7\u6807\u9898\u6837\u5f0f */\n            font-size: 1.75rem;\n            margin-bottom: 1rem;\n            color: var(--accent-primary);\n        }\n\n        h3 { /* \u4e09\u7ea7\u6807\u9898\u6837\u5f0f */ \n            font-size: 1.25rem;\n            margin-bottom: 0.75rem;\n            color: var(--accent-blue);\n        }\n        \n        /*\u7f51\u683c\u5bb9\u5668\u5e03\u5c40 */\n        .grid-container {\n            display: grid; /* \u4f7f\u7528CSS Grid\u5e03\u5c40 */\n            grid-template-columns: repeat(12, 1fr); /* 12\u5217\u7f51\u683c\u7cfb\u7edf */\n            grid-auto-rows: minmax(150px, auto); /* \u589e\u52a0\u884c\u9ad8\u6700\u5c0f\u503c\u4ee5\u9632\u6b62\u538b\u7f29 */\n            gap: var(--grid-gap); /* \u7f51\u683c\u95f4\u8ddd */\n            margin-top: 20px; /* \u4e0e\u4e0a\u65b9\u5143\u7d20\u7684\u95f4\u8ddd */\n            grid-template-areas:\n                \"main      main      main      main      main      main      main      main      main      main      main      main\"\n                \"topics    topics    topics    topics    topics    topics    topics    topics    topics    topics    topics    topics\"\n                \"mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap   mindmap\"\n                \"quote     quote     quote     quote     quote     quote     links     links     links     links     links     links\"\n                \"bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row bottom_row\";\n        }\n\n        /* \u5361\u7247\u901a\u7528\u6837\u5f0f */\n        .card {\n            background-color: var(--bg-secondary); \n            border-radius: var(--card-radius); \n            padding: var(--card-padding); \n            box-shadow: var(--card-shadow); \n            position: relative; \n            overflow: hidden; \n            transition: transform 0.3s ease, box-shadow 0.3s ease; \n            display: flex; \n            flex-direction: column; \n        }\n\n        .card:hover { \n            transform: translateY(-5px); \n            box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1); \n        }\n\n        .card::before { \n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 3px;\n            background: var(--accent-primary);\n        }\n\n        .card-icon { \n            position: absolute;\n            bottom: var(--card-padding);\n            right: var(--card-padding);\n            font-size: 4rem; \n            opacity: 0.07; \n            color: var(--accent-primary); \n            z-index: 0; \n        }\n\n        /* \u7f51\u683c\u533a\u57df\u5206\u914d */\n        .main-card { grid-area: main; }\n        .topic-cards-wrapper {\n            grid-area: topics;\n            display: grid;\n            grid-template-columns: repeat(12, 1fr);\n            gap: var(--grid-gap);\n            width: 100%; /* \u786e\u4fdd\u5bb9\u5668\u5360\u6ee1\u7f51\u683c\u533a\u57df */\n            min-height: 300px; /* \u8bbe\u7f6e\u6700\u5c0f\u9ad8\u5ea6\u9632\u6b62\u8fc7\u5ea6\u538b\u7f29 */\n            align-items: start; /* \u9632\u6b62\u5361\u7247\u88ab\u62c9\u4f38\u53d8\u5f62 */\n        }\n        .mindmap-card-container { grid-area: mindmap; }\n        .quote-card { grid-area: quote; }\n        .links-card { grid-area: links; }\n        .stats-wordcloud-row {\n            grid-area: bottom_row;\n            display: flex;\n            gap: var(--grid-gap);\n            align-items: stretch; /* Ensures cards in the row stretch to the same height */\n        }\n\n\n        .stats-card {\n            flex-grow: 7; /* Takes 7 parts of the available space */\n            flex-shrink: 1; /* Allows shrinking if needed */\n            flex-basis: 0; /* Initial size before distributing space */\n            min-width: 0; /* Allows shrinking below content size if necessary */\n        }\n        .wordcloud-card {\n            flex-grow: 5; /* Takes 5 parts of the available space */\n            flex-shrink: 1;\n            flex-basis: 0;\n            min-width: 0;\n            max-width: 50%; /* Limits wordcloud card width on larger screens */\n        }\n\n        /* \u4e3b\u9898\u5361\u7247\u6837\u5f0f */\n        .topic-card {\n            grid-column: span 6; /* Each topic card spans 6 columns on desktop */\n            min-height: 300px; /* \u589e\u52a0\u6700\u5c0f\u9ad8\u5ea6\u4ee5\u9632\u6b62\u538b\u7f29 */\n            width: 100%; /* \u786e\u4fdd\u5361\u7247\u5360\u6ee1\u5206\u914d\u7684\u7f51\u683c\u7a7a\u95f4 */\n            box-sizing: border-box; /* \u5305\u542b\u8fb9\u6846\u548c\u5185\u8fb9\u8ddd\u5728\u5bbd\u5ea6\u8ba1\u7b97\u5185 */\n        }\n        .topic-card > .topic-card-content-wrapper {\n            flex-grow: 1; /* Allows content to take available vertical space */\n            padding-bottom: 1rem; /* Space at the bottom of the content */\n        }\n        /* \u667a\u80fd\u8bdd\u9898\u5361\u7247\u5e03\u5c40 - \u5076\u6570\u6210\u5bf9\uff0c\u5947\u6570\u6700\u540e\u4e00\u4e2a\u72ec\u5360\u6574\u884c */\n        .topic-cards-wrapper > .topic-card:nth-last-child(1):nth-child(odd) {\n            grid-column: 1 / -1; /* \u6700\u540e\u4e00\u4e2a\u5947\u6570\u5361\u7247\u4ece\u7b2c1\u5217\u8de8\u8d8a\u5230\u6700\u540e\u4e00\u5217\uff08100%\u5bbd\u5ea6\uff09 */\n        }\n\n        /* \u4e3b\u5361\u7247\u5185\u90e8\u5143\u7d20\u6837\u5f0f */\n        .main-card h1 { text-align: center; }\n        .main-card .date { text-align: center; }\n        .date { font-size: 1.1rem; color: var(--text-secondary); margin-bottom: 1rem; }\n        .meta-info { display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 1rem; justify-content: center; }\n        .meta-info span { background-color: var(--bg-tertiary); padding: 5px 10px; border-radius: 20px; font-size: 0.9rem; color: var(--accent-blue); }\n        .summary { margin-top: 1rem; line-height: 1.7; text-align: left; }\n\n\n\n\n        .topic-category { display: inline-block; background-color: var(--accent-tertiary); color: var(--bg-primary); padding: 3px 8px; border-radius: 4px; font-size: 0.8rem; margin-bottom: 0.5rem; }\n                    .topic-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 0.75rem; margin-bottom: 0.75rem; }\n            .tag { background-color: var(--bg-tertiary); color: var(--accent-primary); padding: 3px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: 500; }\n        .highlight-keyword { background-color: var(--highlight-keyword-bg); padding: 1px 2px; border-radius: 2px; font-weight: 500; display: inline; line-height: 1.2; box-decoration-break: clone; -webkit-box-decoration-break: clone; }\n        .highlight-name { color: var(--highlight-name-color); font-weight: 500; }\n        .topic-chat-count { font-size: 0.9rem; color: var(--text-secondary); }\n\n        /* \u8bdd\u9898\u8ba8\u8bba\u5bb9\u5668\u6837\u5f0f */\n        .topic-discussion-container { margin: 0.5rem 0; }\n        .topic-description { margin-bottom: 0.6rem; }\n        .topic-description p { margin: 0; line-height: 1.4; font-size: 0.9rem; color: var(--text-secondary); }\n        \n        .chat-conversation { \n            background-color: rgba(251, 101, 30, 0.02);\n            border: 1px solid rgba(251, 101, 30, 0.08);\n            border-radius: 4px; \n            padding: 0.75rem; \n            margin-top: 0.5rem; \n        }\n        \n        .chat-message { \n            margin-bottom: 0.5rem; \n            padding: 0.3rem 0;\n            border-bottom: 1px solid rgba(251, 101, 30, 0.06);\n            position: relative;\n        }\n        .chat-message:last-child { \n            margin-bottom: 0; \n            border-bottom: none;\n        }\n        \n        .message-header {\n            display: flex;\n            align-items: baseline;\n            gap: 8px;\n            margin-bottom: 0.25rem;\n        }\n        \n        .message-author { \n            color: var(--highlight-name-color); \n            font-weight: 600; \n            font-size: 0.85rem;\n            flex-shrink: 0;\n        }\n        \n        .message-time {\n            color: var(--text-secondary);\n            font-size: 0.7rem;\n            font-weight: 400;\n            opacity: 0.6;\n            transition: opacity 0.2s ease;\n            flex-shrink: 0;\n            position: relative;\n        }\n        \n        .message-time:hover {\n            opacity: 1;\n        }\n        \n        .message-time.relative-time::after {\n            content: attr(data-full-time);\n            position: absolute;\n            bottom: 100%;\n            left: 50%;\n            transform: translateX(-50%);\n            background-color: rgba(0, 0, 0, 0.8);\n            color: white;\n            padding: 4px 8px;\n            border-radius: 4px;\n            font-size: 0.65rem;\n            white-space: nowrap;\n            opacity: 0;\n            pointer-events: none;\n            transition: opacity 0.2s ease;\n            z-index: 1000;\n            margin-bottom: 4px;\n        }\n        \n        .message-time.relative-time:hover::after {\n            opacity: 1;\n        }\n        \n        .message-content { \n            color: var(--text-primary); \n            line-height: 1.4; \n            font-size: 0.85rem;\n            display: block;\n            border-left: 2px solid var(--accent-primary);\n            background-color: var(--bg-secondary);\n            padding: 0.4rem 0.6rem;\n            border-radius: 2px;\n            margin-left: 0.75rem;\n        }\n        \n        /* \u65f6\u95f4\u5206\u7ec4\u548c\u8de8\u5ea6\u63d0\u793a\u6837\u5f0f */\n        .time-divider {\n            display: flex;\n            align-items: center;\n            margin: 1rem 0 0.75rem 0;\n            text-align: center;\n        }\n        \n        .time-divider::before,\n        .time-divider::after {\n            content: '';\n            flex: 1;\n            height: 1px;\n            background: linear-gradient(to right, transparent, rgba(251, 101, 30, 0.2), transparent);\n        }\n        \n        .time-divider-text {\n            padding: 0 12px;\n            color: var(--text-secondary);\n            font-size: 0.75rem;\n            font-weight: 500;\n            background-color: var(--bg-secondary);\n            border-radius: 12px;\n            padding: 2px 10px;\n            border: 1px solid rgba(251, 101, 30, 0.1);\n        }\n        \n        /* \u8bdd\u9898\u65f6\u95f4\u8de8\u5ea6\u63d0\u793a - \u4f4e\u4f18\u5148\u7ea7\u6241\u5e73\u8bbe\u8ba1 */\n        .topic-time-span {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 6px;\n            margin: 0.5rem 0 0.5rem 0;\n            padding: 4px 8px;\n            background-color: rgba(251, 101, 30, 0.08);\n            color: var(--text-secondary);\n            border: 1px solid rgba(251, 101, 30, 0.15);\n            border-radius: 8px;\n            font-size: 0.7rem;\n            font-weight: 400;\n            text-align: center;\n        }\n        \n        .topic-time-span .time-icon {\n            font-size: 0.7rem;\n            opacity: 0.7;\n        }\n        \n        .topic-time-span .time-range {\n            font-weight: 500;\n        }\n        \n        .topic-time-span .aggregated-label {\n            opacity: 0.8;\n            font-size: 0.65rem;\n        }\n        \n        /* \u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668 */\n        .time-jump-indicator {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 6px;\n            margin: 0.75rem 0;\n            padding: 6px 12px;\n            background-color: rgba(255, 152, 0, 0.1);\n            border: 1px solid rgba(255, 152, 0, 0.2);\n            border-radius: 12px;\n            color: #e65100;\n            font-size: 0.7rem;\n            font-weight: 500;\n            text-align: center;\n        }\n        \n        .time-jump-indicator .jump-icon {\n            font-size: 0.75rem;\n            opacity: 0.8;\n        }\n        \n        .time-jump-indicator .jump-text {\n            font-weight: 600;\n        }\n\n        .quote { position: relative; padding-left: 20px; margin: 10px 0; font-style: italic; color: var(--text-secondary); }\n        .quote::before { content: '\"'; position: absolute; left: 0; top: 0; font-size: 1.5rem; color: var(--accent-tertiary); }\n        .quote-author { text-align: right; font-size: 0.9rem; color: var(--accent-tertiary); margin-top: 5px; }\n\n        .link-item { display: flex; align-items: center; margin-bottom: 10px; padding: 8px; border-radius: 6px; background-color: var(--bg-tertiary); transition: background-color 0.2s ease; text-decoration: none; }\n        .link-item:hover { background-color: rgba(0, 123, 255, 0.1); }\n        .link-item a { text-decoration: none; color: inherit; display: flex; align-items: center; width: 100%; }\n        .link-item a:hover .link-title { text-decoration: underline; color: var(--accent-blue); }\n        .link-icon { margin-right: 10px; color: var(--accent-blue); }\n        .link-title { flex-grow: 1; color: var(--text-primary); }\n        .link-title.is-link { color: var(--accent-blue); }\n        .link-title.is-link:hover { text-decoration: underline; }\n\n        .user-stats-table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }\n        .user-stats-table th { text-align: center; padding: 0.75rem 0.5rem; border-bottom: 2px solid var(--accent-primary); color: var(--text-primary); font-weight: 600; font-size: 0.9rem; }\n        .user-stats-table td { padding: 0.75rem 0.5rem; border-bottom: 1px solid var(--bg-tertiary); color: var(--text-secondary); font-size: 0.85rem; vertical-align: top; text-align: left; }\n        .user-stats-table .user-name-col { width: 25%; font-weight: 500; word-break: break-all; }\n        .user-stats-table .message-count-col { text-align: center; width: 15%; white-space: nowrap; }\n        .user-stats-table .contribution-col { width: 60%; word-break: break-all; }\n        .user-stats-table tr:last-child td { border-bottom: none; }\n\n        .wordcloud { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; padding: 20px 0; }\n        .wordcloud-item { padding: 5px 10px; border-radius: 4px; font-weight: 500; transition: transform 0.2s ease; }\n        .wordcloud-item:hover { transform: scale(1.1); }\n        .size-1 { font-size: 0.9rem; color: var(--text-secondary); }\n        .size-2 { font-size: 1.1rem; color: var(--accent-cyan); }\n        .size-3 { font-size: 1.3rem; color: var(--accent-blue); }\n        .size-4 { font-size: 1.5rem; color: var(--accent-purple); }\n        .size-5 { font-size: 1.8rem; color: var(--accent-tertiary); }\n\n        /* \u67e5\u770b\u66f4\u591a\u804a\u5929\u8bb0\u5f55\u529f\u80fd\u6837\u5f0f - \u73b0\u4ee3\u5316\u91cd\u65b0\u8bbe\u8ba1 */\n        .expandable-messages-container {\n            margin-top: 0.75rem;\n        }\n        \n        /* \u5c55\u5f00\u533a\u57df\u5bb9\u5668 - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .expanded-messages-area {\n            background: linear-gradient(145deg, #f8fafc 0%, #f1f5f9 100%);\n            border: 1px solid #e2e8f0;\n            border-radius: 12px;\n            margin: 0.75rem 0;\n            padding: 0;\n            position: relative;\n            box-shadow: \n                0 1px 3px rgba(0, 0, 0, 0.05),\n                0 1px 2px rgba(0, 0, 0, 0.1);\n            overflow: hidden;\n            transition: all 0.3s ease;\n        }\n        \n        .expanded-messages-area::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            height: 3px;\n            background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);\n            border-radius: 12px 12px 0 0;\n        }\n        \n        /* \u5c55\u5f00\u533a\u57df\u6807\u9898 - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .expanded-area-header {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            padding: 1rem 1.25rem 0.75rem 1.25rem;\n            margin-bottom: 0;\n            background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);\n            border-bottom: 1px solid #e2e8f0;\n        }\n        \n        .expanded-area-title {\n            display: flex;\n            align-items: center;\n            gap: 8px;\n            color: #475569;\n            font-size: 0.875rem;\n            font-weight: 600;\n            letter-spacing: -0.025em;\n        }\n        \n        .expanded-area-title .icon {\n            font-size: 1rem;\n            color: #3b82f6;\n        }\n        \n        .expanded-area-progress {\n            color: #64748b;\n            font-size: 0.75rem;\n            background: linear-gradient(145deg, #f1f5f9, #e2e8f0);\n            padding: 4px 10px;\n            border-radius: 6px;\n            border: 1px solid #cbd5e1;\n            font-weight: 500;\n        }\n        \n        /* \u5c55\u5f00\u6d88\u606f\u5185\u5bb9\u533a\u57df */\n        .expanded-messages-content {\n            padding: 1rem 1.25rem;\n            max-height: 60vh;\n            overflow-y: auto;\n            scrollbar-width: thin;\n            scrollbar-color: #cbd5e1 #f1f5f9;\n        }\n        \n        .expanded-messages-content::-webkit-scrollbar {\n            width: 6px;\n        }\n        \n        .expanded-messages-content::-webkit-scrollbar-track {\n            background: #f1f5f9;\n            border-radius: 3px;\n        }\n        \n        .expanded-messages-content::-webkit-scrollbar-thumb {\n            background: #cbd5e1;\n            border-radius: 3px;\n        }\n        \n        .expanded-messages-content::-webkit-scrollbar-thumb:hover {\n            background: #94a3b8;\n        }\n        \n        /* \u5c55\u5f00\u6d88\u606f\u7684\u73b0\u4ee3\u5316\u6837\u5f0f */\n        .expanded-message {\n            background: #ffffff;\n            border: 1px solid #e2e8f0;\n            border-radius: 8px;\n            padding: 0.875rem;\n            margin-bottom: 0.75rem;\n            position: relative;\n            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n        }\n        \n        .expanded-message:hover {\n            background: #fefefe;\n            border-color: #cbd5e1;\n            transform: translateY(-1px);\n            box-shadow: \n                0 4px 6px rgba(0, 0, 0, 0.05),\n                0 1px 3px rgba(0, 0, 0, 0.1);\n        }\n        \n        .expanded-message:last-child {\n            margin-bottom: 0;\n        }\n        \n        /* \u5c55\u5f00\u6d88\u606f\u5934\u90e8 - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .expanded-message-header {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            margin-bottom: 0.5rem;\n        }\n        \n        .expanded-message-author {\n            color: #1e293b;\n            font-weight: 600;\n            font-size: 0.8rem;\n            display: flex;\n            align-items: center;\n            gap: 6px;\n        }\n        \n        .expanded-message-author::before {\n            content: '';\n            width: 6px;\n            height: 6px;\n            background: linear-gradient(135deg, #3b82f6, #8b5cf6);\n            border-radius: 50%;\n            flex-shrink: 0;\n        }\n        \n        .expanded-message-time {\n            color: #64748b;\n            font-size: 0.75rem;\n            background: #f8fafc;\n            padding: 2px 8px;\n            border-radius: 4px;\n            border: 1px solid #e2e8f0;\n            position: relative;\n            font-weight: 500;\n            transition: all 0.2s ease;\n        }\n        \n        .expanded-message-time:hover {\n            background: #f1f5f9;\n            border-color: #cbd5e1;\n        }\n        \n        .expanded-message-time::after {\n            content: attr(data-full-time);\n            position: absolute;\n            bottom: 100%;\n            right: 0;\n            transform: translateY(-4px);\n            background: #1e293b;\n            color: white;\n            padding: 4px 8px;\n            border-radius: 6px;\n            font-size: 0.7rem;\n            white-space: nowrap;\n            opacity: 0;\n            pointer-events: none;\n            transition: opacity 0.2s ease;\n            z-index: 1000;\n            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n        }\n        \n        .expanded-message-time:hover::after {\n            opacity: 1;\n        }\n        \n        /* \u5c55\u5f00\u6d88\u606f\u5185\u5bb9 - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .expanded-message-content {\n            color: #334155;\n            line-height: 1.6;\n            font-size: 0.875rem;\n            padding: 0.75rem;\n            background: #f8fafc;\n            border: 1px solid #e2e8f0;\n            border-radius: 6px;\n            margin-top: 0.5rem;\n            position: relative;\n        }\n        \n        .expanded-message-content::before {\n            content: '';\n            position: absolute;\n            left: 0;\n            top: 0;\n            bottom: 0;\n            width: 3px;\n            background: linear-gradient(180deg, #3b82f6, #8b5cf6);\n            border-radius: 0 0 0 6px;\n        }\n        \n        /* \u5c55\u5f00\u533a\u57df\u7684\u65f6\u95f4\u5206\u9694\u7b26 */\n        .expanded-time-divider {\n            display: flex;\n            align-items: center;\n            margin: 1rem 0 0.75rem 0;\n            text-align: center;\n        }\n        \n        .expanded-time-divider::before,\n        .expanded-time-divider::after {\n            content: '';\n            flex: 1;\n            height: 1px;\n            background: linear-gradient(to right, transparent, rgba(0, 126, 255, 0.3), transparent);\n        }\n        \n        .expanded-time-divider-text {\n            padding: 0 12px;\n            color: var(--accent-blue);\n            font-size: 0.75rem;\n            font-weight: 600;\n            background-color: rgba(255, 255, 255, 0.8);\n            border-radius: 12px;\n            padding: 4px 12px;\n            border: 1px solid rgba(0, 126, 255, 0.2);\n            box-shadow: 0 1px 3px rgba(0, 126, 255, 0.1);\n        }\n        \n        /* \u5c55\u5f00\u533a\u57df\u7684\u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668 */\n        .expanded-time-jump {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            gap: 6px;\n            margin: 0.75rem 0;\n            padding: 6px 12px;\n            background: linear-gradient(135deg, rgba(255, 152, 0, 0.1), rgba(255, 193, 7, 0.1));\n            border: 1px solid rgba(255, 152, 0, 0.3);\n            border-radius: 12px;\n            color: #e65100;\n            font-size: 0.7rem;\n            font-weight: 500;\n            text-align: center;\n        }\n        \n        .expanded-time-jump .jump-icon {\n            font-size: 0.75rem;\n        }\n        \n        /* \u6309\u94ae\u6837\u5f0f */\n        .load-more-section {\n            margin: 0.75rem 0;\n            display: flex;\n            justify-content: center;\n            position: relative;\n        }\n        \n        .load-more-section::before {\n            content: '';\n            position: absolute;\n            top: 50%;\n            left: 0;\n            right: 0;\n            height: 1px;\n            background: linear-gradient(to right, transparent, rgba(251, 101, 30, 0.2), transparent);\n            z-index: 1;\n        }\n        \n        .load-more-button {\n            background: linear-gradient(135deg, #3b82f6, #8b5cf6);\n            color: white;\n            border: none;\n            padding: 12px 24px;\n            border-radius: 12px;\n            font-size: 0.875rem;\n            font-weight: 600;\n            cursor: pointer;\n            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n            gap: 8px;\n            position: relative;\n            z-index: 2;\n            white-space: nowrap;\n            box-shadow: \n                0 4px 6px rgba(0, 0, 0, 0.05),\n                0 1px 3px rgba(0, 0, 0, 0.1);\n            min-width: 200px;\n            overflow: hidden;\n        }\n        \n        .load-more-button::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: -100%;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);\n            transition: left 0.6s ease;\n        }\n        \n        .load-more-button:hover::before {\n            left: 100%;\n        }\n        \n        .load-more-button:hover {\n            background: linear-gradient(135deg, #2563eb, #7c3aed);\n            transform: translateY(-1px);\n            box-shadow: \n                0 10px 15px rgba(0, 0, 0, 0.1),\n                0 4px 6px rgba(0, 0, 0, 0.05);\n        }\n        \n        .load-more-button .progress-text {\n            font-size: 0.75rem;\n            opacity: 0.9;\n            font-weight: 500;\n        }\n        \n        /* \u5c55\u5f00\u533a\u57df\u5e95\u90e8\u64cd\u4f5c\u533a */\n        .expanded-area-footer {\n            padding: 0.75rem 1.25rem;\n            background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);\n            border-top: 1px solid #e2e8f0;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n        }\n        \n        /* \u6536\u8d77\u6309\u94ae - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .collapse-all-button {\n            background: linear-gradient(135deg, #64748b, #475569);\n            color: white;\n            border: none;\n            padding: 8px 16px;\n            border-radius: 8px;\n            font-size: 0.8rem;\n            font-weight: 500;\n            cursor: pointer;\n            transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n            gap: 6px;\n            white-space: nowrap;\n            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n        }\n        \n        .collapse-all-button:hover {\n            background: linear-gradient(135deg, #475569, #334155);\n            transform: translateY(-1px);\n            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n        }\n        \n        /* \u52a8\u753b */\n        .expanded-message {\n            animation: expandedSlideIn 0.4s ease forwards;\n            opacity: 0;\n            transform: translateX(-10px);\n        }\n        \n        @keyframes expandedSlideIn {\n            from { \n                opacity: 0; \n                transform: translateX(-10px);\n            }\n            to { \n                opacity: 1; \n                transform: translateX(0);\n            }\n        }\n        \n        .expanded-message:nth-child(1) { animation-delay: 0.1s; }\n        .expanded-message:nth-child(2) { animation-delay: 0.15s; }\n        .expanded-message:nth-child(3) { animation-delay: 0.2s; }\n        .expanded-message:nth-child(4) { animation-delay: 0.25s; }\n        .expanded-message:nth-child(5) { animation-delay: 0.3s; }\n        .expanded-message:nth-child(6) { animation-delay: 0.35s; }\n        \n        /* \u5c55\u5f00\u533a\u57df\u52a8\u753b */\n        .expanded-messages-area {\n            animation: expandedAreaSlideIn 0.5s ease forwards;\n            opacity: 0;\n            transform: translateY(10px);\n        }\n        \n        @keyframes expandedAreaSlideIn {\n            from { \n                opacity: 0; \n                transform: translateY(10px);\n            }\n            to { \n                opacity: 1; \n                transform: translateY(0);\n            }\n        }\n        \n        @keyframes slideOutToTop {\n            from {\n                opacity: 1;\n                transform: translateY(0);\n                max-height: 1000px;\n            }\n            to {\n                opacity: 0;\n                transform: translateY(-20px);\n                max-height: 0;\n            }\n        }\n        \n        /* \u6298\u53e0\u52a8\u753b */\n        .collapsing {\n            overflow: hidden;\n            transition: all 0.5s ease;\n            transform-origin: top;\n        }\n        \n        .collapsing.collapse-out {\n            opacity: 0;\n            transform: scaleY(0);\n            margin-top: 0;\n            margin-bottom: 0;\n            padding-top: 0;\n            padding-bottom: 0;\n        }\n        \n        /* \u52a0\u8f7d\u6307\u793a\u5668 - \u73b0\u4ee3\u5316\u8bbe\u8ba1 */\n        .loading-indicator {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 1.25rem;\n            color: #475569;\n            font-size: 0.875rem;\n            font-weight: 500;\n            background: linear-gradient(145deg, #f8fafc 0%, #f1f5f9 100%);\n            border: 1px solid #e2e8f0;\n            border-radius: 12px;\n            margin: 0.75rem 0;\n            box-shadow: \n                0 1px 3px rgba(0, 0, 0, 0.05),\n                0 1px 2px rgba(0, 0, 0, 0.1);\n        }\n        \n        .loading-indicator .spinner {\n            width: 18px;\n            height: 18px;\n            border: 2px solid #e2e8f0;\n            border-top-color: #3b82f6;\n            border-radius: 50%;\n            animation: spin 1s linear infinite;\n            margin-right: 10px;\n        }\n        \n        @keyframes spin {\n            to { transform: rotate(360deg); }\n        }\n\n\n\n        /* \u64cd\u4f5c\u6309\u94ae\u533a\u57df\u6837\u5f0f */\n        .action-buttons-container {\n            display: flex; \n            flex-wrap: wrap; \n            justify-content: center; \n            gap: 10px; \n            margin: 20px 0;\n            padding-bottom: 20px; \n        }\n\n        .action-button {\n            background-color: var(--accent-primary);\n            color: white;\n            border: none;\n            padding: 10px 20px;\n            border-radius: var(--card-radius);\n            font-size: 1rem;\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.3s ease, transform 0.2s ease;\n            box-shadow: var(--card-shadow);\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n            flex-shrink: 0; \n        }\n        .action-button:hover { background-color: var(--accent-secondary); transform: translateY(-2px); }\n        .action-button i, .action-button .fas { margin-right: 8px; }\n\n        .footer { margin-top: 30px; text-align: center; color: var(--text-secondary); font-size: 0.9rem; }\n        #report-content-wrapper .footer { margin-top: 30px; padding-bottom: 20px; } \n\n        .mindmap-card {}\n        .mindmap-card h2 i { margin-right: 8px; }\n        .mindmap-controls { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; flex-wrap: wrap; }\n        .mindmap-controls button { background-color: var(--km-blue); color: white; border: none; padding: 8px 12px; border-radius: var(--card-radius); font-size: 0.9rem; cursor: pointer; transition: background-color 0.2s ease; font-weight: 500; display: inline-flex; align-items: center; justify-content: center; }\n        .mindmap-controls button:hover { background-color: #005bb5; }\n        .mindmap-controls button i { margin-right: 5px; }\n        .mindmap-controls .fullscreen-toggle-btn { margin-left: auto; } \n\n        .kityminder-container {\n            flex-grow: 1; \n            overflow: hidden; \n            background-color: #fff; \n            border-radius: 8px;\n            padding: 0; \n            border: 1px solid var(--bg-tertiary);\n            min-height: 400px; \n            position: relative; \n            transition: height 0.3s ease-in-out; \n            display: flex; \n            align-items: center; \n            justify-content: center; \n            touch-action: none;\n            font-family: var(--font-family); /* \u786e\u4fddKityMinder\u5bb9\u5668\u4f7f\u7528\u6b63\u786e\u7684\u5b57\u4f53 */\n        }\n        \n        /* \u786e\u4fddKityMinder SVG\u6587\u672c\u4f7f\u7528\u6b63\u786e\u7684\u5b57\u4f53 */\n        .kityminder-container svg text {\n            font-family: var(--font-family) !important;\n        }\n        .kityminder-container:empty::before { \n            content: \"\u601d\u7ef4\u5bfc\u56fe\u52a0\u8f7d\u4e2d\u6216\u5185\u5bb9\u4e3a\u7a7a...\";\n            color: var(--text-secondary); font-style: italic; text-align: center;\n            padding: 20px; display: flex; justify-content: center; align-items: center;\n            height: 100%; min-height: 150px;\n        }\n\n        #mermaid-main-feedback-message, #mermaid-modal-feedback-message {\n            text-align: center; margin-top: 15px; padding: 8px; border-radius: 4px;\n            font-weight: 500; font-size: 0.9rem; display: none; \n        }\n        #mermaid-main-feedback-message.success, #mermaid-modal-feedback-message.success {\n            color: #155724; background-color: #d4edda; border: 1px solid #c3e6cb; display: block;\n        }\n        #mermaid-main-feedback-message.error, #mermaid-modal-feedback-message.error {\n            color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; display: block;\n        }\n        #mermaid-main-feedback-message.info, #mermaid-modal-feedback-message.info {\n            color: #0c5460; background-color: #d1ecf1; border: 1px solid #bee5eb; display: block;\n        }\n\n        #feedback-message { text-align: center; margin-top: 10px; font-weight: 500; }\n        #feedback-message.success { color: green; }\n        #feedback-message.error { color: red; }\n\n        .modal-overlay {\n            position: fixed; top: 0; left: 0; width: 100%; height: 100%;\n            background-color: rgba(0, 0, 0, 0.75); \n            display: none; \n            justify-content: center; align-items: center; z-index: 1000; padding: 20px;\n        }\n        .modal-overlay.active { display: flex; } \n        .modal-content { \n            width: 95%; height: 90%; max-width: 1200px;\n        }\n        .modal-close-btn {\n            position: absolute; top: 15px; right: 20px; background: none; border: none;\n            font-size: 2.2rem; color: var(--text-secondary); cursor: pointer;\n            line-height: 1; padding: 5px; z-index: 1010; \n        }\n        .modal-close-btn:hover { color: var(--text-primary); }\n        #modalKityMinderContainer { \n            flex-grow: 1; min-height: 0; \n            display: flex;\n            align-items: center;\n            justify-content: center;\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; transform: translateY(20px); }\n            to { opacity: 1; transform: translateY(0); }\n        }\n        .card { animation: fadeIn 0.5s ease forwards; }\n        .main-card { animation-delay: 0.1s; }\n        /* \u52a8\u6001\u8bdd\u9898\u5361\u7247\u52a8\u753b\u5ef6\u8fdf - \u652f\u6301\u4efb\u610f\u6570\u91cf\u7684\u8bdd\u9898 */\n        .topic-cards-wrapper .topic-card:nth-child(1) { animation-delay: 0.2s; }\n        .topic-cards-wrapper .topic-card:nth-child(2) { animation-delay: 0.3s; }\n        .topic-cards-wrapper .topic-card:nth-child(3) { animation-delay: 0.4s; }\n        .topic-cards-wrapper .topic-card:nth-child(4) { animation-delay: 0.5s; }\n        .topic-cards-wrapper .topic-card:nth-child(5) { animation-delay: 0.6s; }\n        .topic-cards-wrapper .topic-card:nth-child(6) { animation-delay: 0.7s; }\n        .topic-cards-wrapper .topic-card:nth-child(7) { animation-delay: 0.8s; }\n        .topic-cards-wrapper .topic-card:nth-child(8) { animation-delay: 0.9s; }\n        .topic-cards-wrapper .topic-card:nth-child(9) { animation-delay: 1.0s; }\n        .topic-cards-wrapper .topic-card:nth-child(10) { animation-delay: 1.1s; }\n        .topic-cards-wrapper .topic-card:nth-child(n+11) { animation-delay: 1.2s; }\n        .mindmap-card-container .card { animation-delay: 0.6s; } \n        .quote-card { animation-delay: 0.7s; }\n        .links-card { animation-delay: 0.8s; }\n        .stats-wordcloud-row { animation: fadeIn 0.5s ease forwards; animation-delay: 0.9s; }\n        #report-content-wrapper .footer { animation: fadeIn 0.5s ease forwards; animation-delay: 1.0s; }\n        .action-buttons-container { animation: fadeIn 0.5s ease forwards; animation-delay: 1.1s; }\n\n        @media (max-width: 768px) {\n            body { padding: 10px; font-size: 14px; }\n            h1 { font-size: 1.8rem; }\n            h2 { font-size: 1.4rem; }\n            h3 { font-size: 1.1rem; }\n\n            .grid-container { \n                grid-template-columns: 1fr;\n                grid-template-areas:\n                    \"main\"\n                    \"topics\"\n                    \"mindmap\"\n                    \"quote\"\n                    \"links\"\n                    \"bottom_row\";\n            }\n\n\n\n            .stats-wordcloud-row { flex-direction: column; } \n            .stats-wordcloud-row > .card { width: 100%; flex-basis: auto !important; max-width: none; } \n            \n            .topic-cards-wrapper { \n                display: flex !important; \n                flex-direction: column; \n                gap: var(--grid-gap); \n                grid-template-columns: none !important; /* \u8986\u76d6\u684c\u9762\u7aef\u7684\u7f51\u683c\u8bbe\u7f6e */\n            } \n            .topic-card { \n                width: 100%; \n                min-height: auto; \n                grid-column: unset !important; /* \u79fb\u9664\u7f51\u683c\u5217\u8bbe\u7f6e */\n            }\n\n            .topic-card > .topic-card-content-wrapper { padding-bottom: 1rem; }\n            .topic-chat-count { position: static; margin-top: 10px; margin-bottom: 10px; text-align: left; }\n            \n            /* \u79fb\u52a8\u7aef\u8bdd\u9898\u8ba8\u8bba\u5bb9\u5668\u6837\u5f0f */\n            .topic-description p { font-size: 0.85rem; }\n            .chat-conversation { padding: 0.6rem; }\n            .chat-message { margin-bottom: 0.4rem; padding: 0.25rem 0; }\n            .message-header { gap: 6px; }\n            .message-author { font-size: 0.8rem; }\n            .message-time { font-size: 0.65rem; }\n            .message-content { \n                font-size: 0.8rem; \n                line-height: 1.3; \n                padding: 0.3rem 0.5rem; \n                margin-left: 0.5rem;\n            }\n            .time-divider-text { font-size: 0.7rem; padding: 1px 8px; }\n            \n            .topic-time-span { \n                margin: 0.3rem 0 0.4rem 0; \n                padding: 3px 6px; \n                font-size: 0.65rem; \n                gap: 4px;\n            }\n            .topic-time-span .time-icon { font-size: 0.65rem; }\n            .topic-time-span .aggregated-label { font-size: 0.6rem; }\n            \n            .time-jump-indicator { \n                margin: 0.6rem 0; \n                padding: 4px 10px; \n                font-size: 0.65rem; \n                gap: 4px;\n            }\n            .time-jump-indicator .jump-icon { font-size: 0.7rem; }\n            \n            .card-icon { font-size: 2.5rem; opacity:0.05; } \n            .mindmap-card .card-icon { bottom: 15px; right: 15px; }\n            \n            .user-stats-table th, .user-stats-table td { padding: 0.5rem 0.25rem; font-size: 0.8rem; }\n            .user-stats-table td { text-align: left; } \n            .user-stats-table .message-count-col { text-align: center; } \n            \n            .action-button { padding: 8px 15px; font-size: 0.9rem; } \n            .load-more-button { \n                padding: 6px 12px; \n                font-size: 0.75rem; \n                gap: 4px;\n            }\n            .load-more-button .progress-text { \n                font-size: 0.7rem; \n            }\n            .collapse-all-button { \n                padding: 6px 12px; \n                font-size: 0.75rem; \n                gap: 4px;\n            }\n            \n            /* \u79fb\u52a8\u7aef\u5c55\u5f00\u533a\u57df\u6837\u5f0f */\n            .expanded-messages-area {\n                margin: 0.75rem 0;\n                padding: 0.75rem;\n            }\n            \n            .expanded-area-header {\n                margin-bottom: 0.5rem;\n                padding-bottom: 0.4rem;\n            }\n            \n            .expanded-area-title {\n                font-size: 0.75rem;\n                gap: 4px;\n            }\n            \n            .expanded-area-progress {\n                font-size: 0.65rem;\n                padding: 1px 6px;\n            }\n            \n            .expanded-message {\n                padding: 0.6rem;\n                margin-bottom: 0.4rem;\n            }\n            \n            .expanded-message-header {\n                margin-bottom: 0.3rem;\n            }\n            \n            .expanded-message-author {\n                font-size: 0.75rem;\n                gap: 3px;\n            }\n            \n            .expanded-message-time {\n                font-size: 0.65rem;\n                padding: 1px 4px;\n            }\n            \n            .expanded-message-content {\n                font-size: 0.8rem;\n                line-height: 1.4;\n                padding: 0.3rem 0.5rem;\n            }\n            \n            .expanded-time-divider {\n                margin: 0.75rem 0 0.5rem 0;\n            }\n            \n            .expanded-time-divider-text {\n                font-size: 0.7rem;\n                padding: 3px 10px;\n            }\n            \n            .expanded-time-jump {\n                margin: 0.6rem 0;\n                padding: 4px 10px;\n                font-size: 0.65rem;\n                gap: 4px;\n            }\n\n            .mindmap-card-container .card { padding: 15px; } \n            .mindmap-controls button { font-size: 0.8rem; padding: 6px 10px; }\n            .modal-content { width: 100%; height: 95%; padding:15px; } \n            .modal-close-btn { font-size: 1.8rem; top:10px; right:10px;}\n\n\n        }\n\n        @media (max-width: 480px) {\n            .user-stats-table { display: block; overflow-x: auto; } \n            .user-stats-table th, .user-stats-table td { white-space: normal; } \n            .user-stats-table thead, .user-stats-table tbody, .user-stats-table tr { display: block; } \n            .user-stats-table tr { border-bottom: 1px solid var(--bg-tertiary); }\n            .user-stats-table th { display: none; } \n            .user-stats-table td { \n                display: block; text-align: left; \n                border-bottom: none; padding-left: 0.5rem; \n                width: 100% !important; \n            }\n            .user-stats-table td.message-count-col { text-align: left; } \n            .user-stats-table td::before { \n                content: attr(data-label); \n                font-weight: bold; \n                display: inline-block; \n                width: 100px; \n                margin-right: 10px; \n                color: var(--text-primary); \n                text-align: left; \n            }\n             .user-stats-table td.contribution-col::before {\n                width: auto; \n                margin-bottom: 5px; \n                display: block; \n            }\n             .user-stats-table td.contribution-col {\n                padding-left: 0.5rem; \n            }\n            .user-stats-table tr:last-child { border-bottom: none; }\n\n\n        }\n    </style>\n</head>\n<body>\n    <div id=\"report-content-wrapper\"> <div class=\"grid-container\">\n            <div class=\"card main-card\">\n                <h1 contenteditable=\"false\">[\u4e3b\u62a5\u544a\u6807\u9898 - \u4f8b\u5982\uff1aAI\u4ea7\u54c1\u56e2\u65e5\u62a5]</h1>\n                <div class=\"date\">[\u65e5\u671f - \u4f8b\u5982\uff1a2025\u5e745\u670819\u65e5]</div>\n                <div class=\"meta-info\">\n                    <!-- \u7edf\u8ba1\u6307\u6807\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u7edf\u8ba1\u6307\u6807 -->\n                    <!-- META_INFO_TEMPLATE_START -->\n                    <span><i class=\"fas fa-comment\"></i> \u6d88\u606f\u6570: [\u6570\u5b57]+</span>\n                    <span><i class=\"fas fa-users\"></i> \u6d3b\u8dc3\u7528\u6237: [\u6570\u5b57]+</span>\n                    <span><i class=\"fas fa-fire\"></i> \u70ed\u70b9\u8bdd\u9898: [\u6570\u5b57]</span>\n                    <!-- META_INFO_TEMPLATE_END -->\n                    <!-- \n                    \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                    1. \u5220\u9664 META_INFO_TEMPLATE_START \u548c META_INFO_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                    2. \u6839\u636e\u5b9e\u9645\u7edf\u8ba1\u6307\u6807\u6570\u91cf\uff0c\u4e3a\u6bcf\u4e2a\u6307\u6807\u751f\u6210\u4e00\u4e2a <span> \u5143\u7d20\n                    3. \u9009\u62e9\u5408\u9002\u7684\u56fe\u6807\uff1a\n                       - fas fa-comment (\u6d88\u606f\u6570)\n                       - fas fa-users (\u7528\u6237\u6570)\n                       - fas fa-fire (\u70ed\u70b9\u8bdd\u9898)\n                       - fas fa-chart-line (\u8d8b\u52bf\u6570\u636e)\n                       - fas fa-clock (\u65f6\u95f4\u7edf\u8ba1)\n                       - fas fa-thumbs-up (\u4e92\u52a8\u6570\u636e)\n                    4. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                    -->\n                </div>\n                <p class=\"summary\">[\u5728\u6b64\u5904\u586b\u5199\u5f53\u65e5\u8ba8\u8bba\u7684\u603b\u4f53\u6458\u8981\u3002\u4fdd\u6301\u7b80\u6d01\u660e\u4e86\uff0c\u7a81\u51fa\u7fa4\u7ec4\u7684\u4e3b\u8981\u4e3b\u9898\u548c\u6c1b\u56f4\u3002]</p>\n                <i class=\"fas fa-microchip card-icon\"></i>\n            </div>\n\n\n\n            <div class=\"topic-cards-wrapper\">\n                <!-- \u8bdd\u9898\u5361\u7247\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u8bdd\u9898\u5361\u7247 -->\n                <!-- TOPIC_CARDS_TEMPLATE_START -->\n                <div class=\"card topic-card\">\n                    <div class=\"topic-card-content-wrapper\">\n                        <h2 contenteditable=\"false\"><i class=\"fas fa-lightbulb\"></i> [\u4e3b\u98981\u6807\u9898]</h2>\n                        <div class=\"topic-category\">[\u5206\u7c7b1 - \u4f8b\u5982\uff1a\u5de5\u5177\u6280\u5de7]</div>\n                        <div class=\"topic-discussion-container\">\n                            <div class=\"topic-description\">\n                                <p>\u5173\u4e8e<span class=\"highlight-keyword\">[\u8bdd\u9898\u5173\u952e\u8bcd]</span>\u7684\u8ba8\u8bba\u5f15\u53d1\u4e86\u7fa4\u5185\u6210\u5458\u7684\u5e7f\u6cdb\u5173\u6ce8\uff0c\u5927\u5bb6\u4ece\u4e0d\u540c\u89d2\u5ea6\u5206\u4eab\u4e86\u5404\u81ea\u7684\u89c2\u70b9\u548c\u7ecf\u9a8c\u3002</p>\n                            </div>\n                            <div class=\"chat-conversation\">\n                                <!-- \u8bdd\u9898\u65f6\u95f4\u8de8\u5ea6\u63d0\u793a\u533a\u57df - AI\u6839\u636e\u9700\u8981\u52a8\u6001\u751f\u6210 -->\n                                <!-- TOPIC_TIME_SPAN_TEMPLATE_START -->\n                                <div class=\"topic-time-span\">\n                                    <span class=\"time-icon\">\u23f0</span>\n                                    <span class=\"time-range\">[\u5f00\u59cb\u65f6\u95f4] - [\u7ed3\u675f\u65f6\u95f4]</span>\n                                    <span class=\"aggregated-label\">\u2022 \u8be5\u8bdd\u9898\u7684\u805a\u5408\u5bf9\u8bdd</span>\n                                </div>\n                                <!-- TOPIC_TIME_SPAN_TEMPLATE_END -->\n                                \n                                <!-- \u804a\u5929\u5bf9\u8bdd\u6a21\u677f\u533a\u57df - AI\u5c06\u6839\u636e\u5b9e\u9645\u5bf9\u8bdd\u5185\u5bb9\u52a8\u6001\u751f\u6210 -->\n                                <!-- CONVERSATION_TEMPLATE_START -->\n                                <div class=\"chat-message\">\n                                    <div class=\"message-header\">\n                                        <span class=\"message-author\">@[\u7528\u6237\u540d]</span>\n                                        <span class=\"message-time relative-time\" data-full-time=\"[\u5b8c\u6574\u65f6\u95f4]\" title=\"[\u5b8c\u6574\u65f6\u95f4]\">[\u76f8\u5bf9\u65f6\u95f4]</span>\n                                    </div>\n                                    <span class=\"message-content\">[\u7528\u6237\u7684\u5177\u4f53\u804a\u5929\u5185\u5bb9]</span>\n                                </div>\n                                <!-- CONVERSATION_TEMPLATE_END -->\n                                <!-- \n                                AI\u4f7f\u7528\u8bf4\u660e\uff1a\n                                \u3010\u8bdd\u9898\u65f6\u95f4\u8de8\u5ea6\u63d0\u793a\u3011\n                                1. \u5220\u9664 TOPIC_TIME_SPAN_TEMPLATE_START \u548c TOPIC_TIME_SPAN_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                                2. \u5982\u679c\u8bdd\u9898\u8de8\u8d8a\u8d85\u8fc72\u5c0f\u65f6\uff08\u5982\u4e0a\u5348\u3001\u4e0b\u5348\u5206\u6563\u5bf9\u8bdd\uff09\uff0c\u751f\u6210\u65f6\u95f4\u8de8\u5ea6\u63d0\u793a\n                                3. \u65f6\u95f4\u683c\u5f0f\uff1a\u4e0a\u53489:30 - \u4e0b\u534815:45\u3001\u4eca\u592910:00 - 18:30\u7b49\n                                4. \u5982\u679c\u8bdd\u9898\u96c6\u4e2d\u5728\u77ed\u65f6\u95f4\u5185\uff08\u59821\u5c0f\u65f6\u5185\uff09\uff0c\u53ef\u7701\u7565\u6b64\u63d0\u793a\n                                \n                                \u3010\u804a\u5929\u5bf9\u8bdd\u3011\n                                1. \u5220\u9664 CONVERSATION_TEMPLATE_START \u548c CONVERSATION_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                                2. \u6839\u636e\u5b9e\u9645\u804a\u5929\u8bb0\u5f55\u52a8\u6001\u751f\u6210\u4efb\u610f\u6570\u91cf\u7684 chat-message div\n                                3. \u6bcf\u6761\u6d88\u606f\u5305\u542b message-header\uff08\u7528\u6237\u540d+\u65f6\u95f4\uff09\u548c message-content\uff08\u804a\u5929\u5185\u5bb9\uff09\n                                4. \u7528\u6237\u540d\u683c\u5f0f\uff1a@\u7528\u6237\u540d\n                                5. \u65f6\u95f4\u663e\u793a\uff1a\u76f8\u5bf9\u65f6\u95f4\uff08\u5982\"2\u5c0f\u65f6\u524d\"\uff09+ \u5b8c\u6574\u65f6\u95f4tooltip\n                                6. \u804a\u5929\u5185\u5bb9\u4f7f\u7528\u539f\u59cb\u5bf9\u8bdd\uff0c\u4fdd\u6301\u771f\u5b9e\u6027\n                                7. \u6570\u91cf\u7075\u6d3b\uff1a\u53ef\u4ee5\u662f2\u6761\u30015\u6761\u30018\u6761\u7b49\u4efb\u610f\u6570\u91cf\n                                8. \u6309\u65f6\u95f4\u987a\u5e8f\u6392\u5217\uff0c\u4f53\u73b0\u5bf9\u8bdd\u7684\u8fde\u8d2f\u6027\n                                9. \u65f6\u95f4\u683c\u5f0f\uff1a\u76f8\u5bf9\u65f6\u95f4\u7528\u4e8e\u663e\u793a\uff0c\u5b8c\u6574\u65f6\u95f4\u7528\u4e8edata-full-time\u548ctitle\u5c5e\u6027\n                                10. \u65f6\u95f4\u8df3\u8dc3\u5904\u7406\uff1a\u5f53\u76f8\u90bb\u6d88\u606f\u65f6\u95f4\u5dee\u8d85\u8fc730\u5206\u949f\u65f6\uff0c\u81ea\u52a8\u63d2\u5165\u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668\n                                -->\n                            </div>\n                            <!-- \u67e5\u770b\u66f4\u591a\u804a\u5929\u8bb0\u5f55\u529f\u80fd - \u52a8\u6001\u63d2\u5165\u533a\u57df -->\n                            <div class=\"expandable-messages-container\" data-topic-id=\"[\u8bdd\u9898ID]\" data-total-messages=\"[\u603b\u6570\u91cf]\">\n                                <!-- \u67e5\u770b\u66f4\u591a\u6309\u94ae\u548c\u65b0\u6d88\u606f\u5c06\u52a8\u6001\u63d2\u5165\u5230\u8fd9\u91cc -->\n                            </div>\n                        </div>\n                        <div class=\"topic-tags\">\n                            <span class=\"tag\">[ChatGPT]</span>\n                            <span class=\"tag\">[\u6280\u672f\u95ee\u9898]</span>\n                            <span class=\"tag\">[\u6027\u80fd\u4f18\u5316]</span>\n                        </div>\n                        <div class=\"topic-chat-count\"><i class=\"fas fa-comments\"></i> \u76f8\u5173\u6d88\u606f: [\u603b\u6570\u91cf]\u6761</div>\n                    </div>\n                    <i class=\"fas fa-tools card-icon\"></i>\n                </div>\n               \n                <!-- TOPIC_CARDS_TEMPLATE_END -->\n                <!-- \n                \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                1. \u5220\u9664 TOPIC_CARDS_TEMPLATE_START \u548c TOPIC_CARDS_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                2. \u6839\u636e\u5b9e\u9645\u8bdd\u9898\u6570\u91cf\uff0c\u4e3a\u6bcf\u4e2a\u8bdd\u9898\u751f\u6210\u4e00\u4e2a\u5b8c\u6574\u7684 topic-card div\n                3. \u6bcf\u4e2a\u8bdd\u9898\u5361\u7247\u4f7f\u7528\u5408\u9002\u7684\u56fe\u6807\uff1a\n                   - fas fa-lightbulb (\u60f3\u6cd5/\u8ba8\u8bba)\n                   - fas fa-rocket (\u65b0\u529f\u80fd/\u4ea7\u54c1)  \n                   - fas fa-tools (\u5de5\u5177/\u6280\u672f)\n                   - fas fa-comments-dollar (\u5546\u4e1a/\u76c8\u5229)\n                   - fas fa-star (\u70ed\u95e8\u8bdd\u9898)\n                   - fas fa-fire (\u70ed\u70b9\u4e8b\u4ef6)\n                   - fas fa-cogs (\u6280\u672f\u65b9\u6848)\n                   - fas fa-users (\u793e\u533a/\u7528\u6237)\n                4. \u8bdd\u9898\u6807\u7b7e(topic-tags)\u5e94\u8be5\u4f7f\u7528 topic_tags \u5b57\u6bb5\uff0c\u663e\u793a2-4\u4e2a\u7cbe\u51c6\u7684\u4e1a\u5185\u70ed\u8bcd\n                5. \u6807\u7b7e\u793a\u4f8b\uff1aChatGPT\u3001Claude\u3001Cursor\u3001\u6280\u672f\u95ee\u9898\u3001\u6027\u80fd\u4f18\u5316\u3001\u7528\u6237\u4f53\u9a8c\u7b49\n                6. \u76f8\u5173\u6d88\u606f\u6570\u91cf\uff1a\u663e\u793a\u8be5\u8bdd\u9898\u76f8\u5173\u7684\u603b\u804a\u5929\u6d88\u606f\u6570\uff08\u5982\uff1a52\u6761\u3001128\u6761\u7b49\uff09\n                7. \u8ba8\u8bba\u5bb9\u5668\u4e2d\u7684\u804a\u5929\u5f15\u7528\uff1a\u9009\u62e93-6\u6761\u6700\u5177\u4ee3\u8868\u6027\u7684\u6d88\u606f\u8fdb\u884c\u5c55\u793a\n                8. \u6ce8\u610f\u533a\u5206\uff1a\u603b\u6d88\u606f\u6570 \u2260 \u5f15\u7528\u5c55\u793a\u6570\uff0c\u524d\u8005\u901a\u5e38\u8fdc\u5927\u4e8e\u540e\u8005\n                9. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                10. \u652f\u6301\u4efb\u610f\u6570\u91cf\u7684\u8bdd\u9898\u5361\u7247\uff08\u52a8\u753b\u5ef6\u8fdf\u5df2\u4f18\u5316\u652f\u63011-10+\u4e2a\u8bdd\u9898\uff09\n                11. \"\u67e5\u770b\u66f4\u591a\"\u529f\u80fd\u914d\u7f6e\uff1a\n                    - \u5c06expandable-messages-container\u7684data-topic-id\u8bbe\u7f6e\u4e3a\u8bdd\u9898\u7684topic_id\n                    - \u5c06expandable-messages-container\u7684data-total-messages\u8bbe\u7f6e\u4e3a\u8be5\u8bdd\u9898\u7684\u603b\u6d88\u606f\u6570\u91cf\n                    - \u5728JavaScript\u7684TOPIC_MESSAGES_DATA_PLACEHOLDER\u533a\u57df\u586b\u5145\u5b8c\u6574\u7684\u6d88\u606f\u6570\u636e\n                    - \u6309\u94ae\u548c\u6d88\u606f\u5c06\u7531JavaScript\u81ea\u52a8\u751f\u6210\uff0c\u65e0\u9700\u624b\u52a8\u6dfb\u52a0HTML\n                -->\n            </div>\n\n            <div class=\"mindmap-card-container card\">\n                <div class=\"mindmap-card\">\n                    <h2><i class=\"fas fa-sitemap\"></i> \u6838\u5fc3\u6982\u5ff5\u5173\u7cfb\u56fe</h2>\n                    <div class=\"mindmap-controls\">\n                        <button id=\"zoomInBtn\"><i class=\"fas fa-search-plus\"></i> \u653e\u5927</button>\n                        <button id=\"zoomOutBtn\"><i class=\"fas fa-search-minus\"></i> \u7f29\u5c0f</button>\n                        <button id=\"downloadDiagramBtn\"><i class=\"fas fa-download\"></i> \u4e0b\u8f7d SVG</button>\n                        <button id=\"fullscreenOpenBtn\" class=\"fullscreen-toggle-btn\"><i class=\"fas fa-expand\"></i> \u5168\u5c4f</button>\n                    </div>\n                    <div class=\"kityminder-container\" id=\"mainKityMinderContainer\">\n                        </div>\n                    <div id=\"mermaid-main-feedback-message\"></div> <i class=\"fas fa-project-diagram card-icon\"></i>\n                </div>\n            </div>\n\n            <div class=\"card quote-card\">\n                <h2 contenteditable=\"false\"><i class=\"fas fa-quote-left\"></i> \u7cbe\u5f69\u5f15\u7528</h2>\n                <!-- \u7cbe\u5f69\u5f15\u7528\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u5f15\u7528 -->\n                <!-- QUOTES_TEMPLATE_START -->\n                <div class=\"quote\">\n                    \"[\u5f15\u8a00\u5185\u5bb9]\"\n                    <div class=\"quote-author\">- @[\u53d1\u8a00\u4eba]</div>\n                </div>\n                <!-- QUOTES_TEMPLATE_END -->\n                <!-- \n                \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                1. \u5220\u9664 QUOTES_TEMPLATE_START \u548c QUOTES_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                2. \u6839\u636e\u5b9e\u9645\u7cbe\u5f69\u5f15\u7528\u6570\u91cf\uff0c\u4e3a\u6bcf\u6761\u5f15\u7528\u751f\u6210\u4e00\u4e2a\u5b8c\u6574\u7684 quote div\n                3. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                -->\n                <i class=\"fas fa-comment-dots card-icon\"></i>\n            </div>\n\n            <div class=\"card links-card\">\n                <h2 contenteditable=\"false\"><i class=\"fas fa-link\"></i> \u91cd\u8981\u94fe\u63a5\u4e0e\u8d44\u6e90</h2>\n                <!-- \u94fe\u63a5\u8d44\u6e90\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u94fe\u63a5 -->\n                <!-- LINKS_TEMPLATE_START -->\n                <div class=\"link-item\">\n                    <a href=\"[URL\u94fe\u63a5]\" target=\"_blank\">\n                        <i class=\"fas fa-external-link-alt link-icon\"></i>\n                        <span class=\"link-title\">[\u94fe\u63a5\u6807\u9898]</span>\n                    </a>\n                </div>\n                <div class=\"link-item\">\n                     <i class=\"fas fa-file link-icon\"></i> <span class=\"link-title\">[\u6587\u6863\u6807\u9898]</span>\n                </div>\n                <!-- LINKS_TEMPLATE_END -->\n                <!-- \n                \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                1. \u5220\u9664 LINKS_TEMPLATE_START \u548c LINKS_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                2. \u6839\u636e\u5b9e\u9645\u94fe\u63a5\u6570\u91cf\uff0c\u4e3a\u6bcf\u4e2a\u94fe\u63a5\u751f\u6210\u4e00\u4e2a\u5b8c\u6574\u7684 link-item div\n                3. \u5bf9\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u4f7f\u7528\u5e26 <a href> \u7684\u683c\u5f0f\n                4. \u5bf9\u4e8e\u6587\u6863/\u5185\u90e8\u8d44\u6e90\uff0c\u4f7f\u7528\u4e0d\u5e26\u94fe\u63a5\u7684\u683c\u5f0f\n                5. \u9009\u62e9\u5408\u9002\u7684\u56fe\u6807\uff1afas fa-external-link-alt (\u5916\u90e8\u94fe\u63a5) \u6216 fas fa-file (\u6587\u6863)\n                6. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                7. \u91cd\u8981\uff0c\u5168\u6570\u751f\u6210\uff0c\u4e0d\u8981\u9057\u6f0f\n                -->\n                <i class=\"fas fa-share-alt card-icon\"></i>\n            </div>\n\n            <div class=\"stats-wordcloud-row\">\n                <div class=\"card stats-card\">\n                    <h2 contenteditable=\"false\"><i class=\"fas fa-chart-line\"></i> \u6d3b\u8dc3\u4e4b\u661f</h2>\n                    <table class=\"user-stats-table\">\n                        <thead>\n                            <tr>\n                                <th class=\"user-name-col\">\u7528\u6237</th>\n                                <th class=\"message-count-col\">\u53d1\u8a00\u6570</th>\n                                <th class=\"contribution-col\">\u4e3b\u8981\u8d21\u732e</th>\n                            </tr>\n                        </thead>\n                        <tbody>\n                            <!-- \u6d3b\u8dc3\u7528\u6237\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u7528\u6237\u884c -->\n                            <!-- USERS_TEMPLATE_START -->\n                            <tr>\n                                <td data-label=\"\u7528\u6237\" class=\"user-name-col\">@[\u7528\u6237\u540d]</td>\n                                <td data-label=\"\u53d1\u8a00\u6570\" class=\"message-count-col\">[\u53d1\u8a00\u6570]+</td>\n                                <td data-label=\"\u4e3b\u8981\u8d21\u732e\" class=\"contribution-col\">[\u7528\u6237\u4e3b\u8981\u8d21\u732e\u5185\u5bb9]</td>\n                            </tr>\n                            <!-- USERS_TEMPLATE_END -->\n                            <!-- \n                            \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                            1. \u5220\u9664 USERS_TEMPLATE_START \u548c USERS_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                            2. \u6839\u636e\u5b9e\u9645\u6d3b\u8dc3\u7528\u6237\u6570\u91cf\uff0c\u4e3a\u6bcf\u4e2a\u7528\u6237\u751f\u6210\u4e00\u4e2a\u5b8c\u6574\u7684 <tr> \u884c\n                            3. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                            4. \u4fdd\u6301 data-label \u5c5e\u6027\u7528\u4e8e\u79fb\u52a8\u7aef\u663e\u793a\n                            -->\n                        </tbody>\n                    </table>\n                    <i class=\"fas fa-user-friends card-icon\"></i>\n                </div>\n\n                <div class=\"card wordcloud-card\">\n                    <h2 contenteditable=\"false\"><i class=\"fas fa-tags\"></i> \u8bdd\u9898\u6807\u7b7e\u4e91</h2>\n                    <div class=\"wordcloud\">\n                        <!-- \u8bdd\u9898\u6807\u7b7e\u4e91\u6a21\u677f\u533a\u57df - AI\u5c06\u5728\u6b64\u5904\u52a8\u6001\u751f\u6210\u6807\u7b7e\u9879 -->\n                        <!-- WORDCLOUD_TEMPLATE_START -->\n                        <span class=\"wordcloud-item size-5\">[ChatGPT]</span>\n                        <span class=\"wordcloud-item size-4\">[\u6280\u672f\u95ee\u9898]</span>\n                        <span class=\"wordcloud-item size-3\">[\u6027\u80fd\u4f18\u5316]</span>\n                        <span class=\"wordcloud-item size-2\">[\u7528\u6237\u4f53\u9a8c]</span>\n                        <!-- WORDCLOUD_TEMPLATE_END -->\n                        <!-- \n                        \u8bf4\u660e\uff1aAI\u5e94\u8be5\uff1a\n                        1. \u5220\u9664 WORDCLOUD_TEMPLATE_START \u548c WORDCLOUD_TEMPLATE_END \u4e4b\u95f4\u7684\u6a21\u677f\u5185\u5bb9\n                        2. \u6839\u636e\u8bdd\u9898\u6807\u7b7e\u51fa\u73b0\u9891\u6b21\uff0c\u4e3a\u6bcf\u4e2a\u6807\u7b7e\u751f\u6210\u4e00\u4e2a wordcloud-item span\n                        3. \u4f7f\u7528\u5408\u9002\u7684\u5c3a\u5bf8\u7c7b\uff1asize-1 (\u6700\u5c0f) \u5230 size-5 (\u6700\u5927)\n                        4. \u6309\u51fa\u73b0\u9891\u6b21\u6392\u5e8f\uff0c\u70ed\u95e8\u6807\u7b7e\u4f7f\u7528\u66f4\u5927\u7684\u5c3a\u5bf8\n                        5. \u4f18\u5148\u663e\u793a\u4e1a\u5185\u70ed\u8bcd\uff1aChatGPT\u3001Claude\u3001Cursor\u3001Agent\u7b49\n                        6. \u4fdd\u6301\u76f8\u540c\u7684HTML\u7ed3\u6784\u548cCSS\u7c7b\u540d\n                        -->\n                    </div>\n                    <i class=\"fas fa-tags card-icon\"></i>\n                </div>\n            </div>\n\n\n            \n\n\n        </div> <div class=\"footer\">\n            <p>\u6570\u636e\u6765\u6e90\uff1a[\u60a8\u7684\u6570\u636e\u6765\u6e90 - \u4f8b\u5982\uff1aAI\u4ea7\u54c1\u56e2\u804a\u5929\u8bb0\u5f55] | \u751f\u6210\u65e5\u671f\uff1a[\u751f\u6210\u65e5\u671f] | \u7edf\u8ba1\u5468\u671f\uff1a[\u7edf\u8ba1\u5468\u671f - \u4f8b\u5982\uff1a[\u65e5\u671f]\u5168\u5929]</p>\n            <p>[\u514d\u8d23\u58f0\u660e - \u4f8b\u5982\uff1a\u672c\u62a5\u544a\u7531AI\u81ea\u52a8\u751f\u6210\uff0c\u4ec5\u4f9b\u53c2\u8003\uff0c\u4e0d\u4ee3\u8868\u6240\u6709\u7fa4\u6210\u5458\u89c2\u70b9\u3002]</p>\n        </div>\n    </div> <div class=\"action-buttons-container\">\n        <button id=\"screenshotToClipboardBtn\" class=\"action-button\"><i class=\"fas fa-clipboard\"></i> \u62f7\u8d1d\u622a\u56fe</button>\n        <button id=\"screenshotDownloadBtn\" class=\"action-button\"><i class=\"fas fa-camera\"></i> \u4e0b\u8f7d\u622a\u56fe</button>\n        <button id=\"copyHtmlBtn\" class=\"action-button\"><i class=\"fas fa-copy\"></i> \u62f7\u8d1d\u6e90\u7801</button>\n        <button id=\"downloadHtmlBtn\" class=\"action-button\"><i class=\"fas fa-download\"></i> \u4e0b\u8f7d\u62a5\u544a</button>\n        <button id=\"viewHistoricalReportBtn\" class=\"action-button\"><i class=\"fas fa-history\"></i> \u67e5\u770b\u5386\u53f2\u65e5\u62a5</button>\n        <button id=\"viewPreviousReportBtn\" class=\"action-button\"><i class=\"fas fa-calendar-alt\"></i> \u67e5\u770b\u6628\u65e5\u65e5\u62a5</button>\n    </div>\n    <div id=\"feedback-message\"></div> <div id=\"fullscreenModal\" class=\"modal-overlay\">\n        <div class=\"modal-content card\"> <button id=\"fullscreenCloseBtn\" class=\"modal-close-btn\" aria-label=\"\u5173\u95ed\u5168\u5c4f\">&times;</button>\n            <h2 style=\"padding-top: 15px;\"><i class=\"fas fa-sitemap\"></i> \u6838\u5fc3\u6982\u5ff5\u5173\u7cfb\u56fe (\u5168\u5c4f)</h2>\n            <div class=\"mindmap-controls\">\n                <button id=\"modalZoomInBtn\"><i class=\"fas fa-search-plus\"></i> \u653e\u5927</button>\n                <button id=\"modalZoomOutBtn\"><i class=\"fas fa-search-minus\"></i> \u7f29\u5c0f</button>\n                <button id=\"modalDownloadDiagramBtn\"><i class=\"fas fa-download\"></i> \u4e0b\u8f7d SVG</button>\n            </div>\n            <div class=\"kityminder-container\" id=\"modalKityMinderContainer\">\n                </div>\n            <div id=\"mermaid-modal-feedback-message\"></div> </div>\n    </div>\n\n    <script>\n    // --- KityMinder (\u601d\u7ef4\u5bfc\u56fe) JavaScript \u5f00\u59cb ---\n\n    // --- \u5168\u5c40\u53d8\u91cf ---\n    let mainKM, modalKM; // \u5206\u522b\u5b58\u50a8\u4e3b\u89c6\u56fe\u548c\u6a21\u6001\u6846\u89c6\u56fe\u7684KityMinder\u5b9e\u4f8b\n    const kityminderData = { // \u601d\u7ef4\u5bfc\u56fe\u6570\u636e\u7ed3\u6784\n        root: { // \u6839\u8282\u70b9\u5b9a\u4e49\n            data: { text: \"\u6838\u5fc3\u6982\u5ff5\", expandState: \"expand\" }, // \u6839\u8282\u70b9\u663e\u793a\u6587\u672c\u548c\u9ed8\u8ba4\u5c55\u5f00\u72b6\u6001\n            children: [ // \u5b50\u8282\u70b9\u6570\u7ec4\uff0c\u6dfb\u52a0\u793a\u4f8b\u6570\u636e\n                { data: { text: \"\u4e3b\u8981\u8bae\u9898A\", expandState: \"expand\" }, children: [\n                    { data: { text: \"\u8bae\u9898A-1\" } },\n                    { data: { text: \"\u8bae\u9898A-2\", expandState: \"collapse\" }, children: [ // \u9ed8\u8ba4\u6298\u53e0\u7684\u5b50\u8282\u70b9\n                        { data: { text: \"A-2-\u8be6\u60c51\" } },\n                        { data: { text: \"A-2-\u8be6\u60c52\" } }\n                    ]},\n                    { data: { text: \"\u8bae\u9898A-3\" } }\n                ]},\n                { data: { text: \"\u4e3b\u8981\u8bae\u9898B\" } },\n                { data: { text: \"\u4e3b\u8981\u8bae\u9898C\", expandState: \"expand\" }, children: [\n                    { data: { text: \"\u8bae\u9898C-1\" } },\n                    { data: { text: \"\u8bae\u9898C-2\" } }\n                ]}\n            ]\n        },\n        template: 'default', // \u4f7f\u7528KityMinder\u7684\u9ed8\u8ba4\u6a21\u677f\n        theme: 'customPictureTheme', // \u5e94\u7528\u81ea\u5b9a\u4e49\u4e3b\u9898\n        version: \"1.4.50\" // KityMinder\u7248\u672c\u53f7\n    };\n\n    // --- DOM\u5143\u7d20\u83b7\u53d6 ---\n    const mainZoomInBtn = document.getElementById('zoomInBtn');\n    const mainZoomOutBtn = document.getElementById('zoomOutBtn');\n    const mainDownloadDiagramBtn = document.getElementById('downloadDiagramBtn');\n    const mainKityMinderContainer = document.getElementById('mainKityMinderContainer');\n    const mermaidMainFeedbackMessage = document.getElementById('mermaid-main-feedback-message');\n    const fullscreenOpenBtn = document.getElementById('fullscreenOpenBtn');\n    const fullscreenModal = document.getElementById('fullscreenModal');\n    const fullscreenCloseBtn = document.getElementById('fullscreenCloseBtn');\n    const modalKityMinderContainer = document.getElementById('modalKityMinderContainer');\n    const modalZoomInBtn = document.getElementById('modalZoomInBtn');\n    const modalZoomOutBtn = document.getElementById('modalZoomOutBtn');\n    const modalDownloadDiagramBtn = document.getElementById('modalDownloadDiagramBtn');\n    const mermaidModalFeedbackMessage = document.getElementById('mermaid-modal-feedback-message');\n\n    // --- KityMinder \u81ea\u5b9a\u4e49\u4e3b\u9898\u5b9a\u4e49 ---\n    if (typeof kityminder !== 'undefined' && kityminder.Theme) {\n        kityminder.Theme.register('customPictureTheme', {\n            'root-color': 'var(--km-root-text, #333333)', 'root-background': 'var(--km-root-bg, #f0f0f0)',\n            'root-stroke': 'var(--km-root-bg)', 'root-font-size': 19, 'root-font-weight': 'bold', 'root-font-family': 'Inter, \"SF Pro Display\", \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Hiragino Sans GB\", \"Noto Sans CJK SC\", sans-serif',\n            'root-padding': [14, 24], 'root-margin': [10, 2], 'root-radius': 5, 'root-space': 8, 'root-stroke-width': 1,\n            'main-color': 'var(--km-node-text, #ffffff)', 'main-background': 'var(--km-orange)',\n            'main-stroke': 'var(--km-orange)', 'main-font-size': 19, 'main-font-weight': 'bold', 'main-font-family': 'Inter, \"SF Pro Display\", \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Hiragino Sans GB\", \"Noto Sans CJK SC\", sans-serif',\n            'main-padding': [10, 20], 'main-margin': [10, 2], 'main-radius': 5, 'main-space': 4, 'main-stroke-width': 1,\n            'sub-color': 'var(--km-node-text, #ffffff)', 'sub-background': 'var(--km-blue)',\n            'sub-stroke': 'var(--km-blue)', 'sub-font-size': 19, 'sub-font-weight': 'bold', 'sub-font-family': 'Inter, \"SF Pro Display\", \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Hiragino Sans GB\", \"Noto Sans CJK SC\", sans-serif',\n            'sub-padding': [8, 16], 'sub-margin': [8, 15], 'sub-radius': 5, 'sub-space': 4, 'sub-stroke-width': 1,\n            'connect-color': 'var(--km-connect-color, #777777)', 'connect-width': 1.5, 'main-connect-width': 1.5,\n            'selected-connect-color': 'var(--km-orange)', 'selected-connect-width': 2.5,\n            'background': \"#ffffff\", 'text-selection-color': 'var(--km-blue)',\n            'node-padding': [6, 12], 'node-margin': [8, 15], 'node-radius': 5, 'font-size': 19, 'font-weight': 'bold', 'font-family': 'Inter, \"SF Pro Display\", \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Hiragino Sans GB\", \"Noto Sans CJK SC\", sans-serif',\n        });\n        console.log(\"KityMinder Debug: \u81ea\u5b9a\u4e49\u4e3b\u9898 'customPictureTheme' \u5df2\u6ce8\u518c\u3002\");\n    } else {\n        console.error(\"KityMinder Debug: kityminder \u6216 kityminder.Theme \u672a\u5b9a\u4e49\u3002\u65e0\u6cd5\u6ce8\u518c\u81ea\u5b9a\u4e49\u4e3b\u9898\u3002\");\n    }\n\n    // --- KityMinder \u8f85\u52a9\u51fd\u6570 ---\n    function showKityMinderFeedback(element, message, type = 'info', duration = 3000) {\n        if (element) {\n            element.textContent = message; element.className = type; element.style.display = 'block';\n            setTimeout(() => { element.textContent = ''; element.className = ''; element.style.display = 'none'; }, duration);\n        }\n    }\n\n    async function downloadKityMinderAsSVG(kmInstance, baseFilename, feedbackElem) {\n        if (!kmInstance || typeof kmInstance.exportData !== 'function' || !kmInstance.getRoot || !kmInstance.getRoot()) {\n            showKityMinderFeedback(feedbackElem, '\u65e0\u6cd5\u4e0b\u8f7d\uff1a\u601d\u7ef4\u5bfc\u56fe\u5b9e\u4f8b\u65e0\u6548\u6216\u5185\u5bb9\u4e3a\u7a7a\u3002', 'error'); return;\n        }\n        try {\n            if (typeof kmInstance.refresh === 'function') {\n                kmInstance.refresh(); await new Promise(resolve => setTimeout(resolve, 100));\n            }\n            const svgExportPromise = kmInstance.exportData('svg');\n            if (!svgExportPromise || typeof svgExportPromise.then !== 'function') {\n                showKityMinderFeedback(feedbackElem, '\u4e0b\u8f7d\u5931\u8d25\uff1a\u5bfc\u51fa\u51fd\u6570\u884c\u4e3a\u5f02\u5e38\u3002', 'error'); return;\n            }\n            const exportedObject = await svgExportPromise;\n            let svgString = \"\";\n            if (typeof exportedObject === 'string') { svgString = exportedObject; }\n            else if (exportedObject && typeof exportedObject.data === 'string') { svgString = exportedObject.data; }\n            else {\n                const paper = kmInstance.getPaper && kmInstance.getPaper();\n                const svgNode = paper && paper.container && paper.container.firstChild;\n                if (svgNode && typeof XMLSerializer !== 'undefined') { svgString = new XMLSerializer().serializeToString(svgNode); }\n            }\n            if (svgString && svgString.length > 100) { // Basic check for non-empty SVG\n                const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });\n                const url = URL.createObjectURL(blob); const link = document.createElement('a');\n                link.href = url; link.download = `${baseFilename}.svg`; document.body.appendChild(link);\n                link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);\n                showKityMinderFeedback(feedbackElem, `${baseFilename}.svg \u5df2\u4e0b\u8f7d!`, 'success');\n            } else {\n                showKityMinderFeedback(feedbackElem, `\u4e0b\u8f7d\u5931\u8d25\uff1a\u751f\u6210\u7684SVG\u5185\u5bb9\u4e3a\u7a7a\u6216\u65e0\u6548\u3002\u8bf7\u786e\u4fdd\u5bfc\u56fe\u5df2\u6b63\u786e\u6e32\u67d3\u3002`, 'error');\n            }\n        } catch (error) {\n            console.error(`\u4e0b\u8f7d ${baseFilename}.svg \u5931\u8d25:`, error);\n            showKityMinderFeedback(feedbackElem, `\u4e0b\u8f7d ${baseFilename}.svg \u5931\u8d25: ${error.message || error}`, 'error');\n        }\n    }\n    \n    function setupKityMinderTouchEvents(km, container) {\n        let isPanning = false;\n        let lastPanX, lastPanY;\n\n        let isPinching = false;\n        let initialPinchDistance = 0;\n        let lastPinchZoomValue = km.getZoomValue(); \n\n        container.addEventListener('touchstart', (e) => {\n            if (e.touches.length === 1) {\n                isPanning = true;\n                isPinching = false; \n                const touch = e.touches[0];\n                lastPanX = touch.clientX;\n                lastPanY = touch.clientY;\n                e.preventDefault(); \n            } else if (e.touches.length === 2) {\n                isPinching = true;\n                isPanning = false; \n                const touch1 = e.touches[0];\n                const touch2 = e.touches[1];\n                initialPinchDistance = Math.hypot(touch1.clientX - touch2.clientX, touch1.clientY - touch2.clientY);\n                lastPinchZoomValue = km.getZoomValue(); \n                e.preventDefault(); \n            }\n        }, { passive: false });\n\n        container.addEventListener('touchmove', (e) => {\n            if (isPanning && e.touches.length === 1) {\n                const touch = e.touches[0];\n                const dx = touch.clientX - lastPanX;\n                const dy = touch.clientY - lastPanY;\n\n                const paper = km.getPaper();\n                if (paper) {\n                    const viewbox = paper.getViewBox();\n                    paper.setViewBox(viewbox.x - dx, viewbox.y - dy, viewbox.width, viewbox.height);\n                }\n                lastPanX = touch.clientX;\n                lastPanY = touch.clientY;\n                e.preventDefault();\n\n            } else if (isPinching && e.touches.length === 2) {\n                const touch1 = e.touches[0];\n                const touch2 = e.touches[1];\n                const currentPinchDistance = Math.hypot(touch1.clientX - touch2.clientX, touch1.clientY - touch2.clientY);\n\n                if (initialPinchDistance > 0) { \n                    const scaleFactor = currentPinchDistance / initialPinchDistance;\n                    let newZoomLevelPercentage = lastPinchZoomValue * scaleFactor;\n                    \n                    newZoomLevelPercentage = Math.max(20, Math.min(newZoomLevelPercentage, 300)); \n\n                    const rect = container.getBoundingClientRect();\n                    const screenPinchCenterX = (touch1.clientX + touch2.clientX) / 2 - rect.left;\n                    const screenPinchCenterY = (touch1.clientY + touch2.clientY) / 2 - rect.top;\n                    \n                    const paper = km.getPaper();\n                    let paperPinchCenter = null;\n\n                    if (paper && typeof paper.screenToPaperPoint === 'function' && typeof kity !== 'undefined' && kity.Point) {\n                        const screenPoint = new kity.Point(screenPinchCenterX, screenPinchCenterY);\n                        paperPinchCenter = paper.screenToPaperPoint(screenPoint);\n                    }\n                    \n                    km.execCommand('zoom', newZoomLevelPercentage, paperPinchCenter);\n                }\n                e.preventDefault();\n            }\n        }, { passive: false });\n\n        container.addEventListener('touchend', (e) => {\n            if (e.touches.length === 0) { \n                isPanning = false;\n                isPinching = false;\n                initialPinchDistance = 0;\n            } else if (e.touches.length === 1) { \n                if (isPinching) { \n                    isPinching = false;\n                    initialPinchDistance = 0;\n                    \n                    isPanning = true; \n                    const touch = e.touches[0];\n                    lastPanX = touch.clientX;\n                    lastPanY = touch.clientY;\n                }\n            }\n        });\n    }\n\n\n    async function initializeKityMinder(containerElement, data, feedbackElem, isModal = false) {\n        console.log(\"\u6b63\u5728\u521d\u59cb\u5316KityMinder\u4e8e\u5bb9\u5668:\", containerElement.id);\n        containerElement.innerHTML = ''; \n        if (typeof kityminder === 'undefined' || typeof kityminder.Minder === 'undefined') {\n            showKityMinderFeedback(feedbackElem, \"KityMinder\u5e93\u672a\u52a0\u8f7d!\", 'error'); return null;\n        }\n        try {\n            const km = new kityminder.Minder({ renderTo: containerElement });\n            km.importJson(data); km.enable(); \n            await new Promise(resolve => setTimeout(resolve, 500)); \n\n            if (km.getRoot && km.getRoot()) { \n                km.getRoot().traverse(function(node) {\n                    node.setData('font-weight', 'bold'); node.setData('font-size', 19); node.setData('stroke-width', 1);\n                    node.setData('font-family', 'Inter, \"SF Pro Display\", \"Segoe UI\", \"Microsoft YaHei\", \"PingFang SC\", \"Hiragino Sans GB\", \"Noto Sans CJK SC\", sans-serif');\n                    if (node.getLevel() === 0) { node.setData('stroke', 'var(--km-root-bg)'); node.setData('padding', [14, 24]); }\n                    else if (node.getLevel() === 1) { node.setData('stroke', 'var(--km-orange)'); node.setData('padding', [10, 20]); }\n                    else if (node.getLevel() === 2) { node.setData('stroke', 'var(--km-blue)'); node.setData('padding', [8, 16]); }\n                    else if (node.getLevel() >= 3) { \n                        node.setData('background', 'var(--km-lightblue)'); node.setData('color', 'var(--km-node-text, #ffffff)');\n                        node.setData('padding', [8, 16]); node.setData('stroke', 'var(--km-lightblue)');\n                    }\n                    node.render();\n                });\n                km.refresh(); await new Promise(resolve => setTimeout(resolve, 100));\n\n                try {\n                    const paper = km.getPaper(); const renderContainer = km.getRenderContainer();\n                    if (!paper || !renderContainer) {\n                        console.error(\"KityMinder paper \u6216 render container \u4e0d\u53ef\u7528\uff0c\u65e0\u6cd5\u8fdb\u884c\u81ea\u9002\u5e94\u663e\u793a\u3002\");\n                        km.execCommand('camera', km.getRoot(), 100); km.execCommand('zoom', isModal ? 85 : 75); \n                        if (km) { setupKityMinderTouchEvents(km, containerElement); } \n                        return km;\n                    }\n                    const diagramBox = renderContainer.getBoundaryBox();\n                    if (diagramBox && diagramBox.width > 0 && diagramBox.height > 0 && isFinite(diagramBox.x) && isFinite(diagramBox.y) && isFinite(diagramBox.width) && isFinite(diagramBox.height)) {\n                        const viewPadding = 2; const containerWidth = containerElement.clientWidth; const containerHeight = containerElement.clientHeight;\n                        if (containerWidth <= 0 || containerHeight <= 0) {\n                            console.warn(\"KityMinder Debug: \u5bb9\u5668\u5c1a\u65e0\u5c3a\u5bf8\uff0c\u65e0\u6cd5\u8fdb\u884c\u81ea\u9002\u5e94\u7f29\u653e\u3002\u56de\u9000\u5230\u9ed8\u8ba4\u89c6\u56fe\u3002\");\n                            km.execCommand('camera', km.getRoot(), 100); km.execCommand('zoom', isModal ? 85 : 75); \n                            if (km) { setupKityMinderTouchEvents(km, containerElement); }\n                            return km;\n                        }\n                        const usableWidth = containerWidth - 2 * viewPadding; const usableHeight = containerHeight - 2 * viewPadding;\n                        const scaleX = usableWidth / diagramBox.width; const scaleY = usableHeight / diagramBox.height;\n                        let targetScale = Math.min(scaleX, scaleY);\n                        targetScale *= 0.995; \n                        let finalZoomPercent = Math.min(Math.max(targetScale * 100, 20), 100); \n                        const diagramCenterPoint = (typeof kity !== 'undefined' && kity.Point) ? new kity.Point(diagramBox.x + diagramBox.width / 2, diagramBox.y + diagramBox.height / 2) : null;\n                        km.execCommand('zoom', finalZoomPercent, diagramCenterPoint);\n\n                        if (!isModal) { \n                            const actualDiagramHeightAfterZoom = diagramBox.height * (finalZoomPercent / 100);\n                            containerElement.style.height = Math.max(parseInt(getComputedStyle(containerElement).minHeight) || 350, actualDiagramHeightAfterZoom + 30) + 'px'; \n                            containerElement.style.minHeight = '0px'; \n                        }\n                    } else {\n                        console.warn(\"KityMinder Debug: \u65e0\u6548\u7684 diagramBox \u5c3a\u5bf8\u3002\u56de\u9000\u5230\u9ed8\u8ba4\u89c6\u56fe\u3002\");\n                        km.execCommand('camera', km.getRoot(), 100); km.execCommand('zoom', isModal ? 80 : 70);\n                    }\n                } catch (e_view) {\n                    console.error(`KityMinder Debug: \u81ea\u9002\u5e94\u89c6\u56fe\u8c03\u6574\u51fa\u9519 (${containerElement.id}):`, e_view);\n                    km.execCommand('camera', km.getRoot(), 100); km.execCommand('zoom', isModal ? 80 : 70);\n                }\n                km.refresh(); \n            }\n            console.log(\"KityMinder \u521d\u59cb\u5316\u5e76\u5bfc\u5165\u6570\u636e\u5b8c\u6210:\", containerElement.id); \n            if (km) { \n                setupKityMinderTouchEvents(km, containerElement);\n            }\n            return km;\n        } catch (error) {\n            console.error(\"\u521d\u59cb\u5316 KityMinder \u51fa\u9519 (\" + containerElement.id + \"):\", error);\n            showKityMinderFeedback(feedbackElem, `\u521d\u59cb\u5316KityMinder\u5931\u8d25: ${error.message}`, 'error');\n            containerElement.innerHTML = `<p style=\"color:red; text-align:center; padding:20px;\">\u521d\u59cb\u5316KityMinder\u5931\u8d25: ${error.message}</p>`;\n            return null;\n        }\n    }\n\n    // --- KityMinder \u4e8b\u4ef6\u76d1\u542c ---\n    if(mainZoomInBtn) mainZoomInBtn.addEventListener('click', () => { if (mainKM) mainKM.execCommand('zoomIn'); });\n    if(mainZoomOutBtn) mainZoomOutBtn.addEventListener('click', () => { if (mainKM) mainKM.execCommand('zoomOut'); });\n    if(mainDownloadDiagramBtn) mainDownloadDiagramBtn.addEventListener('click', () => { downloadKityMinderAsSVG(mainKM, '\u6838\u5fc3\u6982\u5ff5\u5173\u7cfb\u56fe-\u4e3b\u89c6\u56fe', mermaidMainFeedbackMessage); });\n\n    if(fullscreenOpenBtn) fullscreenOpenBtn.addEventListener('click', () => {\n        if(fullscreenModal) fullscreenModal.classList.add('active'); document.body.classList.add('modal-active');\n        if(modalKityMinderContainer) modalKityMinderContainer.innerHTML = ''; \n        initializeKityMinder(modalKityMinderContainer, kityminderData, mermaidModalFeedbackMessage, true).then(kmInstance => modalKM = kmInstance);\n    });\n    if(fullscreenCloseBtn) fullscreenCloseBtn.addEventListener('click', () => {\n        if(fullscreenModal) fullscreenModal.classList.remove('active'); document.body.classList.remove('modal-active');\n        if(modalKityMinderContainer) modalKityMinderContainer.innerHTML = ''; modalKM = null; \n    });\n    if(modalZoomInBtn) modalZoomInBtn.addEventListener('click', () => { if (modalKM) modalKM.execCommand('zoomIn'); });\n    if(modalZoomOutBtn) modalZoomOutBtn.addEventListener('click', () => { if (modalKM) modalKM.execCommand('zoomOut'); });\n    if(modalDownloadDiagramBtn) modalDownloadDiagramBtn.addEventListener('click', () => { downloadKityMinderAsSVG(modalKM, '\u6838\u5fc3\u6982\u5ff5\u5173\u7cfb\u56fe-\u5168\u5c4f', mermaidModalFeedbackMessage); });\n\n    function initializeMainKityMinderDiagram() {\n        console.log(\"DOM\u5b8c\u5168\u52a0\u8f7d\u6216\u51fd\u6570\u88ab\u8c03\u7528\u3002\u6b63\u5728\u521d\u59cb\u5316\u4e3bKityMinder\u3002\");\n        if (typeof kityminder !== 'undefined' && mainKityMinderContainer) {\n            initializeKityMinder(mainKityMinderContainer, kityminderData, mermaidMainFeedbackMessage, false).then(kmInstance => mainKM = kmInstance);\n        } else {\n            if (mermaidMainFeedbackMessage) { showKityMinderFeedback(mermaidMainFeedbackMessage, \"KityMinder\u5e93\u52a0\u8f7d\u5931\u8d25\u6216\u5bb9\u5668\u672a\u627e\u5230\uff0c\u65e0\u6cd5\u521d\u59cb\u5316\u601d\u7ef4\u5bfc\u56fe\u3002\", \"error\"); }\n            console.error(\"KityMinder\u5e93\u6216\u4e3b\u5bb9\u5668\u4e0d\u53ef\u7528\uff0c\u65e0\u6cd5\u521d\u59cb\u5316\u3002\");\n        }\n    }\n    // --- KityMinder (\u601d\u7ef4\u5bfc\u56fe) JavaScript \u7ed3\u675f ---\n\n    // --- \u62a5\u544a\u9875\u9762\u901a\u7528 JavaScript \u5f00\u59cb ---\n    function getCssVariable(variableName, defaultValue = null) {\n        const value = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim();\n        return value || defaultValue;\n    }\n\n    function getReportBaseName() {\n        const mainTitleElement = document.querySelector('.main-card h1'); let reportTitle = \"\u62a5\u544a\";\n        const placeholderTitle = \"[\u4e3b\u62a5\u544a\u6807\u9898 - \u4f8b\u5982\uff1aAI\u4ea7\u54c1\u56e2\u65e5\u62a5]\";\n        if (mainTitleElement && mainTitleElement.textContent) {\n            const currentTitle = mainTitleElement.textContent.trim();\n            if (currentTitle && currentTitle !== placeholderTitle) { reportTitle = currentTitle; }\n        }\n        if (reportTitle.endsWith(\"\u65e5\u62a5\")) { reportTitle = reportTitle.substring(0, reportTitle.length - 2); }\n        return reportTitle + \"\u65e5\u62a5\"; \n    }\n\n    function getFormattedDateString() {\n        const reportDateElement = document.querySelector('.main-card .date'); let dateStr = \"nodate\";\n        const placeholderDate = \"[\u65e5\u671f - \u4f8b\u5982\uff1a2025\u5e745\u670819\u65e5]\";\n        if (reportDateElement && reportDateElement.textContent) {\n            const dateContent = reportDateElement.textContent.trim();\n            if (dateContent && dateContent !== placeholderDate) {\n                const match = dateContent.match(/(\\d{4})\u5e74\\s*(\\d{1,2})\u6708\\s*(\\d{1,2})\u65e5/);\n                if (match) { dateStr = `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`; }\n                else { console.warn(\"\u65e5\u671f\u5185\u5bb9 '\" + dateContent + \"' \u4e0e 'YYYY\u5e74M\u6708D\u65e5' \u683c\u5f0f\u4e0d\u5339\u914d\u3002\u6587\u4ef6\u540d\u5c06\u4f7f\u7528 'nodate'\u3002\");}\n            } else { console.warn(\"\u65e5\u671f\u5185\u5bb9\u4e3a\u5360\u4f4d\u7b26\u6216\u7a7a\u3002\u6587\u4ef6\u540d\u5c06\u4f7f\u7528 'nodate'\u3002\"); }\n        } else { console.warn(\"\u672a\u627e\u5230\u65e5\u671f\u5143\u7d20\u6216\u65e0\u5185\u5bb9\u3002\u6587\u4ef6\u540d\u5c06\u4f7f\u7528 'nodate'\u3002\"); }\n        return dateStr;\n    }\n\n    function sanitizeFilename(filename) { return filename.replace(/[^\\w\\u4e00-\\u9fa5\\-\\.]/g, '_').replace(/_+/g, '_'); }\n\n    const previousReportUrl = \"\u5728\u6b64\u5904\u586b\u5199\u6628\u65e5\u65e5\u62a5\u7684URL\"; \n    const historicalReportUrl = \"https://czkzyp3cp1.feishu.cn/share/base/view/shrcnoweVpP2vDaWtAEFspjqRNf\"; \n\n    function showReportFeedbackMessage(message, type = 'info', duration = 3000) {\n        const feedbackElement = document.getElementById('feedback-message');\n        if (feedbackElement) {\n            feedbackElement.textContent = message; feedbackElement.className = type;\n            setTimeout(() => { element.textContent = ''; element.className = ''; }, duration);\n        }\n    }\n\n    async function captureScreenshot(elementToCapture) {\n        await document.fonts.ready; \n\n        const bodyBgColor = getCssVariable('--bg-primary', '#F5F5EE');\n        const cardBgColor = getCssVariable('--bg-secondary', '#faf9f6');\n        const tertiaryBgColor = getCssVariable('--bg-tertiary', '#f1f3f5');\n        const highlightKeywordBgColor = getCssVariable('--highlight-keyword-bg', '#ffe8cc');\n        const kityMinderContainerBgColor = '#ffffff'; \n\n        if (typeof mainKM !== 'undefined' && mainKM && typeof mainKM.refresh === 'function') {\n            try { mainKM.refresh(); await new Promise(resolve => setTimeout(resolve, 150)); } \n            catch (e) { console.warn(\"\u5237\u65b0KityMinder\u65f6\u51fa\u9519:\", e); }\n        }\n\n        return html2canvas(elementToCapture, {\n            useCORS: true, scale: window.devicePixelRatio || 1.5, logging: false, backgroundColor: bodyBgColor,\n            width: elementToCapture.scrollWidth, height: elementToCapture.scrollHeight,\n            windowWidth: elementToCapture.scrollWidth, windowHeight: elementToCapture.scrollHeight,\n            x: 0, y: 0, scrollX: 0, scrollY: 0,\n            onclone: function(clonedDoc) {\n                clonedDoc.body.style.backgroundColor = bodyBgColor;\n                clonedDoc.documentElement.style.backgroundColor = bodyBgColor;\n                const reportWrapper = clonedDoc.getElementById('report-content-wrapper');\n                if (reportWrapper) {\n                    reportWrapper.style.backgroundColor = bodyBgColor;\n                    reportWrapper.style.height = elementToCapture.scrollHeight + 'px'; \n                    reportWrapper.style.overflow = 'visible'; \n                }\n                clonedDoc.querySelectorAll('.card').forEach(card => { card.style.backgroundColor = cardBgColor; card.style.overflow = 'visible'; });\n                const mindmapCardContainer = clonedDoc.querySelector('.mindmap-card-container.card');\n                if (mindmapCardContainer) { mindmapCardContainer.style.backgroundColor = cardBgColor; }\n\n                clonedDoc.querySelectorAll('.kityminder-container').forEach(kmContainer => {\n                    kmContainer.style.backgroundColor = kityMinderContainerBgColor; kmContainer.style.overflow = 'visible';\n                    const svgElements = kmContainer.querySelectorAll('svg');\n                    svgElements.forEach(svg => {\n                        svg.style.overflow = 'visible !important'; \n                        const originalKmContainer = document.getElementById(kmContainer.id);\n                        if (originalKmContainer) {\n                            const originalSvg = originalKmContainer.querySelector('svg');\n                            if (originalSvg) {\n                                svg.setAttribute('width', originalSvg.getAttribute('width'));\n                                svg.setAttribute('height', originalSvg.getAttribute('height'));\n                                svg.setAttribute('viewBox', originalSvg.getAttribute('viewBox'));\n                            }\n                        }\n                    });\n                });\n                clonedDoc.querySelectorAll('.link-item, .qr-code-item, .meta-info span, .tag').forEach(el => { el.style.backgroundColor = tertiaryBgColor; });\n                clonedDoc.querySelectorAll('.highlight-keyword').forEach(el => { \n                    el.style.display = 'inline'; el.style.padding = '1px 2px'; el.style.lineHeight = '1.2';\n                    el.style.boxDecorationBreak = 'clone'; el.style.webkitBoxDecorationBreak = 'clone';\n                    el.style.backgroundColor = highlightKeywordBgColor;\n                });\n                const allElements = clonedDoc.getElementsByTagName(\"*\");\n                for (let i = 0; i < allElements.length; i++) {\n                    allElements[i].style.transition = 'none !important'; allElements[i].style.animation = 'none !important';\n                }\n            }\n        });\n    }\n\n    const screenshotToClipboardBtn = document.getElementById('screenshotToClipboardBtn');\n    if(screenshotToClipboardBtn) screenshotToClipboardBtn.addEventListener('click', async function() {\n        const reportContent = document.getElementById('report-content-wrapper');\n        if (!reportContent) { showReportFeedbackMessage('\u622a\u56fe\u9519\u8bef: \u672a\u627e\u5230\u62a5\u544a\u5185\u5bb9\u3002', 'error'); return; }\n        const actionButtons = document.querySelector('.action-buttons-container');\n        const feedbackMsgElement = document.getElementById('feedback-message');\n        const originalButtonText = this.innerHTML; this.innerHTML = '<i class=\"fas fa-spinner fa-spin\"></i> \u5904\u7406\u4e2d...'; this.disabled = true;\n        if(actionButtons) actionButtons.style.visibility = 'hidden'; if(feedbackMsgElement) feedbackMsgElement.style.visibility = 'hidden';\n        try {\n            await new Promise(resolve => setTimeout(resolve, 500)); \n            const canvas = await captureScreenshot(reportContent);\n            canvas.toBlob(async function(blob) {\n                if (blob) {\n                    try {\n                        if (navigator.clipboard && navigator.clipboard.write) { \n                            await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);\n                            showReportFeedbackMessage('\u622a\u56fe\u5df2\u62f7\u8d1d\u5230\u526a\u8d34\u677f!', 'success');\n                        } else { \n                            const dataUrl = canvas.toDataURL('image/png'); const img = document.createElement('img');\n                            img.src = dataUrl; img.style.maxWidth = '100%'; img.style.border = '1px solid #ccc';\n                            const tempContainer = document.createElement('div');\n                            tempContainer.style.cssText = 'position:fixed;top:10px;left:10px;z-index:9999;background-color:white;padding:10px;border:1px solid black;';\n                            tempContainer.innerHTML = '<strong>\u8bf7\u624b\u52a8\u53f3\u952e\u590d\u5236\u56fe\u7247:</strong><br>'; tempContainer.appendChild(img);\n                            document.body.appendChild(tempContainer);\n                            showReportFeedbackMessage('\u622a\u56fe\u5df2\u751f\u6210\u3002\u8bf7\u624b\u52a8\u53f3\u952e\u590d\u5236\u56fe\u7247\u3002\u6b64\u63d0\u793a\u5c06\u572810\u79d2\u540e\u6d88\u5931\u3002', 'info', 10000);\n                            setTimeout(() => { if(document.body.contains(tempContainer)) document.body.removeChild(tempContainer); }, 10000);\n                        }\n                    } catch (copyError) {\n                        console.error('\u62f7\u8d1d\u5230\u526a\u8d34\u677f\u5931\u8d25:', copyError);\n                        showReportFeedbackMessage('\u62f7\u8d1d\u622a\u56fe\u5230\u526a\u8d34\u677f\u5931\u8d25: ' + copyError.message, 'error', 5000);\n                    }\n                } else { throw new Error('\u65e0\u6cd5\u5c06Canvas\u8f6c\u6362\u4e3aBlob\u3002'); }\n            }, 'image/png');\n        } catch (error) {\n            console.error('\u622a\u56fe\u6355\u83b7\u5931\u8d25:', error); showReportFeedbackMessage('\u622a\u56fe\u6355\u83b7\u5931\u8d25: ' + error.message, 'error');\n        } finally {\n            if(actionButtons) actionButtons.style.visibility = 'visible'; if(feedbackMsgElement) feedbackMsgElement.style.visibility = 'visible';\n            this.innerHTML = originalButtonText; this.disabled = false;\n        }\n    });\n\n    const screenshotDownloadBtn = document.getElementById('screenshotDownloadBtn');\n    if(screenshotDownloadBtn) screenshotDownloadBtn.addEventListener('click', async function() {\n        const reportContent = document.getElementById('report-content-wrapper');\n        if (!reportContent) { showReportFeedbackMessage('\u622a\u56fe\u9519\u8bef: \u672a\u627e\u5230\u62a5\u544a\u5185\u5bb9\u3002', 'error'); return; }\n        const actionButtons = document.querySelector('.action-buttons-container');\n        const feedbackMsgElement = document.getElementById('feedback-message');\n        const originalButtonText = this.innerHTML; this.innerHTML = '<i class=\"fas fa-spinner fa-spin\"></i> \u751f\u6210\u4e2d...'; this.disabled = true;\n        if(actionButtons) actionButtons.style.visibility = 'hidden'; if(feedbackMsgElement) feedbackMsgElement.style.visibility = 'hidden';\n        try {\n            await new Promise(resolve => setTimeout(resolve, 500)); \n            const canvas = await captureScreenshot(reportContent);\n            const image = canvas.toDataURL('image/png'); const link = document.createElement('a');\n            const reportBaseName = getReportBaseName(); const dateSuffix = getFormattedDateString();\n            let filename = sanitizeFilename(reportBaseName + '-' + dateSuffix) + '.png';\n            link.href = image; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link);\n            showReportFeedbackMessage('\u622a\u56fe\u5df2\u4e0b\u8f7d\u4e3a ' + filename + '!', 'success');\n        } catch (error) {\n            console.error('\u4e0b\u8f7d\u622a\u56fe\u5931\u8d25:', error); showReportFeedbackMessage('\u4e0b\u8f7d\u622a\u56fe\u5931\u8d25: ' + error.message, 'error');\n        } finally {\n            if(actionButtons) actionButtons.style.visibility = 'visible'; if(feedbackMsgElement) feedbackMsgElement.style.visibility = 'visible';\n            this.innerHTML = originalButtonText; this.disabled = false;\n        }\n    });\n\n    const downloadHtmlBtn = document.getElementById('downloadHtmlBtn');\n    if(downloadHtmlBtn) downloadHtmlBtn.addEventListener('click', function() {\n        try {\n            const htmlContent = document.documentElement.outerHTML;\n            const blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });\n            const link = document.createElement('a');\n            const reportBaseName = getReportBaseName(); const dateSuffix = getFormattedDateString();\n            let filename = sanitizeFilename(reportBaseName + '-' + dateSuffix) + '.html';\n            link.href = URL.createObjectURL(blob); link.download = filename; document.body.appendChild(link);\n            link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href);\n            showReportFeedbackMessage('HTML\u62a5\u544a\u5df2\u4e0b\u8f7d\u4e3a ' + filename + '!', 'success');\n        } catch (error) {\n            console.error('\u4e0b\u8f7dHTML\u5931\u8d25:', error); showReportFeedbackMessage('\u4e0b\u8f7dHTML\u5931\u8d25: ' + error.message, 'error');\n        }\n    });\n\n    const copyHtmlBtn = document.getElementById('copyHtmlBtn');\n    if(copyHtmlBtn) copyHtmlBtn.addEventListener('click', function() {\n        const htmlContent = document.documentElement.outerHTML; const Gthis = this; \n        const originalButtonHTML = Gthis.innerHTML; const textarea = document.createElement('textarea');\n        textarea.value = htmlContent; textarea.style.position = 'fixed'; textarea.style.top = '-9999px'; textarea.style.left = '-9999px'; \n        document.body.appendChild(textarea);\n        try {\n            textarea.select(); textarea.setSelectionRange(0, textarea.value.length); \n            if (document.execCommand('copy')) { \n                Gthis.innerHTML = '<i class=\"fas fa-check\"></i> \u5df2\u62f7\u8d1d!';\n                showReportFeedbackMessage('HTML\u5df2\u62f7\u8d1d\u5230\u526a\u8d34\u677f!', 'success', 2000);\n            } else {\n                Gthis.innerHTML = '<i class=\"fas fa-times\"></i> \u62f7\u8d1d\u5931\u8d25';\n                showReportFeedbackMessage('\u62f7\u8d1d\u5931\u8d25\u3002\u60a8\u7684\u6d4f\u89c8\u5668\u53ef\u80fd\u4e0d\u652f\u6301\u6b64\u64cd\u4f5c\u3002', 'error', 2000);\n            }\n        } catch (err) {\n            Gthis.innerHTML = '<i class=\"fas fa-times\"></i> \u62f7\u8d1d\u51fa\u9519';\n            showReportFeedbackMessage('\u62f7\u8d1d\u51fa\u9519: ' + err.message, 'error', 2000); console.error('\u62f7\u8d1dHTML\u6e90\u7801\u51fa\u9519:', err);\n        } finally {\n            setTimeout(function() { Gthis.innerHTML = originalButtonHTML; }, 2000); \n            document.body.removeChild(textarea); \n        }\n    });\n\n    const viewHistoricalReportBtn = document.getElementById('viewHistoricalReportBtn');\n    if(viewHistoricalReportBtn) viewHistoricalReportBtn.addEventListener('click', function() {\n        const urlToOpen = historicalReportUrl.trim();\n        if (urlToOpen && (urlToOpen.startsWith('http://') || urlToOpen.startsWith('https://'))) {\n            const newWindow = window.open(urlToOpen, '_blank');\n            if (!newWindow || newWindow.closed || typeof newWindow.closed == 'undefined') { \n                showReportFeedbackMessage('\u65e0\u6cd5\u6253\u5f00\u65b0\u7a97\u53e3\u3002\u8bf7\u68c0\u67e5\u60a8\u7684\u5f39\u51fa\u7a97\u53e3\u62e6\u622a\u8bbe\u7f6e\u3002', 'error', 5000);\n            }\n        } else { showReportFeedbackMessage('\u5386\u53f2\u65e5\u62a5\u7684URL\u65e0\u6548\u3002', 'error', 5000); }\n    });\n\n    const viewPreviousReportBtn = document.getElementById('viewPreviousReportBtn');\n    if(viewPreviousReportBtn) viewPreviousReportBtn.addEventListener('click', function() {\n        const urlToOpen = previousReportUrl.trim();\n        const placeholderUrlText = \"\u5728\u6b64\u5904\u586b\u5199\u6628\u65e5\u65e5\u62a5\u7684URL\";\n        if (urlToOpen && urlToOpen !== placeholderUrlText && (urlToOpen.startsWith('http://') || urlToOpen.startsWith('https://') || !urlToOpen.includes('://'))) { \n            const newWindow = window.open(urlToOpen, '_blank');\n            if (!newWindow || newWindow.closed || typeof newWindow.closed == 'undefined') {\n                showReportFeedbackMessage('\u65e0\u6cd5\u6253\u5f00\u65b0\u7a97\u53e3\u3002\u8bf7\u68c0\u67e5\u60a8\u7684\u5f39\u51fa\u7a97\u53e3\u62e6\u622a\u8bbe\u7f6e\u3002', 'error', 5000);\n            }\n        } else { showReportFeedbackMessage('\u6628\u65e5\u65e5\u62a5\u7684URL\u672a\u914d\u7f6e\u6216\u65e0\u6548\u3002\u8bf7\u68c0\u67e5\u811a\u672c\u4e2d\u7684 previousReportUrl \u53d8\u91cf\u3002', 'error', 5000); }\n    });\n\n    // --- \u67e5\u770b\u66f4\u591a\u804a\u5929\u8bb0\u5f55\u529f\u80fd JavaScript \u5f00\u59cb - \u91cd\u65b0\u8bbe\u8ba1 ---\n    \n    // \u8c03\u8bd5\u5f00\u5173\n    const DEBUG_EXPAND_MESSAGES = true; // \u8bbe\u7f6e\u4e3a true \u542f\u7528\u8c03\u8bd5\u4fe1\u606f\n    \n    // \u5b58\u50a8\u6bcf\u4e2a\u8bdd\u9898\u7684\u5b8c\u6574\u804a\u5929\u8bb0\u5f55\u6570\u636e\u548c\u72b6\u6001\n    let topicMessagesData = {};\n    let expandStates = new Map(); // \u5b58\u50a8\u6bcf\u4e2a\u8bdd\u9898\u7684\u5c55\u5f00\u72b6\u6001\n    \n    // \u6bcf\u6b21\u52a0\u8f7d\u7684\u6d88\u606f\u6570\u91cf\n    const MESSAGES_PER_LOAD = 5;\n    \n    /**\n     * \u521d\u59cb\u5316\u8bdd\u9898\u804a\u5929\u8bb0\u5f55\u6570\u636e\n     * AI\u4f7f\u7528\u8bf4\u660e\uff1a\u5728\u751f\u6210HTML\u65f6\uff0c\u9700\u8981\u5c06\u6bcf\u4e2a\u8bdd\u9898\u7684\u5b8c\u6574messages\u6570\u7ec4\u5d4c\u5165\u5230\u8fd9\u4e2a\u51fd\u6570\u4e2d\n     */\n    function initializeTopicMessagesData() {\n        /* TOPIC_MESSAGES_DATA_PLACEHOLDER_START */\n        // AI\u5c06\u5728\u6b64\u5904\u66ff\u6362\u4e3a\u5b9e\u9645\u7684\u8bdd\u9898\u6d88\u606f\u6570\u636e\uff0c\u683c\u5f0f\u793a\u4f8b\uff1a\n        // topicMessagesData = {\n        //     \"merged_004\": [\n        //         { sender_name: \"\u6768\u98de\", content: \"\u6709\u65f6\u5019agent\u4e4b\u95f4\u7684\u6c9f\u901a\u5c31\u662f\u8fd9\u4e48\u7b80\u5355\u7c97\u66b4[\u65fa\u67f4] \u8fd9\u90fd\u5f97\u76ca\u4e8e\u4e00\u4e2a\u53eb\u505a -p \u7684\u53c2\u6570\", time: \"2025-06-29T11:41:27+08:00\" },\n        //         { sender_name: \"\u039b-xzy\", content: \"Monica \u9ad8\u7ea7\u7248\u6709\u9700\u8981\u7684\u5417\uff0c\u8fd8\u6709\u534a\u5e74\uff0c\u95f2\u7f6e\u4e86\u3002\u5e73\u65f6\u4e3b\u529b\u8be5\u7528 gpt plus \u548c gemini pro \u4e86\", time: \"2025-06-29T11:51:28+08:00\" },\n        //         // ... \u8be5\u8bdd\u9898\u7684\u6240\u6709\u5176\u4ed6\u6d88\u606f\n        //     ],\n        //     \"\u53e6\u4e00\u4e2a\u8bdd\u9898ID\": [\n        //         // ... \u8be5\u8bdd\u9898\u7684\u6240\u6709\u6d88\u606f\n        //     ]\n        // };\n        topicMessagesData = {};\n        /* TOPIC_MESSAGES_DATA_PLACEHOLDER_END */\n    }\n    \n    /**\n     * \u683c\u5f0f\u5316\u65f6\u95f4\u663e\u793a\n     */\n    function formatMessageTime(timeString) {\n        if (!timeString) return { relative: '', full: '' };\n        \n        const messageTime = new Date(timeString);\n        \n        // \u59cb\u7ec8\u663e\u793a\u5177\u4f53\u65e5\u671f\u65f6\u95f4\n        const relativeTime = messageTime.toLocaleString('zh-CN', {\n            month: 'numeric',\n            day: 'numeric',\n            hour: '2-digit',\n            minute: '2-digit'\n        }).replace(/\\//g, '-');\n        \n        const fullTime = messageTime.toLocaleString('zh-CN', {\n            year: 'numeric',\n            month: '2-digit',\n            day: '2-digit',\n            hour: '2-digit',\n            minute: '2-digit',\n            second: '2-digit'\n        });\n        \n        return { relative: relativeTime, full: fullTime };\n    }\n    \n    /**\n     * \u521b\u5efa\u65f6\u95f4\u5206\u9694\u7b26\n     */\n    function createTimeDivider(timeString) {\n        const divider = document.createElement('div');\n        divider.className = 'time-divider';\n        \n        const messageTime = new Date(timeString);\n        const today = new Date();\n        const yesterday = new Date(today);\n        yesterday.setDate(today.getDate() - 1);\n        \n        let dividerText = '';\n        \n        if (messageTime.toDateString() === today.toDateString()) {\n            dividerText = '\u4eca\u5929';\n        } else if (messageTime.toDateString() === yesterday.toDateString()) {\n            dividerText = '\u6628\u5929';\n        } else {\n            dividerText = messageTime.toLocaleDateString('zh-CN', {\n                month: 'long',\n                day: 'numeric',\n                weekday: 'short'\n            });\n        }\n        \n        const textSpan = document.createElement('span');\n        textSpan.className = 'time-divider-text';\n        textSpan.textContent = dividerText;\n        \n        divider.appendChild(textSpan);\n        return divider;\n    }\n    \n    /**\n     * \u521b\u5efa\u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668\n     */\n    function createTimeJumpIndicator(hoursDiff) {\n        const indicator = document.createElement('div');\n        indicator.className = 'time-jump-indicator';\n        \n        let jumpText = '';\n        if (hoursDiff < 1) {\n            const minutes = Math.round(hoursDiff * 60);\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${minutes}\u5206\u949f`;\n        } else if (hoursDiff < 24) {\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${Math.round(hoursDiff)}\u5c0f\u65f6`;\n        } else {\n            const days = Math.round(hoursDiff / 24);\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${days}\u5929`;\n        }\n        \n        indicator.innerHTML = `\n            <span class=\"jump-icon\">\u23f0</span>\n            <span class=\"jump-text\">${jumpText}</span>\n        `;\n        \n        return indicator;\n    }\n    \n    /**\n     * \u521b\u5efa\u5c55\u5f00\u533a\u57df\u7684\u65f6\u95f4\u5206\u9694\u7b26\n     */\n    function createExpandedTimeDivider(timeString) {\n        const divider = document.createElement('div');\n        divider.className = 'expanded-time-divider';\n        \n        const messageTime = new Date(timeString);\n        const today = new Date();\n        const yesterday = new Date(today);\n        yesterday.setDate(today.getDate() - 1);\n        \n        let dividerText = '';\n        \n        if (messageTime.toDateString() === today.toDateString()) {\n            dividerText = '\u4eca\u5929';\n        } else if (messageTime.toDateString() === yesterday.toDateString()) {\n            dividerText = '\u6628\u5929';\n        } else {\n            dividerText = messageTime.toLocaleDateString('zh-CN', {\n                month: 'long',\n                day: 'numeric',\n                weekday: 'short'\n            });\n        }\n        \n        const textSpan = document.createElement('span');\n        textSpan.className = 'expanded-time-divider-text';\n        textSpan.textContent = dividerText;\n        \n        divider.appendChild(textSpan);\n        return divider;\n    }\n    \n    /**\n     * \u521b\u5efa\u5c55\u5f00\u533a\u57df\u7684\u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668\n     */\n    function createExpandedTimeJump(hoursDiff) {\n        const indicator = document.createElement('div');\n        indicator.className = 'expanded-time-jump';\n        \n        let jumpText = '';\n        if (hoursDiff < 1) {\n            const minutes = Math.round(hoursDiff * 60);\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${minutes}\u5206\u949f`;\n        } else if (hoursDiff < 24) {\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${Math.round(hoursDiff)}\u5c0f\u65f6`;\n        } else {\n            const days = Math.round(hoursDiff / 24);\n            jumpText = `\u65f6\u95f4\u8df3\u8dc3 ${days}\u5929`;\n        }\n        \n        indicator.innerHTML = `\n            <span class=\"jump-icon\">\u23f0</span>\n            <span>${jumpText}</span>\n        `;\n        \n        return indicator;\n    }\n    \n    /**\n     * \u521b\u5efa\u5c55\u5f00\u6d88\u606fHTML\u5143\u7d20 - \u5168\u65b0\u6837\u5f0f\n     */\n    function createExpandedMessageElement(message, showTimeDivider = false, timeJumpHours = null) {\n        const fragment = document.createDocumentFragment();\n        \n        // \u5982\u679c\u9700\u8981\u663e\u793a\u65f6\u95f4\u8df3\u8dc3\u6307\u793a\u5668\n        if (timeJumpHours !== null && timeJumpHours >= 0.5) {\n            const jumpIndicator = createExpandedTimeJump(timeJumpHours);\n            fragment.appendChild(jumpIndicator);\n        }\n        \n        // \u5982\u679c\u9700\u8981\u663e\u793a\u65f6\u95f4\u5206\u9694\u7b26\uff08\u65e5\u671f\u53d8\u5316\uff09\n        if (showTimeDivider && message.time) {\n            const timeDivider = createExpandedTimeDivider(message.time);\n            fragment.appendChild(timeDivider);\n        }\n        \n        const messageDiv = document.createElement('div');\n        messageDiv.className = 'expanded-message';\n        \n        // \u521b\u5efa\u6d88\u606f\u5934\u90e8\n        const headerDiv = document.createElement('div');\n        headerDiv.className = 'expanded-message-header';\n        \n        const authorSpan = document.createElement('span');\n        authorSpan.className = 'expanded-message-author';\n        authorSpan.textContent = message.sender_name;\n        \n        // \u6dfb\u52a0\u65f6\u95f4\u663e\u793a\n        if (message.time) {\n            const timeInfo = formatMessageTime(message.time);\n            const timeSpan = document.createElement('span');\n            timeSpan.className = 'expanded-message-time';\n            timeSpan.textContent = timeInfo.relative;\n            timeSpan.setAttribute('data-full-time', timeInfo.full);\n            \n            headerDiv.appendChild(authorSpan);\n            headerDiv.appendChild(timeSpan);\n        } else {\n            headerDiv.appendChild(authorSpan);\n        }\n        \n        const contentDiv = document.createElement('div');\n        contentDiv.className = 'expanded-message-content';\n        contentDiv.textContent = message.content;\n        \n        messageDiv.appendChild(headerDiv);\n        messageDiv.appendChild(contentDiv);\n        fragment.appendChild(messageDiv);\n        \n        return fragment;\n    }\n    \n    /**\n     * \u521b\u5efa\"\u5c55\u5f00\u5b8c\u6574\u5bf9\u8bdd\"\u6309\u94ae\n     */\n    function createExpandButton(topicId, remainingCount) {\n        const section = document.createElement('div');\n        section.className = 'load-more-section';\n        \n        const expandButton = document.createElement('button');\n        expandButton.className = 'load-more-button';\n        expandButton.setAttribute('data-topic-id', topicId);\n        \n        expandButton.innerHTML = `\n            <i class=\"fas fa-chevron-down\"></i>\n            <span>\u5c55\u5f00\u5b8c\u6574\u5bf9\u8bdd</span>\n            <span class=\"progress-text\">(\u8fd8\u6709${remainingCount}\u6761\u6d88\u606f)</span>\n        `;\n        \n        expandButton.addEventListener('click', function() {\n            loadMoreMessages(this);\n        });\n        \n        section.appendChild(expandButton);\n        return section;\n    }\n    \n    /**\n     * \u521b\u5efa\"\u67e5\u770b\u66f4\u591a\"\u6309\u94ae\uff08\u5411\u540e\u517c\u5bb9\uff09\n     */\n    function createLoadMoreButton(topicId, shownCount, totalCount) {\n        return createExpandButton(topicId, totalCount - shownCount);\n    }\n    \n    /**\n     * \u521b\u5efa\u5c55\u5f00\u533a\u57df\u5bb9\u5668\n     */\n    function createExpandedArea(topicId, shownCount, totalCount) {\n        const area = document.createElement('div');\n        area.className = 'expanded-messages-area';\n        area.setAttribute('data-topic-id', topicId);\n        \n        // \u521b\u5efa\u5934\u90e8\n        const header = document.createElement('div');\n        header.className = 'expanded-area-header';\n        \n        const title = document.createElement('div');\n        title.className = 'expanded-area-title';\n        title.innerHTML = `\n            <i class=\"fas fa-comments icon\"></i>\n            <span>\u5b8c\u6574\u5bf9\u8bdd\u8bb0\u5f55</span>\n        `;\n        \n        const progress = document.createElement('div');\n        progress.className = 'expanded-area-progress';\n        progress.textContent = `\u5171 ${totalCount} \u6761\u6d88\u606f`;\n        \n        header.appendChild(title);\n        header.appendChild(progress);\n        area.appendChild(header);\n        \n        // \u521b\u5efa\u5185\u5bb9\u533a\u57df\n        const content = document.createElement('div');\n        content.className = 'expanded-messages-content';\n        area.appendChild(content);\n        \n        // \u6dfb\u52a0\u8bf4\u660e\u6587\u672c\n        const note = document.createElement('div');\n        note.style.cssText = 'padding: 0.75rem; margin-bottom: 0.5rem; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 0.8rem; color: #64748b; text-align: center;';\n        note.textContent = '\u4ee5\u4e0b\u662f\u8be5\u8bdd\u9898\u7684\u5b8c\u6574\u5bf9\u8bdd\u8bb0\u5f55\uff0c\u6309\u65f6\u95f4\u987a\u5e8f\u6392\u5217';\n        content.appendChild(note);\n        \n        // \u521b\u5efa\u5e95\u90e8\u64cd\u4f5c\u533a\n        const footer = document.createElement('div');\n        footer.className = 'expanded-area-footer';\n        area.appendChild(footer);\n        \n        return area;\n    }\n    \n    /**\n     * \u521b\u5efa\u4e00\u952e\u6536\u8d77\u6309\u94ae\n     */\n    function createCollapseAllButton(topicId) {\n        const button = document.createElement('button');\n        button.className = 'collapse-all-button';\n        button.setAttribute('data-topic-id', topicId);\n        \n        button.innerHTML = `\n            <i class=\"fas fa-chevron-up\"></i>\n            <span>\u6536\u8d77\u5bf9\u8bdd</span>\n        `;\n        \n        button.addEventListener('click', function() {\n            collapseAllMessages(this);\n        });\n        \n        return button;\n    }\n    \n    /**\n     * \u521b\u5efa\u52a0\u8f7d\u6307\u793a\u5668\n     */\n    function createLoadingIndicator() {\n        const indicator = document.createElement('div');\n        indicator.className = 'loading-indicator';\n        indicator.innerHTML = `\n            <div class=\"spinner\"></div>\n            <span>\u6b63\u5728\u52a0\u8f7d\u5b8c\u6574\u5bf9\u8bdd\u8bb0\u5f55...</span>\n        `;\n        return indicator;\n    }\n    \n    /**\n     * \u52a0\u8f7d\u5b8c\u6574\u5bf9\u8bdd - \u5168\u65b0\u8bbe\u8ba1\u7684\u6838\u5fc3\u51fd\u6570\n     */\n    function loadMoreMessages(button) {\n        const topicId = button.getAttribute('data-topic-id');\n        const container = document.querySelector(`[data-topic-id=\"${topicId}\"]`);\n        \n        if (!topicMessagesData[topicId] || !container) {\n            console.warn(`\u672a\u627e\u5230\u8bdd\u9898 ${topicId} \u7684\u6d88\u606f\u6570\u636e\u6216\u5bb9\u5668`);\n            return;\n        }\n        \n        const state = expandStates.get(topicId);\n        \n        // \u6309\u65f6\u95f4\u987a\u5e8f\u83b7\u53d6\u6240\u6709\u6d88\u606f\n        const allMessages = [...topicMessagesData[topicId]];\n        allMessages.sort((a, b) => new Date(a.time) - new Date(b.time));\n        \n        // \u83b7\u53d6\u5df2\u663e\u793a\u7684\u6d88\u606f\u5185\u5bb9\uff0c\u7528\u4e8e\u53bb\u91cd\n        const topicCard = container.closest('.topic-card');\n        const chatConversation = topicCard.querySelector('.chat-conversation');\n        const initialMessageContents = new Set();\n        \n        if (chatConversation) {\n            const initialMessageElements = chatConversation.querySelectorAll('.chat-message .message-content');\n            initialMessageElements.forEach(element => {\n                initialMessageContents.add(element.textContent.trim());\n            });\n        }\n        \n        // \u83b7\u53d6\u6240\u6709\u6d88\u606f\u7528\u4e8e\u5c55\u5f00\uff08\u5b8c\u6574\u5bf9\u8bdd\u8bb0\u5f55\u5e94\u8be5\u5305\u542b\u6240\u6709\u6d88\u606f\uff09\n        const messagesToExpand = allMessages.filter((message) => {\n            // \u4e0d\u8fc7\u6ee4\u4efb\u4f55\u6d88\u606f\uff0c\u5c55\u5f00\u533a\u57df\u663e\u793a\u5b8c\u6574\u7684\u65f6\u95f4\u987a\u5e8f\u5bf9\u8bdd\n            return true;\n        });\n        \n        if (messagesToExpand.length === 0) {\n            console.warn('\u6ca1\u6709\u66f4\u591a\u6d88\u606f\u53ef\u4ee5\u5c55\u5f00');\n            return;\n        }\n        \n        // \u8c03\u8bd5\u4fe1\u606f\n        if (DEBUG_EXPAND_MESSAGES) {\n            console.log(`\u8bdd\u9898 ${topicId}: \u603b\u6d88\u606f\u6570=${allMessages.length}, \u521d\u59cb\u663e\u793a=${state.initialCount}, \u5b8c\u6574\u5c55\u5f00=${messagesToExpand.length}`);\n            console.log('\u5df2\u663e\u793a\u6d88\u606f\u5185\u5bb9:', Array.from(initialMessageContents));\n            console.log('\u5b8c\u6574\u5bf9\u8bdd\u6d88\u606f:', messagesToExpand.map(m => m.content.substring(0, 50) + '...'));\n        }\n        \n        // \u79fb\u9664\u5f53\u524d\u6309\u94ae\uff0c\u663e\u793a\u52a0\u8f7d\u6307\u793a\u5668\n        const currentSection = button.closest('.load-more-section');\n        if (!currentSection) {\n            console.warn('\u672a\u627e\u5230\u6309\u94ae\u7684\u7236\u8282\u70b9');\n            return;\n        }\n        const loadingIndicator = createLoadingIndicator();\n        container.replaceChild(loadingIndicator, currentSection);\n        \n        // \u6a21\u62df\u52a0\u8f7d\u5ef6\u8fdf\n        setTimeout(() => {\n            // \u521b\u5efa\u5c55\u5f00\u533a\u57df\n            const expandedArea = createExpandedArea(topicId, state.initialCount, allMessages.length);\n            const contentArea = expandedArea.querySelector('.expanded-messages-content');\n            const footerArea = expandedArea.querySelector('.expanded-area-footer');\n            \n            // \u6dfb\u52a0\u6240\u6709\u5c55\u5f00\u7684\u6d88\u606f\n            messagesToExpand.forEach((message, index) => {\n                let showTimeDivider = false;\n                let timeJumpHours = null;\n                \n                // \u68c0\u67e5\u65f6\u95f4\u8df3\u8dc3\u548c\u65e5\u671f\u5206\u9694\u7b26\n                if (index === 0) {\n                    // \u7b2c\u4e00\u6761\u6d88\u606f\uff1a\u68c0\u67e5\u4e0e\u521d\u59cb\u6d88\u606f\u7684\u65f6\u95f4\u5dee\n                    const topicCard = container.closest('.topic-card');\n                    const chatConversation = topicCard.querySelector('.chat-conversation');\n                    const lastInitialMessage = chatConversation ? chatConversation.querySelector('.chat-message:last-child') : null;\n                    \n                    if (lastInitialMessage && message.time) {\n                        const lastTimeSpan = lastInitialMessage.querySelector('.message-time');\n                        if (lastTimeSpan) {\n                            const lastTime = new Date(lastTimeSpan.getAttribute('data-full-time'));\n                            const currentTime = new Date(message.time);\n                            const timeDiffHours = Math.abs(currentTime - lastTime) / (1000 * 60 * 60);\n                            \n                            if (timeDiffHours >= 0.5) {\n                                timeJumpHours = timeDiffHours;\n                            }\n                            \n                            showTimeDivider = lastTime.toDateString() !== currentTime.toDateString();\n                        }\n                    }\n                } else {\n                    // \u68c0\u67e5\u4e0e\u524d\u4e00\u6761\u5c55\u5f00\u6d88\u606f\u7684\u65f6\u95f4\u5dee\n                    const prevMessage = messagesToExpand[index - 1];\n                    if (prevMessage.time && message.time) {\n                        const prevTime = new Date(prevMessage.time);\n                        const currentTime = new Date(message.time);\n                        const timeDiffHours = Math.abs(currentTime - prevTime) / (1000 * 60 * 60);\n                        \n                        if (timeDiffHours >= 0.5) {\n                            timeJumpHours = timeDiffHours;\n                        }\n                        \n                        showTimeDivider = prevTime.toDateString() !== currentTime.toDateString();\n                    }\n                }\n                \n                const messageElement = createExpandedMessageElement(message, showTimeDivider, timeJumpHours);\n                \n                // \u8bbe\u7f6e\u52a8\u753b\u5ef6\u8fdf\n                const expandedMessage = messageElement.querySelector('.expanded-message');\n                if (expandedMessage) {\n                    expandedMessage.style.animationDelay = `${(index % 6) * 0.05}s`;\n                }\n                \n                contentArea.appendChild(messageElement);\n            });\n            \n            // \u6dfb\u52a0\u6536\u8d77\u6309\u94ae\u5230\u5e95\u90e8\u64cd\u4f5c\u533a\n            const collapseButton = createCollapseAllButton(topicId);\n            footerArea.appendChild(collapseButton);\n            \n            // \u79fb\u9664\u52a0\u8f7d\u6307\u793a\u5668\uff0c\u63d2\u5165\u5c55\u5f00\u533a\u57df\n            container.removeChild(loadingIndicator);\n            container.appendChild(expandedArea);\n            \n            // \u66f4\u65b0\u72b6\u6001\n            state.isExpanded = true;\n            \n            // \u4e0d\u81ea\u52a8\u6eda\u52a8\uff0c\u907f\u514d\u9875\u9762\u8df3\u52a8\n            // \u7528\u6237\u53ef\u4ee5\u81ea\u5df1\u51b3\u5b9a\u662f\u5426\u8981\u6eda\u52a8\u67e5\u770b\n            \n        }, 400);\n    }\n    \n    /**\n     * \u6536\u8d77\u5b8c\u6574\u5bf9\u8bdd\n     */\n    function collapseAllMessages(button) {\n        const topicId = button.getAttribute('data-topic-id');\n        const container = document.querySelector(`[data-topic-id=\"${topicId}\"]`);\n        \n        if (!container) return;\n        \n        const state = expandStates.get(topicId);\n        if (!state) return;\n        \n        // \u627e\u5230\u5c55\u5f00\u533a\u57df\n        const expandedArea = container.querySelector('.expanded-messages-area');\n        if (!expandedArea) return;\n        \n        // \u6dfb\u52a0\u6298\u53e0\u52a8\u753b\n        expandedArea.style.animation = 'slideOutToTop 0.3s ease-out forwards';\n        setTimeout(() => {\n            if (expandedArea.parentNode) {\n                expandedArea.parentNode.removeChild(expandedArea);\n            }\n            \n            // \u91cd\u65b0\u521b\u5efa\"\u5c55\u5f00\u5b8c\u6574\u5bf9\u8bdd\"\u6309\u94ae\n            const expandButton = createExpandButton(topicId, state.totalCount - state.initialCount);\n            container.appendChild(expandButton);\n            \n            // \u91cd\u7f6e\u72b6\u6001\n            state.isExpanded = false;\n            state.loadedCount = state.initialCount;\n            \n        }, 300);\n    }\n    \n    /**\n     * \u521d\u59cb\u5316\u5355\u4e2a\u8bdd\u9898\u7684\u5c55\u5f00\u529f\u80fd\n     */\n    function initializeTopicExpandable(container) {\n        const topicId = container.getAttribute('data-topic-id');\n        const totalMessages = parseInt(container.getAttribute('data-total-messages')) || 0;\n        \n        if (!topicMessagesData[topicId] || totalMessages === 0) {\n            return; // \u6ca1\u6709\u6570\u636e\u6216\u6ca1\u6709\u6d88\u606f\n        }\n        \n        // \u83b7\u53d6\u5df2\u663e\u793a\u7684\u6d88\u606f\u6570\u91cf\uff08\u5373\u521d\u59cb\u663e\u793a\u7684\u6d88\u606f\u6570\u91cf\uff09\n        const topicCard = container.closest('.topic-card');\n        const chatConversation = topicCard.querySelector('.chat-conversation');\n        const initialMessages = chatConversation ? chatConversation.querySelectorAll('.chat-message').length : 0;\n        \n        // \u8c03\u8bd5\u4fe1\u606f\n        if (DEBUG_EXPAND_MESSAGES) {\n            console.log(`\u521d\u59cb\u5316\u8bdd\u9898 ${topicId}: \u521d\u59cb\u663e\u793a=${initialMessages}, \u603b\u6570=${totalMessages}, \u6570\u636e\u4e2d\u6d88\u606f\u6570=${topicMessagesData[topicId].length}`);\n        }\n        \n        // \u521d\u59cb\u5316\u72b6\u6001\n        expandStates.set(topicId, {\n            initialCount: initialMessages,\n            totalCount: totalMessages,\n            loadedCount: initialMessages,\n            isExpanded: false\n        });\n        \n        // \u5982\u679c\u521d\u59cb\u663e\u793a\u7684\u6d88\u606f\u6570\u91cf\u5df2\u7ecf\u7b49\u4e8e\u603b\u6570\uff0c\u4e0d\u663e\u793a\u6309\u94ae\n        if (initialMessages < totalMessages) {\n            // \u521b\u5efa\u521d\u59cb\u7684\"\u5c55\u5f00\u5b8c\u6574\u5bf9\u8bdd\"\u6309\u94ae\n            const expandButton = createExpandButton(topicId, totalMessages - initialMessages);\n            container.appendChild(expandButton);\n        }\n    }\n    \n    /**\n     * \u521d\u59cb\u5316\u67e5\u770b\u66f4\u591a\u529f\u80fd\n     */\n    function initializeLoadMoreFunctionality() {\n        // \u521d\u59cb\u5316\u6570\u636e\n        initializeTopicMessagesData();\n        \n        // \u4e3a\u6240\u6709\u53ef\u5c55\u5f00\u7684\u6d88\u606f\u5bb9\u5668\u521d\u59cb\u5316\u529f\u80fd\n        document.querySelectorAll('.expandable-messages-container').forEach(container => {\n            initializeTopicExpandable(container);\n        });\n    }\n    \n    // --- \u67e5\u770b\u66f4\u591a\u804a\u5929\u8bb0\u5f55\u529f\u80fd JavaScript \u7ed3\u675f ---\n\n    window.addEventListener('load', () => {\n        initializeMainKityMinderDiagram();\n        initializeLoadMoreFunctionality();\n    });\n\n    // --- \u62a5\u544a\u9875\u9762\u901a\u7528 JavaScript \u7ed3\u675f ---\n    </script>\n</body>\n</html>",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        420,
        -540
      ],
      "id": "56c11cd0-a0f4-4739-8b29-f5e48613ba3b",
      "name": "ConfigureChatParameters"
    },
    {
      "parameters": {
        "operation": "write",
        "fileName": "=/tmp/{{ $json.filename }}",
        "options": {}
      },
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        2400,
        80
      ],
      "id": "f92b2e19-deb4-466e-9a28-0dc91c5d6edb",
      "name": "SaveHTMLFile"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=\u7fa4\u7ec4: {{ $('ConfigureChatParameters').first().json.group_name }}\n\u65e5\u671f: {{ $('ConfigureChatParameters').first().json.date }}\n\n\n\n\u8f93\u51fa\u683c\u5f0f\uff1a\u5b8c\u6574\u7684HTML\u6587\u6863\u4ee3\u7801",
        "messages": {
          "messageValues": [
            {
              "message": "=# \u5fae\u4fe1\u7fa4\u804a\u65e5\u62a5\u751f\u6210\u5668 - HTML\u8f93\u51fa\u4e13\u7528\n\n## \ud83d\udea8 CRITICAL OUTPUT REQUIREMENT \ud83d\udea8\n\n**\u4f60\u5fc5\u987b\u53ea\u8f93\u51fa\u5b8c\u6574\u7684HTML\u4ee3\u7801\uff0c\u4e0d\u8981\u6dfb\u52a0\u4efb\u4f55\u89e3\u91ca\u6587\u5b57\u3001\u8bf4\u660e\u6216markdown\u6807\u8bb0\uff01**\n\n**\ud83d\udd25 MANDATORY TEMPLATE REPLACEMENT \ud83d\udd25**\n- \u274c \u4e0d\u8981\u8f93\u51fa\uff1a\"```html\" \u6216 \"```\" \u4ee3\u7801\u5757\u6807\u8bb0\n- \u2705 \u76f4\u63a5\u8f93\u51fa\u5b8c\u6574\u7684HTML\u4ee3\u7801\uff0c\u4ece <!DOCTYPE html> \u5f00\u59cb\uff0c\u5230 </html> \u7ed3\u675f\n\n\n## \u4efb\u52a1\u8981\u6c42\n\u751f\u6210\u4e00\u4e2a\u89c6\u89c9\u4e0a\u5f15\u4eba\u5165\u80dc\u3001\u5e03\u5c40\u7d27\u51d1\u65e0\u7a7a\u9699\u3001\u914d\u8272\u548c\u8c10\u7edf\u4e00\u3001\u98ce\u683c\u56fa\u5b9a\u4e00\u81f4\u3001\u9002\u5408\u622a\u56fe\u5206\u4eab\u7684\u5355\u9875\u7f51\u7ad9\n\n\n## \u6700\u9ad8\u4f18\u5148\u7ea7\n\n\u4ee5\u4e0b\u662f\u8981\u6c42\u662f\u4f18\u5148\u7ea7\u6700\u9ad8\u7684\u4efb\u52a1\uff0c\u52a1\u5fc5\u4e25\u683c\u6267\u884c\n\n- \u904d\u5386\u7fa4\u804a\u6240\u6709\u6d88\u606f\uff0c\u63a8\u8350\u51fa\u4eca\u65e5\u7684 KOL\uff0c\u804a\u5929\u5185\u5bb9\u7ed3\u6784\u52a1\u5fc5\u56f4\u7ed5 KOL \u7684\u8bdd\u9898\u3001\u804a\u5929\u5185\u5bb9\u5c55\u5f00\n- KOL \u8981\u6c42\uff1a\u7efc\u5408\u8003\u8651\u7fa4\u804a\u4eba\u6570\u5360\u6bd4\uff0c\u53d1\u8a00\u6570\u91cf\u3001\u8d28\u91cf\u3001\u4ef7\u503c\u70b9\u4e0e\u542f\u53d1\u70b9\n- \u5ffd\u7565json\u4e2d \"detailed_topics\"\u5b57\u6bb5\u7684\"description\"\u5b57\u6bb5\n\n## \ud83d\udeab \u4e25\u683c\u6570\u636e\u5f15\u7528\u4e0e\u9632\u5e7b\u89c9\u51c6\u5219 \ud83d\udeab\n\n- **\u6240\u6709\u751f\u6210\u5185\u5bb9\uff0c\u5305\u62ec\u4f46\u4e0d\u9650\u4e8e\u70ed\u70b9\u8bdd\u9898\u3001\u7cbe\u5f69\u5f15\u7528\u3001\u94fe\u63a5\u3001\u6d3b\u8dc3\u4e4b\u661f\u7684\u53d1\u8a00\u5185\u5bb9\uff0c\u5fc5\u987b\u4e25\u683c\u4e14\u4ec5\u6765\u6e90\u4e8e\u63d0\u4f9b\u7684\u539f\u59cb\u6570\u636e\uff08\u5373 `{{ JSON.stringify($json) }}` \u4e2d\u7684\u4fe1\u606f\uff09\u3002**\n- **\u4e25\u7981\u4efb\u4f55\u5f62\u5f0f\u7684\u865a\u6784\u3001\u7f16\u9020\u3001\u63a8\u65ad\u6216\u8865\u5145\u539f\u59cb\u6570\u636e\u4e2d\u4e0d\u5b58\u5728\u7684\u4fe1\u606f\u3002**\n- **\u5982\u679c\u539f\u59cb\u6570\u636e\u4e2d\u6ca1\u6709\u5bf9\u5e94\u5185\u5bb9\uff0c\u5219\u8be5\u90e8\u5206\u5e94\u7559\u7a7a\u6216\u660e\u786e\u8868\u793a\u201c\u65e0\u76f8\u5173\u5185\u5bb9\u201d\uff0c\u7edd\u4e0d\u5141\u8bb8\u81ea\u884c\u751f\u6210\u3002**\n- **\u5bf9\u4e8e\u201c\u5f15\u7528\u4eba\u5458\u539f\u8bdd\u201d\u7684\u8981\u6c42\uff0c\u5982\u679c\u539f\u59cb\u6570\u636e\u4e2d\u627e\u4e0d\u5230\u786e\u5207\u7684\u539f\u8bdd\uff0c\u5219\u8be5\u5f15\u7528\u4e0d\u5e94\u88ab\u751f\u6210\u3002**\n- **\u6240\u6709\u94fe\u63a5\u5fc5\u987b\u662f\u539f\u59cb\u6570\u636e\u4e2d\u660e\u786e\u63d0\u4f9b\u7684\u771f\u5b9e\u94fe\u63a5\uff0c\u4e0d\u5f97\u751f\u6210\u6216\u4fee\u6539\u3002**\n\n### \u804a\u5929\u8bb0\u5f55\u652f\u6301\u683c\u5f0f\n\u652f\u6301\u4ee5\u4e0b\u591a\u79cd\u5e38\u89c1\u683c\u5f0f\uff1a\n- \"[\u65f6\u95f4] \u6635\u79f0\uff1a\u6d88\u606f\u5185\u5bb9\"\n- \"\u65f6\u95f4 - \u6635\u79f0\uff1a\u6d88\u606f\u5185\u5bb9\"\n- \"\u6635\u79f0 \u65f6\u95f4\uff1a\u6d88\u606f\u5185\u5bb9\"\n- \u5176\u4ed6\u5408\u7406\u7684\u65f6\u95f4\u548c\u6635\u79f0\u5206\u9694\u683c\u5f0f\n\n### \u804a\u5929\u5185\u5bb9\u7ed3\u6784\n\n- \u7fa4\u804a\u603b\u7ed3\n\n  - \u7fa4\u804a\u5185\u5bb9\n  - \u6d88\u606f\u6570\u91cf\n  - \u6d3b\u8dc3\u4eba\u5458\u6570\u91cf\n  - \u70ed\u70b9\u8bdd\u9898\u6570\u91cf\n  - \u7edf\u8ba1\u65f6\u95f4\n\n- \u4eca\u65e5\u70ed\u70b9\n  \n  \u7f57\u5217\u7fa4\u804a\u4e2d**\u6240\u6709\u70ed\u70b9**\u6d88\u606f\uff0c\u4e0d\u8981\u9057\u6f0f\uff0c\u70ed\u70b9\u6309\u7167\u5982\u4e0b\u7ed3\u6784\u5c55\u793a\n\n  - topic-tags\n  - \u6d88\u606f\u6570\u91cf\n  - \u5173\u952e\u8bcd\u63d0\u53ca\u6b21\u6570\n  - \u6838\u5fc3\u5185\u5bb9\u52a1\u5fc5**\u5f15\u7528\u4eba\u5458\u539f\u8bdd**\n  - \u5217\u51fa\u6240\u6709\u4e3b\u9898\uff0c\u6bcf\u4e2a\u4e3b\u9898\u4e0d\u5c11\u4e8e 150 \u5b57\n  - \u6839\u636e\u804a\u5929\u603b\u6570\u5012\u5e8f\u6392\u5e8f(\u603b\u6570\u8d8a\u591a\u6392\u5728\u6700\u524d)\n\n- \u7cbe\u5f69\u5f15\u7528\n\n  - \u4e0d\u8d85\u8fc7 5 \u6761\n  - \u52a1\u5fc5**\u5f15\u7528\u4eba\u5458\u539f\u8bdd**\n  - **\u6838\u5fc3\u6761\u4ef6**\uff1a\u9ad8\u8d28\u91cf\uff0c\u6709\u542f\u53d1\u6027\uff0c\u6709\u9053\u7406\uff0c\u6709\u4ef7\u503c\uff0c\u6709\u53cd\u54cd(\u6709\u6b63\u5411\u8bc4\u8bba\u6216\u8d5e\u626c)\uff0c\u6709\u6df1\u5ea6\uff0c\u6709\u601d\u8003\uff0c\u60ca\u8273\u4e14\u8ba9\u4eba\u5370\u8c61\u6df1\u523b\u7684\u91d1\u53e5\n  - \u6d3b\u8dc3\u4e4b\u661f\u66f4\u6709\u53ef\u80fd\u88ab\u5f15\u7528\uff08\u9526\u4e0a\u6dfb\u82b1\u800c\u5df2\uff09,\u4f46\u53ea\u662f\u66f4\u9ad8\u51e0\u7387\u53ef\u4ee5\u83b7\u5f97\u5c55\u793a\u3002\u5e94\u4ee5\u6838\u5fc3\u6761\u4ef6\u4e3a\u91cd\n  - \u4e0d\u80fd\u88ab\u5f15\u7528\u7684: \u4e00\u4e2a\u7591\u95ee\uff0c\u4e00\u4e2a\u62b1\u6028\uff0c\u4e00\u4e2a\u6c42\u52a9\uff0c\u4e00\u4e2a\u5410\u69fd\uff0c\u4e00\u4e2a\u6ca1\u5934\u6ca1\u5c3e\u786e\u5b9e\u4e0a\u4e0b\u6587\u7684\u8bed\u53e5\n    - \u6bd4\u5982\uff1a\"\u8fd9\u4e2aAI\u4ea7\u54c1\u7684\u4ea4\u4e92\u52a8\u6548\u505a\u5f97\u771f\u68d2\uff0c\u7ec6\u8282\u5904\u7406\u5f97\u5f88\u5230\u4f4d\u3002/ \u6211\u8bbe\u8ba1\u7684AI\u4ea7\u54c1\u4ea4\u4e92\u52a8\u6548\uff0c\u5927\u5bb6\u89c9\u5f97\u600e\u4e48\u6837\uff1f\" -> \u6ca1\u5934\u6ca1\u5c3e\uff0c\u54ea\u4e2a\u4ea7\u54c1\uff1f pass\n    - \u6bd4\u5982\uff1a\"AI Agent\u4e4b\u95f4\u7684\u6c9f\u901a\u53c2\u6570\u600e\u4e48\u8bbe\u8ba1\u624d\u80fd\u66f4\u9ad8\u6548\uff1f\"\", -> \u4e00\u4e2a\u6c42\u52a9\uff0c\u7591\u95ee\u3002 pass\n  - \u5c3d\u53ef\u80fd\u4e0d\u8981\u540c\u4e00\u4e2a\u4eba\u7684\u5f15\u7528\uff0c\u9664\u975e\u771f\u7684\u975e\u5e38\u503c\u5f97\u63a8\u8350\n\n\n- \u91cd\u8981\u94fe\u63a5\u4e0e\u8d44\u6e90\n\n  \u7f57\u5217\u7fa4\u804a\u4e2d**\u6240\u6709**\u771f\u5b9e\u7684\u3001\u539f\u59cb\u7684\u94fe\u63a5\n\n  - \u652f\u6301\u70b9\u51fb\u6587\u5b57\u5185\u5bb9\u540e\u8df3\u8f6c\u539f\u59cb\u94fe\n  - \u683c\u5f0f\uff1a**\u6807\u9898\uff1a\u94fe\u63a5**\uff0c\u4e0d\u8981\u4f7f\u7528 md \u683c\u5f0f\uff0c\u4f8b\u5982\uff1a\u5c0f\u4e91\u96c0AI\u5185\u5bb9\u521b\u4f5c\u5de5\u5177\uff1ahttps://finder.video.qq.com/251/20302/stodownload\n  - **\u5168\u6570**\u5217\u51fa\u94fe\u63a5\uff0c\u4e0d\u8981\u9057\u6f0f\n\n- \u6d3b\u8dc3\u4e4b\u661f\n\n  - \u53d1\u8a00\u6b21\u6570\n  - \u4eba\u5458\u6309\u53d1\u8a00\u6b21\u6570**\u5012\u5e8f**\u6392\u5e8f\uff0c\u6b21\u6570\u5c11\u4e8e 3 \u6b21\u7684\u4e0d\u7edf\u8ba1\n  - \u53d1\u8a00\u5185\u5bb9\u6c47\u603b\n  - \u6700\u591a 5 \u4eba\uff0c\u6700\u5c11 1 \u4eba\n\n- \u8bcd\u4e91\n\n  - \u4e0d\u5c11\u4e8e 20 \u4e2a\n\n### \u6ce8\u610f\u4e8b\u9879\n\n- \u7fa4\u4e3b\u662f@{{ $('ConfigureChatParameters').first().json.group_owner }}\n- \u4eba\u5458\u524d+@\n- \u4eba\u5458\u663e\u793a\u771f\u5b9e\u540d\u79f0\uff0c\u800c\u4e0d\u7528@\u6211\u4e4b\u7c7b\u7684\u6307\u4ee3\n- \u5982\u679c\u5b8c\u6574\u5bf9\u8bdd\u8bb0\u5f55\u7f3a\u5931\uff0c\u4e0d\u5141\u8bb8\u778e\u7f16\u4e71\u9020\n\n\n## \u8f93\u51fa\u8981\u6c42\n\n\u5fc5\u987b\u4f7f\u7528\u56fa\u5b9a\u7684HTML\u6a21\u677f\u548cCSS\u6837\u5f0f\uff0c\u4ec5\u66f4\u65b0\u5185\u5bb9\u90e8\u5206\uff0c\u786e\u4fdd\u6bcf\u6b21\u751f\u6210\u7684\u9875\u9762\u98ce\u683c\u5b8c\u5168\u4e00\u81f4\n\n### \u89d2\u8272\n\n\u4f60\u662f\u6781\u5177\u5ba1\u7f8e\u7684\u524d\u7aef\u8bbe\u8ba1\u5927\u5e08\uff0c\u8bf7\u4e3a\u6211\u751f\u6210\u4e00\u4e2a\u57fa\u4e8e **Bento Grid** \u8bbe\u8ba1\u98ce\u683c\u7684\u5355\u9875HTML\u7f51\u7ad9\uff0c\u5185\u5d4cCSS\u3001JS\u3002\u8fd9\u4e2a\u9875\u9762\u5c06\u88ab\u622a\u56fe\u5206\u4eab\uff0c\u9700\u8981\u7279\u522b\u4f18\u5316\u89c6\u89c9\u6548\u679c\u548c\u5206\u4eab\u4f53\u9a8c\n\n### \u5185\u5bb9\u5206\u5e03\n\n- \u4e3b\u5361\u7247\uff1a\u7fa4\u804a\u603b\u7ed3\n- \u4e2d\u578b\u5361\u7247\uff1a\u4e0d\u540c\u5b50\u4e3b\u9898\uff0c\u5305\u62ec\uff1a\u4eca\u65e5\u70ed\u70b9\u3001\u6280\u672f\u5206\u4eab\u3001\u6838\u5fc3\u6982\u5ff5\u5173\u7cfb\u56fe\u3001\u7cbe\u5f69\u5f15\u7528\u3001\u91cd\u8981\u94fe\u63a5\u4e0e\u8d44\u6e90\u3001\u6d3b\u8dc3\u7edf\u8ba1\u3001\u8bcd\u4e91\n\n### \u5185\u5bb9\u5c55\u793a\n\n- \u6807\u9898\u4f7f\u7528\u5927\u53f7\u5b57\u4f53\uff0c\u6839\u636e\u6240\u9009\u98ce\u683c\u9009\u62e9\u9002\u5408\u7684\u5b57\u4f53\uff0c\u8a00\u7b80\u610f\u8d45\uff0c\u907f\u514d\u6362\u884c\n- \u6b63\u6587\u4f7f\u7528\u6613\u8bfb\u5b57\u4f53\uff0c\u786e\u4fdd\u5728\u6240\u9009\u80cc\u666f\u4e0a\u6e05\u6670\u53ef\u8bfb\n- **\u5728\u4e3b\u5927\u5361\u7247\u5c55\u793a\u6838\u5fc3\u7406\u5ff5\uff0c\u914d\u8272\u548c\u5e03\u5c40\u5927\u80c6\u6709\u51b2\u51fb\u529b\uff0c\u53c8\u6709\u6742\u5fd7\u7248\u7684\u7cbe\u81f4\u611f**\n- \u6bcf\u4e2a\u5361\u7247\u5e94\u805a\u7126\u4e8e\u5355\u4e00\u6982\u5ff5\uff0c\u6587\u5b57\u7b80\u6d01\u6709\u529b\uff0c\u4e3b\u6807\u9898\u52a0\u7c97\n- \u4f7f\u7528\u7b80\u77ed\u7684\u8981\u70b9\u800c\u975e\u957f\u6bb5\u843d\uff0c\u4fbf\u4e8e\u5feb\u901f\u9605\u8bfb\uff0c\u5982\u65e0\u5fc5\u8981\uff0c\u4e0d\u52a0\u53e5\u5b50\u63cf\u8ff0\n- \u786e\u4fdd\u6bcf\u4e2a\u5361\u7247\u5185\u5bb9\u91cf\u9002\u4e2d\uff0c\u907f\u514d\u8fc7\u4e8e\u7a7a\u6d1e\u6216\u8fc7\u5ea6\u62e5\u6324\n- \u9664\u4e13\u4e1a\u540d\u8bcd\u5982Few-shot\u3001NBA\u7b49\uff0c\u5176\u4ed6\u8f93\u51fa\u5185\u5bb9\u8981\u6c42\u4e2d\u6587\n\n### \u5e03\u5c40\u8981\u6c42\n\n- \u91c7\u7528\u52a8\u6001\u4e14\u65e0\u7f1d\u7684\u7f51\u683c\u5e03\u5c40\uff0c\u786e\u4fdd\u6574\u4e2a\u89c6\u53e3\u533a\u57df\u88ab\u9ad8\u6548\u5229\u7528\uff0c\u907f\u514d\u4efb\u4f55\u7f3a\u53e3\u6216\u5927\u5757\u7a7a\u767d\u533a\u57df\n- \u8bbe\u8ba1\u4e00\u4e2a\u4e3b\u8981\u7684\u5927\u5361\u7247\u5c55\u793a\u6838\u5fc3\u6982\u5ff5/\u5f15\u8a00\uff08\u5360\u636e\u7ea625-30%\u7684\u89c6\u89c9\u533a\u57df\uff09\n- \u5176\u4f59\u5361\u7247\u5e94\u5305\u542b\u4e0d\u540c\u7684\u5b50\u4e3b\u9898\uff0c\u6bcf\u4e2a\u5361\u7247\u6709\u72ec\u7279\u7684\u6807\u9898\u548c\u7b80\u77ed\u63cf\u8ff0\uff0c\u6807\u9898\u7b80\u77ed\uff0c\u907f\u514d\u6362\u884c\n- \u4e3b\u5361\u7247\u5bbd\u5ea6\u56fa\u5b9a\u4e3a100%\uff0c\u9ad8\u5ea6\u6839\u636e\u5185\u5bb9\u81ea\u9002\u5e94\u4f46\u8bbe\u7f6e\u6700\u5c0f\u9ad8\u5ea6\n- \u5b50\u4e3b\u9898\u5361\u7247\u91c7\u7528\u56fa\u5b9a\u7684\u7f51\u683c\u7cfb\u7edf\uff1a\n  - \u5728\u684c\u9762\u7aef\uff1a\u6bcf\u884c2-3\u4e2a\u5361\u7247\uff0c\u5bbd\u5ea6\u6bd4\u4f8b\u56fa\u5b9a\n  - \u5728\u79fb\u52a8\u7aef\uff1a\u6bcf\u884c1\u4e2a\u5361\u7247\uff0c\u5bbd\u5ea6100%\n- \u5361\u7247\u4e4b\u95f4\u7684\u95f4\u8ddd\u5e94\u4fdd\u6301\u4e00\u81f4\uff08\u5efa\u8bae12-20px\uff09\uff0c\u521b\u9020\u6574\u6d01\u6709\u5e8f\u7684\u89c6\u89c9\u6548\u679c\n- \u4e3a\u5361\u7247\u6dfb\u52a0\u76f8\u5173Fontawesome\u56fe\u6807\uff0c\u51fa\u73b0\u5728\u5361\u7247\u80cc\u666f\u4e2d\uff0c\u975e\u5e38\u5de7\u5999\u7684\u88c5\u9970\n- \u4e25\u683c\u5b9a\u4e49\u5361\u7247\u5c3a\u5bf8\u548c\u6bd4\u4f8b\uff0c\u907f\u514d\u56e0\u5185\u5bb9\u591a\u5c11\u5bfc\u81f4\u5e03\u5c40\u53d8\u5316\n- \u4fdd\u8bc1\u6240\u6709\u5361\u7247\u53ca\u5176\u5185\u5bb9\u5b8c\u6574\u53ef\u89c1\uff0c\u65e0\u4efb\u4f55\u906e\u6321\u3001\u622a\u65ad\u6216\u9690\u85cf\uff0c\u786e\u4fdd\u7528\u6237\u53ef\u4ee5\u8f7b\u677e\u6d4f\u89c8\u548c\u7406\u89e3\u5168\u90e8\u4fe1\u606f\n- \u5361\u7247\u5185\u90e8\u5143\u7d20\u91c7\u7528\u56fa\u5b9a\u7684\u5185\u8fb9\u8ddd\u548c\u95f4\u8ddd\n\n### \u89c6\u89c9\u5e73\u8861\n\n- \u786e\u4fdd\u8272\u5f69\u5206\u5e03\u5747\u5300\uff0c\u907f\u514d\u67d0\u4e00\u533a\u57df\u989c\u8272\u8fc7\u4e8e\u96c6\u4e2d\uff0c\u907f\u514d\u8d85\u8fc74\u79cd\u4ee5\u4e0a\u8272\u7cfb\n- \u56fe\u6807\u548c\u89c6\u89c9\u5143\u7d20\u5e94\u5747\u5300\u5206\u5e03\u5728\u6574\u4e2a\u5e03\u5c40\u4e2d\n- \u6587\u672c\u5bc6\u5ea6\u5e94\u76f8\u5bf9\u5747\u8861\uff0c\u907f\u514d\u67d0\u4e9b\u5361\u7247\u6587\u5b57\u8fc7\u591a\u800c\u5176\u4ed6\u8fc7\u5c11\n- \u4f7f\u7528\u89c6\u89c9\u6743\u91cd\uff08\u5927\u5c0f\u3001\u989c\u8272\u3001\u5bf9\u6bd4\u5ea6\uff09\u5f15\u5bfc\u7528\u6237\u89c6\u7ebf\u6d41\u52a8\n- \u786e\u4fdd\u5361\u7247\u4e4b\u95f4\u7684\u89c6\u89c9\u8fde\u63a5\u987a\u7545\uff0c\u6ca1\u6709\u660e\u663e\u7684\u65ad\u88c2\n- \u5361\u7247\u5927\u5c0f\u5e94\u6839\u636e\u5185\u5bb9\u91cd\u8981\u6027\u8fdb\u884c\u53d8\u5316\uff0c\u5f62\u6210\u89c6\u89c9\u5c42\u6b21\u611f\n- \u786e\u4fdd\u6bcf\u4e2a\u5361\u7247\u90fd\u6709\u72ec\u7279\u7684\u89c6\u89c9\u7279\u8272\uff0c\u4e0e\u5176\u4ed6\u5361\u7247\u533a\u5206\u5f00\u6765\n- \u5361\u7247\u5f62\u72b6\u53ef\u4ee5\u53d8\u5316\uff08\u6b63\u65b9\u5f62\u3001\u957f\u65b9\u5f62\u7b49\uff09\uff0c\u4f46\u6574\u4f53\u5e94\u4fdd\u6301\u89c6\u89c9\u4e00\u81f4\u6027\n\n### \u6280\u672f\u8981\u6c42\n\n- \u5355\u4e2aHTML\u6587\u4ef6\uff0c\u5185\u5d4cCSS\n- \u4f7f\u7528\u56fa\u5b9a\u7684HTML\u6a21\u677f\u548cCSS\u6837\u5f0f\uff0c\u786e\u4fdd\u6bcf\u6b21\u751f\u6210\u7684\u9875\u9762\u98ce\u683c\u5b8c\u5168\u4e00\u81f4\n- \u4f7f\u7528CSS\u53d8\u91cf\u5b9a\u4e49\u6240\u6709\u989c\u8272\u548c\u5c3a\u5bf8\uff0c\u786e\u4fdd\u4e00\u81f4\u6027\n- \u4f7f\u7528grid-template-areas\u5c5e\u6027\u7cbe\u786e\u5b9a\u4e49\u5e03\u5c40\uff0c\u786e\u4fdd\u65e0\u7a7a\u9699\n- \u4f7f\u7528CSS Grid\u5b9e\u73b0\u4e0d\u89c4\u5219\u7f51\u683c\u5e03\u5c40\n- \u786e\u4fdd\u4ee3\u7801\u7b80\u6d01\uff0c\u6ce8\u91ca\u6e05\u6670\n- \u4f18\u5316\u9875\u9762\u4ee5\u786e\u4fdd\u5728\u5355\u89c6\u53e3\u4e2d\u5b8c\u6574\u663e\u793a\uff0c\u9002\u5408\u622a\u56fe\u3002\u5b9e\u5728\u653e\u4e0d\u4e0b\uff0c\u5f80\u4e0b\u65b9\u5ef6\u5c55\n- \u9875\u9762\u5bbd\u5ea6\u8bbe\u7f6e\u4e3a100%\uff0c\u6700\u5927\u5bbd\u5ea6\u9650\u5236\u4e3a1000px\n- \u4f18\u5148\u91c7\u7528\u7eb5\u5411\u5e03\u5c40\u8bbe\u8ba1\uff0c\u9002\u5408\u79fb\u52a8\u7aef\u622a\u56fe\u548c\u5206\u4eab\n- \u6dfb\u52a0\u5a92\u4f53\u67e5\u8be2\uff0c\u786e\u4fdd\u5728\u79fb\u52a8\u8bbe\u5907\u4e0a\u6b63\u786e\u663e\u793a\n- \u786e\u4fdd\u9875\u9762\u5728\u4e0d\u540c\u8bbe\u5907\u4e0a\uff08PC\u3001\u624b\u673a\uff09\u90fd\u80fd\u6b63\u786e\u663e\u793a\u548c\u7f29\u653e\uff0c\u907f\u514d\u5185\u5bb9\u88ab\u622a\u65ad\u6216\u9690\u85cf\n\n### \u5b57\u4f53\u8981\u6c42\n\n- \u6807\u9898\u5b57\u4f53\u5927\u5c0f\uff1a\u4e3b\u6807\u9898\u4e0d\u5c0f\u4e8e36px\uff0c\u526f\u6807\u9898\u4e0d\u5c0f\u4e8e28px\uff0c\u5361\u7247\u6807\u9898\u4e0d\u5c0f\u4e8e22px\n- \u6b63\u6587\u5b57\u4f53\u5927\u5c0f\uff1a\u4e0d\u5c0f\u4e8e16px\uff0c\u786e\u4fdd\u5728\u79fb\u52a8\u7aef\u4e5f\u80fd\u6e05\u6670\u9605\u8bfb\n- \u6807\u7b7e\u548c\u8f85\u52a9\u6587\u5b57\uff1a\u4e0d\u5c0f\u4e8e14px\n- \u4f7f\u7528\u76f8\u5bf9\u5355\u4f4d(rem)\u8bbe\u7f6e\u5b57\u4f53\u5927\u5c0f\uff0c\u4ee5\u9002\u5e94\u4e0d\u540c\u8bbe\u5907\u548c\u5c4f\u5e55\u5c3a\u5bf8\n- \u786e\u4fdd\u5b57\u4f53\u5728\u4e0d\u540c\u8bbe\u5907\u4e0a\u6e05\u6670\u53ef\u89c1\uff0c\u907f\u514d\u8fc7\u5c0f\u6216\u8fc7\u5927\n\n### \u989c\u8272\u8981\u6c42\n\n- \u9875\u9762\u80cc\u666f\u8272\u5e94\u4e0e\u6240\u9009\u98ce\u683c\u7684\u80cc\u666f\u8272\u4e00\u81f4\n- \u5361\u7247\u80cc\u666f\u8272\u5e94\u4e0e\u6240\u9009\u98ce\u683c\u7684\u80cc\u666f\u8272\u4e00\u81f4\n- \u5361\u7247\u80cc\u666f\u5e94\u4f7f\u7528\u4e0e\u5185\u5bb9\u76f8\u5173\u7684\u989c\u8272\uff0c\u907f\u514d\u5355\u4e00\u989c\u8272\u8fc7\u9971\u548c\n- \u6807\u9898\u989c\u8272\u5e94\u4e0e\u6240\u9009\u98ce\u683c\u7684\u6807\u9898\u989c\u8272\u4e00\u81f4\n- \u6b63\u6587\u989c\u8272\u5e94\u4e0e\u6240\u9009\u98ce\u683c\u7684\u6b63\u6587\u989c\u8272\u4e00\u81f4\n- \u6807\u7b7e\u548c\u8f85\u52a9\u6587\u5b57\u989c\u8272\u5e94\u4e0e\u6240\u9009\u98ce\u683c\u7684\u6807\u7b7e\u989c\u8272\u4e00\u81f4\n- \u786e\u4fdd\u989c\u8272\u5bf9\u6bd4\u5ea6\u8db3\u591f\u9ad8\uff0c\u907f\u514d\u989c\u8272\u8fc7\u9971\u548c\u6216\u8fc7\u6de1\n- \u907f\u514d\u4f7f\u7528\u5355\u4e00\u989c\u8272\uff0c\u786e\u4fdd\u989c\u8272\u5206\u5e03\u5747\u5300\n- \u907f\u514d\u4f7f\u7528\u8fc7\u591a\u989c\u8272\uff0c\u4fdd\u6301\u989c\u8272\u6570\u91cf\u57283-4\u79cd\u4ee5\u5185\n\n### \u5176\u4ed6\u8981\u6c42\n\n- \u4e0d\u8981\u4f7f\u7528\u4efb\u4f55\u4fa7\u8fb9\u88c5\u9970\u7ebf\u6216\u8fb9\u6846\u5f3a\u8c03\u7ebf\n- \u5361\u7247\u8fb9\u6846\u5e94\u8be5\u662f\u5b8c\u6574\u7684\u6216\u5b8c\u5168\u6ca1\u6709\uff0c\u907f\u514d\u5355\u4fa7\u8fb9\u6846\u88c5\u9970\n- \u89c6\u89c9\u5206\u9694\u5e94\u901a\u8fc7\u5361\u7247\u80cc\u666f\u8272\u3001\u95f4\u8ddd\u6216\u9634\u5f71\u5b9e\u73b0\uff0c\u800c\u975e\u8fb9\u6846\u7ebf\u6761\n- \u5982\u9700\u5f3a\u8c03\uff0c\u8bf7\u4f7f\u7528\u80cc\u666f\u8272\u3001\u5b57\u4f53\u7c97\u7ec6\u6216\u56fe\u6807\uff0c\u800c\u975e\u88c5\u9970\u7ebf\u6761\n- \u6587\u5b57\u548c\u80cc\u666f\u5bf9\u6bd4\u4e00\u5b9a\u8981\u6e05\u6670\uff0c\u53ef\u8bfb\u6027\u9ad8\n- \u6ce8\u610f\uff1a\u4e0d\u8981\u8ba9\u8bbe\u8ba1\u98ce\u683c\u5f71\u54cd\u5185\u5bb9\u751f\u6210\u548c\u610f\u601d\u4f20\u9012\n\n### \u5185\u5d4c\u8d44\u6e90\n\n- Tailwind CSS (https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/tailwindcss/2.2.19/tailwind.min.css)\n- Font Awesome (https://lf6-cdn-tos.bytecdntp.com/cdn/expire-100-M/font-awesome/6.0.0/css/all.min.css)\n- \u4e2d\u6587\u6392\u7248\u4f7f\u7528 SF Pro Display \u548c Segoe UI\n- \u6839\u636e\u6240\u9009\u98ce\u683c\u6dfb\u52a0\u9002\u5408\u7684Google Fonts\u5b57\u4f53\n\n### \u8bbe\u8ba1\u98ce\u683c\u53c2\u8003\n\n\u6211\u63d0\u4f9b\u4e86\u591a\u79cd\u8bbe\u8ba1\u98ce\u683c\u9009\u9879\uff0c\u8bf7\u6839\u636e\u6211\u9009\u62e9\u7684\u98ce\u683c\u7f16\u53f7\u6216\u540d\u79f0\u6765\u8bbe\u8ba1\uff1a\n\n1. \u6781\u7b80\u4e3b\u4e49\u98ce\u683c (Minimalist)\uff1a\u7b80\u7ea6\u3001\u7559\u767d\u3001\u7cbe\u786e\u6392\u7248\u3001\u65e0\u886c\u7ebf\u5b57\u4f53\u3001\u514b\u5236\u88c5\u9970\u3001\u767d\u8272\u80cc\u666f\uff0c\u6a58\u8272\u5b57\u4f53\n2. \u5927\u80c6\u73b0\u4ee3\u98ce\u683c (Bold Modern)\uff1a\u9c9c\u8273\u5bf9\u6bd4\u8272\u3001\u4e0d\u5bf9\u79f0\u52a8\u6001\u6392\u7248\u3001\u6781\u5927\u6807\u9898\u3001\u51e0\u4f55\u5143\u7d20\n3. \u4f18\u96c5\u590d\u53e4\u98ce\u683c (Elegant Vintage)\uff1a\u7c73\u8272\u80cc\u666f\u3001\u886c\u7ebf\u5b57\u4f53\u3001\u5bf9\u79f0\u6392\u7248\u3001\u7cbe\u81f4\u88c5\u9970\u5143\u7d20\n4. \u672a\u6765\u79d1\u6280\u98ce\u683c (Futuristic Tech)\uff1a\u6df1\u8272\u80cc\u666f\u3001\u9713\u8679\u8272\u3001\u79d1\u6280\u754c\u9762\u3001\u6570\u636e\u53ef\u89c6\u5316\u5143\u7d20\n5. \u65af\u582a\u7684\u7eb3\u7ef4\u4e9a\u98ce\u683c (Scandinavian)\uff1a\u7eaf\u767d\u80cc\u666f\u3001\u5317\u6b27\u8272\u8c03\u3001\u514b\u5236\u6392\u7248\u3001\u7b80\u5355\u51e0\u4f55\u56fe\u6848\n6. \u827a\u672f\u88c5\u9970\u98ce\u683c (Art Deco)\uff1a\u9ed1\u91d1\u914d\u8272\u3001\u5bf9\u79f0\u6392\u7248\u3001\u88c5\u9970\u6027\u5b57\u4f53\u3001\u51e0\u4f55\u56fe\u6848\u3001\u5962\u534e\u611f\n7. \u65e5\u5f0f\u6781\u7b80\u98ce\u683c (Japanese Minimalism)\uff1a\u6781\u5ea6\u7559\u767d\u3001\u514b\u5236\u8272\u5f69\u3001\u975e\u5bf9\u79f0\u6392\u7248\u3001\u7985\u610f\u7f8e\u5b66\n8. \u540e\u73b0\u4ee3\u89e3\u6784\u98ce\u683c (Postmodern Deconstruction)\uff1a\u6253\u7834\u89c4\u5219\u3001\u6df7\u5408\u5b57\u4f53\u3001\u4e0d\u548c\u8c10\u8272\u5f69\n9. \u670b\u514b\u98ce\u683c (Punk)\uff1aDIY\u6548\u679c\u3001\u9ad8\u5bf9\u6bd4\u8272\u5f69\u3001\u4e0d\u89c4\u5219\u6392\u7248\u3001\u624b\u5199\u5b57\u4f53\u3001\u7c97\u7cd9\u8d28\u611f\n10. \u82f1\u4f26\u6447\u6eda\u98ce\u683c (British Rock)\uff1a\u82f1\u56fd\u5143\u7d20\u3001\u7ea2\u767d\u84dd\u8272\u7cfb\u3001\u6df7\u5408\u7ecf\u5178\u4e0e\u73b0\u4ee3\u5b57\u4f53\n11. \u9ed1\u91d1\u5c5e\u98ce\u683c (Black Metal)\uff1a\u7eaf\u9ed1\u80cc\u666f\u3001\u54e5\u7279\u5b57\u4f53\u3001\u795e\u79d8\u7b26\u53f7\u3001\u9ad8\u5bf9\u6bd4\u5355\u8272\u56fe\u50cf\n12. \u5b5f\u83f2\u65af\u98ce\u683c (Memphis Design)\uff1a\u9c9c\u8273\u4e0d\u534f\u8c03\u8272\u5f69\u3001\u51e0\u4f55\u5f62\u72b6\u3001\u6d3b\u6cfc\u6392\u7248\u300180\u5e74\u4ee3\u611f\n13. \u8d5b\u535a\u670b\u514b\u98ce\u683c (Cyberpunk)\uff1a\u6df1\u8272\u80cc\u666f\u3001\u9713\u8679\u8272\u5f69\u3001\u6545\u969c\u6548\u679c\u3001\u79d1\u6280\u754c\u9762\u5143\u7d20\n14. \u6ce2\u666e\u827a\u672f\u98ce\u683c (Pop Art)\uff1a\u4eae\u4e3d\u539f\u8272\u3001\u6f2b\u753b\u98ce\u683c\u3001\u534a\u8c03\u7f51\u70b9\u6548\u679c\u3001\u6d41\u884c\u6587\u5316\u5143\u7d20\n15. \u745e\u58eb\u56fd\u9645\u4e3b\u4e49\u98ce\u683c\u7684\u89e3\u6784\u7248 (Deconstructed Swiss Style)\uff1a\u57fa\u4e8e\u7f51\u683c\u7684\u7834\u574f\u91cd\u7ec4\n16. \u84b8\u6c7d\u6ce2\u7f8e\u5b66 (Vaporwave Aesthetics)\uff1a\u7c89\u7d2b\u9752\u84dd\u6e10\u53d8\u300180-90\u5e74\u4ee3\u5143\u7d20\u3001\u590d\u53e4\u7535\u8111\u754c\u9762\n17. \u65b0\u8868\u73b0\u4e3b\u4e49\u98ce\u683c (Neo-Expressionism)\uff1a\u5f3a\u70c8\u8272\u5f69\u3001\u4e0d\u89c4\u5219\u6392\u7248\u3001\u7c97\u72b7\u7ebf\u6761\u3001\u624b\u5de5\u611f\n18. \u6781\u7b80\u4e3b\u4e49\u7684\u6781\u7aef\u7248\u672c (Extreme Minimalism)\uff1a\u6781\u5ea6\u7559\u767d\u3001\u9ed1\u767d\u7070\u3001\u7cbe\u786e\u6392\u7248\u3001\u96f6\u88c5\u9970\n19. \u65b0\u672a\u6765\u4e3b\u4e49 (Neo-Futurism)\uff1a\u6d41\u7ebf\u578b\u66f2\u7ebf\u3001\u91d1\u5c5e\u8272\u8c03\u3001\u9ad8\u79d1\u6280\u6750\u8d28\u3001\u52a8\u6001\u6392\u7248\n20. \u8d85\u73b0\u5b9e\u4e3b\u4e49\u6570\u5b57\u62fc\u8d34 (Surrealist Digital Collage)\uff1a\u610f\u5916\u5143\u7d20\u7ec4\u5408\u3001\u6bd4\u4f8b\u5931\u8c03\u3001\u68a6\u5e7b\u8272\u5f69\n21. \u65b0\u5df4\u6d1b\u514b\u6570\u5b57\u98ce\u683c (Neo-Baroque Digital)\uff1a\u534e\u4e3d\u88c5\u9970\u3001\u91d1\u8272\u6df1\u8272\u7cfb\u3001\u620f\u5267\u6027\u5149\u5f71\u6548\u679c\n22. \u6db2\u6001\u6570\u5b57\u5f62\u6001\u4e3b\u4e49 (Liquid Digital Morphism)\uff1a\u6d41\u4f53\u6e10\u53d8\u3001\u6db2\u6001\u6548\u679c\u3001\u68a6\u5e7b\u8272\u5f69\n23. \u8d85\u611f\u5b98\u6781\u7b80\u4e3b\u4e49 (Hypersensory Minimalism)\uff1a\u5fae\u5999\u7eb9\u7406\u3001\u7cbe\u786e\u6392\u7248\u3001\u7ec6\u5fae\u8272\u5f69\u53d8\u5316\n24. \u65b0\u8868\u73b0\u4e3b\u4e49\u6570\u636e\u53ef\u89c6\u5316 (Neo-Expressionist Data Visualization)\uff1a\u6570\u636e\u9a71\u52a8\u7684\u62bd\u8c61\u827a\u672f\n25. \u7ef4\u591a\u5229\u4e9a\u98ce\u683c (Victorian Style)\uff1a\u534e\u4e3d\u5370\u5237\u7f8e\u5b66\u3001\u7e41\u590d\u88c5\u9970\u8fb9\u6846\u3001\u4f20\u7edf\u6392\u7248\n26. \u5305\u8c6a\u65af\u98ce\u683c (Bauhaus)\uff1a\u57fa\u672c\u51e0\u4f55\u5f62\u72b6\u3001\u539f\u8272\u3001\u65e0\u886c\u7ebf\u5b57\u4f53\u3001\u529f\u80fd\u4e3b\u4e49\u7f8e\u5b66\n27. \u6784\u6210\u4e3b\u4e49\u98ce\u683c (Constructivism)\uff1a\u51e0\u4f55\u5f62\u72b6\u3001\u7ea2\u9ed1\u914d\u8272\u3001\u52a8\u6001\u6392\u7248\u3001\u9769\u547d\u7f8e\u5b66\n28. \u7b80\u7ea6\u529f\u80fd\u578b\u98ce\u683c (Minimal Functional)\uff1a\u6e05\u6670\u5361\u7247\u5f0f\u5e03\u5c40\u3001\u67d4\u548c\u8272\u5f69\u70b9\u7f00\u3001\u76f4\u89c2\u56fe\u6807\u7cfb\u7edf\u3001\u7cbe\u7b80\u6587\u672c\u5c55\u793a\u3001\u5145\u8db3\u7559\u767d\u7a7a\u95f4\n29. \u5fb7\u56fd\u8868\u73b0\u4e3b\u4e49\u98ce\u683c (German Expressionism)\uff1a\u5f3a\u70c8\u660e\u6697\u5bf9\u6bd4\u3001\u626d\u66f2\u5f62\u6001\u3001\u60c5\u611f\u8868\u8fbe\n\n\u5982\u679c\u6211\u6ca1\u6709\u6307\u5b9a\u98ce\u683c\uff0c\u8bf7\u9ed8\u8ba4\u4f7f\u7528\u6781\u7b80\u4e3b\u4e49\u7684 Bento Grid \u98ce\u683c\u8bbe\u8ba1\n\n## \u91cd\u8981\u8bf4\u660e\n\n### HTML\u6a21\u677f\u5904\u7406\u8981\u6c42\n1. **\u4e25\u683c\u4f7f\u7528\u63d0\u4f9b\u7684HTML\u6a21\u677f**\n2. **\u4fdd\u6301\u6a21\u677f\u7ed3\u6784\u5b8c\u6574**\uff1a\u4e0d\u8981\u4fee\u6539HTML\u7684\u57fa\u7840\u7ed3\u6784\u3001CSS\u6837\u5f0f\u548cJavaScript\u529f\u80fd\n3. **\u4fdd\u6301\u54cd\u5e94\u5f0f\u8bbe\u8ba1**\uff1a\u786e\u4fdd\u751f\u6210\u7684HTML\u5728\u5404\u79cd\u8bbe\u5907\u4e0a\u90fd\u80fd\u6b63\u786e\u663e\u793a\n4. \u804a\u5929\u8bb0\u5f55\u57fa\u4e8ejson: {{ JSON.stringify($json) }}\n5. html\u6a21\u7248\u6587\u4ef6: {{ $('ConfigureChatParameters').first().json.web_style_template }}\n\n## \ud83d\udea8 FINAL REMINDER \ud83d\udea8\n\n**ONLY OUTPUT HTML CODE! NO EXPLANATIONS!**\nStart with: <!DOCTYPE html>\nEnd with: </html>\n\nDo NOT add any text before or after the HTML code!\nDo NOT keep any [xxx] placeholders in the output!"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        1880,
        80
      ],
      "id": "d3fa5a68-2275-48ab-88e4-a051bc108360",
      "name": "HTMLPageRender"
    },
    {
      "parameters": {
        "model": "google/gemini-2.5-flash-preview-05-20",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        1860,
        300
      ],
      "id": "10e8be12-0703-4650-b309-1c0155edb6e0",
      "name": "OpenRouter Chat Model",
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {
          "responseFormat": "json_object"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        1200,
        400
      ],
      "id": "b2b9ebcc-7c68-4a47-9797-6551c15fbac9",
      "name": "DeepSeek Chat Model",
      "credentials": {
        "deepSeekApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "content": "## \u667a\u80fd\u5fae\u4fe1\u7fa4\u804a\u65e5\u62a5\u751f\u6210\u5668 - \u6210\u672c\u4f18\u5316\u7248\n\n### \ud83d\udcd4 \u57fa\u672c\u4fe1\u606f\n\n*   **\u5de5\u4f5c\u6d41\u540d\u79f0**: WeChat-Daily-Digest-AI-Cost-Optimized\n*   **\u7248\u672c**: v1.0\n*   **\u521b\u5efa\u8005**: \u6797\u6708\u534a\u5b50\u804aAI\n*   **\u6838\u5fc3\u4eae\u70b9**: \u91c7\u7528\u9ad8\u6027\u4ef7\u6bd4\u5927\u6a21\u578b\uff0c\u5927\u5e45\u964d\u4f4e\u5355\u6b21\u8fd0\u884c\u6210\u672c\n*   **\u6210\u672c\u8282\u7701\u65b9\u6848\u63d0\u4f9b**: \u4e0d\u5012\u7fc1\u5148\u751f\n*   **\u5fae\u4fe1**: cloud-native-101\n*   **\u516c\u4f17\u53f7**: \u6797\u6708\u534a\u5b50\u7684AI\u7b14\u8bb0\n\n---\n\n### \ud83d\ude80 \u529f\u80fd\u6982\u8ff0\n\n\u6b64\u5de5\u4f5c\u6d41\u662f\u539f\u7248\u201c\u667a\u80fd\u5fae\u4fe1\u7fa4\u804a\u65e5\u62a5\u751f\u6210\u5668\u201d\u7684 \u6210\u672c\u4f18\u5316\u7248\u3002\n\n\u5728\u5b8c\u6574\u4fdd\u7559\u6838\u5fc3\u529f\u80fd\uff08\u81ea\u52a8\u83b7\u53d6\u5fae\u4fe1\u7fa4\u804a\u8bb0\u5f55\u3001\u901a\u8fc7AI\u667a\u80fd\u5206\u6790\u5e76\u751f\u6210\u7cbe\u7f8e\u7684HTML\u683c\u5f0f\u65e5\u62a5\uff09\u7684\u57fa\u7840\u4e0a\uff0c\u5bf9AI\u8c03\u7528\u6d41\u7a0b\u548c\u6a21\u578b\u9009\u62e9\u8fdb\u884c\u4e86\u6df1\u5ea6\u4f18\u5316\u3002\u65e8\u5728\u4ee5\u6781\u4f4e\u7684\u6210\u672c\u5b9e\u73b0\u9ad8\u8d28\u91cf\u7684\u7fa4\u804a\u5206\u6790\u62a5\u544a\uff0c\u7279\u522b\u9002\u5408\u9700\u8981\u9ad8\u9891\u8fd0\u884c\u6216\u5927\u89c4\u6a21\u90e8\u7f72\u7684\u573a\u666f\u3002\n\n---\n\n### \u2699\ufe0f \u914d\u7f6e\u8bf4\u660e\n1.  **\u914d\u7f6e\u7fa4\u804a\u53c2\u6570\u8282\u70b9**:\n    *   `\u66ff\u6362\u7fa4\u804a\u540d\u79f0`: \u8bbe\u7f6e\u60a8\u9700\u8981\u5206\u6790\u7684\u76ee\u6807\u7fa4\u804a\u3002\n    *   `\u786e\u8ba4\u62a5\u544a\u65e5\u671f`: \u9ed8\u8ba4\u81ea\u52a8\u8bbe\u7f6e\u4e3a\u524d\u4e00\u5929\u3002\n    *   `\u8c03\u6574\u6a21\u677f`: \u53ef\u6839\u636e\u9700\u6c42\u4fee\u6539\u751f\u6210\u65e5\u62a5\u7684\u63d0\u793a\u8bcd\u6a21\u677f\u3002\n2.  **\u914d\u7f6eAI\u6a21\u578b\u51ed\u636e**:\n    *   \u5de5\u4f5c\u6d41\u5df2\u9884\u8bbe\u4e3a\u4f7f\u7528\u6210\u672c\u6548\u76ca\u66f4\u9ad8\u7684\u5927\u6a21\u578b\u3002\n    *   \u8bf7\u786e\u4fdd\u5df2\u5728n8n\u4e2d\u914d\u7f6e\u4e86\u5bf9\u5e94\u6a21\u578b\u7684\u51ed\u636e\u3002\n",
        "height": 1080,
        "width": 1040
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1120,
        -540
      ],
      "id": "d4429c3b-c681-4468-8fc3-c4dcdc28e34d",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "modelName": "models/gemini-2.5-flash",
        "options": {
          "temperature": 0.1
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        2020,
        300
      ],
      "id": "18f2e2f0-a790-46cc-b2d2-3db9b7278954",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Run every day at 8am": {
      "main": [
        [
          {
            "node": "ConfigureChatParameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Yesterday's Chatlog": {
      "main": [
        [
          {
            "node": "Parse & Structure Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Structure Data": {
      "main": [
        [
          {
            "node": "Extract Links & Active Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean & Segment by Time": {
      "main": [
        [
          {
            "node": "Split in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split in Batches": {
      "main": [
        [
          {
            "node": "Extract Topics (Low Cost AI)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge & Deduplicate Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Extract Topics (Low Cost AI)",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Extract Topics (Low Cost AI)": {
      "main": [
        [
          {
            "node": "Split in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge & Deduplicate Topics": {
      "main": [
        [
          {
            "node": "AI Semantic Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser1": {
      "ai_outputParser": [
        [
          {
            "node": "AI Semantic Merge",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "AI Semantic Merge": {
      "main": [
        [
          {
            "node": "Assemble Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Final Report": {
      "main": [
        [
          {
            "node": "HTMLPageRender",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u5904\u7406AI\u8f93\u51fa": {
      "main": [
        [
          {
            "node": "SaveHTMLFile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Links & Active Users": {
      "main": [
        [
          {
            "node": "Prepare Raw Message Sample",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Raw Message Sample": {
      "main": [
        [
          {
            "node": "Clean & Segment by Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ConfigureChatParameters": {
      "main": [
        [
          {
            "node": "Fetch Yesterday's Chatlog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTMLPageRender": {
      "main": [
        [
          {
            "node": "\u5904\u7406AI\u8f93\u51fa",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        []
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Semantic Merge",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Extract Topics (Low Cost AI)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "HTMLPageRender",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "64a940ed-99cc-4065-a062-5287b3317c2b",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "UcJbkvRZRPclvh2W",
  "tags": []
}