diff --git a/backend/mindmap.db b/backend/mindmap.db index a432576..7f95f7a 100644 Binary files a/backend/mindmap.db and b/backend/mindmap.db differ diff --git a/frontend/src/components/AISidebar.vue b/frontend/src/components/AISidebar.vue index 5c2d10e..6c8413a 100644 --- a/frontend/src/components/AISidebar.vue +++ b/frontend/src/components/AISidebar.vue @@ -77,6 +77,22 @@ + +
+

📚 历史记录

+
+
+
{{ item.title }}
+
{{ formatTime(item.timestamp) }}
+
+
+
+

📝 AI生成的Markdown结果

@@ -121,16 +137,16 @@
- + - + @@ -788,12 +804,27 @@ const processContentIntelligently = (content, parentNode, nodeCounter) => { } } - // 如果有剩余内容,添加到父节点 + // 如果有剩余内容,按换行切分成并列的子节点 if (remainingContent.length > 0) { const finalContent = remainingContent.join('\n').trim(); if (finalContent) { - const formattedContent = formatMarkdownToText(finalContent); - parentNode.topic = parentNode.topic + '\n\n' + formattedContent; + // 按换行符切分内容 + const paragraphs = finalContent.split('\n\n').filter(p => p.trim()); + + paragraphs.forEach(paragraph => { + const cleanParagraph = formatMarkdownToText(paragraph.trim()); + if (cleanParagraph) { + const paragraphNode = { + id: `node_${currentNodeCounter++}`, + topic: cleanParagraph, + children: [], + level: (parentNode.level || 0) + 1, + data: {} + }; + + parentNode.children.push(paragraphNode); + } + }); } } diff --git a/frontend/src/components/MindMap.vue b/frontend/src/components/MindMap.vue index 2d98549..10488d4 100644 --- a/frontend/src/components/MindMap.vue +++ b/frontend/src/components/MindMap.vue @@ -1228,6 +1228,216 @@ const submitAIQuestion = async () => { }; // 创建AI回答节点 +// 格式化Markdown为结构化文本 +const formatMarkdownToText = (markdown) => { + return markdown + // 处理标题 + .replace(/^### (.*$)/gim, '📋 $1') // 三级标题 + .replace(/^## (.*$)/gim, '📌 $1') // 二级标题 + .replace(/^# (.*$)/gim, '🎯 $1') // 一级标题 + // 处理粗体 - 改进处理逻辑,确保冒号等标点符号正确处理 + .replace(/\*\*(.*?)\*\*/g, (match, content) => { + // 如果内容包含冒号,保持冒号,只处理粗体部分 + if (content.includes(':')) { + const parts = content.split(':'); + if (parts.length > 1) { + return `【${parts[0]}】: ${parts.slice(1).join(':')}`; + } + } + return `【${content}】`; + }) + // 处理斜体 + .replace(/\*(.*?)\*/g, '《$1》') + // 处理列表项 + .replace(/^- (.*$)/gim, ' • $1') + .replace(/^\d+\. (.*$)/gim, ' $&') + // 处理代码块 + .replace(/```(.*?)```/gims, '💻 $1') + // 处理行内代码 + .replace(/`(.*?)`/g, '「$1」') + // 处理链接 + .replace(/\[([^\]]+)\]\([^)]+\)/g, '🔗 $1') + // 处理换行 + .replace(/\n\n/g, '\n') + .replace(/\n/g, '\n '); +}; + +// 智能处理内容,检测是否需要创建子节点 +const processContentIntelligently = (content, parentNode, nodeCounter) => { + const lines = content.split('\n'); + let currentNodeCounter = nodeCounter; + let remainingContent = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + // 检测是否是【】标记的叶节点 + const leafMatch = trimmed.match(/^[-*+]\s*【(.+)】/); + if (leafMatch) { + // 创建叶节点 + const leafTitle = leafMatch[1].trim(); + const leafNode = { + id: `node_${currentNodeCounter++}`, + topic: leafTitle, + children: [], + level: (parentNode.level || 0) + 1, + data: {} + }; + + // 收集这个叶节点的所有子项内容 + let leafContent = []; + let j = i + 1; + while (j < lines.length) { + const nextLine = lines[j].trim(); + const nextLeafMatch = nextLine.match(/^[-*+]\s*【(.+)】/); + + if (nextLeafMatch) { + // 遇到下一个【】标记,停止收集 + break; + } + + // 收集子项内容 + const listMatch = nextLine.match(/^[-*+]\s+(.+)$/); + if (listMatch) { + const listItem = listMatch[1].trim(); + const cleanListItem = formatMarkdownToText(listItem); + leafContent.push('• ' + cleanListItem); + } + + j++; + } + + // 如果有子项内容,添加到叶节点 + if (leafContent.length > 0) { + const formattedContent = formatMarkdownToText(leafContent.join('\n')); + leafNode.topic = leafNode.topic + '\n\n' + formattedContent; + } + + parentNode.children.push(leafNode); + + // 跳过已处理的行 + i = j - 1; + } else if (trimmed) { + // 普通内容,累积到剩余内容中 + remainingContent.push(trimmed); + } + } + + // 如果有剩余内容,按换行切分成并列的子节点 + if (remainingContent.length > 0) { + const finalContent = remainingContent.join('\n').trim(); + if (finalContent) { + // 按换行符切分内容 + const paragraphs = finalContent.split('\n\n').filter(p => p.trim()); + + paragraphs.forEach(paragraph => { + const cleanParagraph = formatMarkdownToText(paragraph.trim()); + if (cleanParagraph) { + const paragraphNode = { + id: `node_${currentNodeCounter++}`, + topic: cleanParagraph, + children: [], + level: (parentNode.level || 0) + 1, + data: {} + }; + + parentNode.children.push(paragraphNode); + } + }); + } + } + + return { nodeCounter: currentNodeCounter }; +}; + +// Markdown转JSON的核心逻辑 - 智能层次化版本 +const markdownToJSON = (markdown) => { + const lines = markdown.split('\n'); + let root = null; + const stack = []; + let nodeCounter = 0; + let currentContent = []; + + lines.forEach((line, index) => { + const trimmed = line.trim(); + + // 检测标题级别 + const match = trimmed.match(/^(#{1,6})\s+(.+)$/); + if (match) { + // 如果有累积的内容,先保存到当前节点 + if (currentContent.length > 0 && stack.length > 0) { + const content = currentContent.join('\n').trim(); + if (content) { + // 智能处理内容:检测是否需要创建子节点 + const processedContent = processContentIntelligently(content, stack[stack.length - 1], nodeCounter); + nodeCounter = processedContent.nodeCounter; + } + currentContent = []; + } + + const level = match[1].length; + const title = match[2].trim(); + + // 清理标题中的Markdown语法 + const cleanTitle = formatMarkdownToText(title); + + // 创建节点 + const node = { + id: `node_${nodeCounter++}`, + topic: cleanTitle, + children: [], + level: level, + data: {} + }; + + // 如果是第一个节点(最高级别),设为根节点 + if (level === 1 && !root) { + root = node; + stack.length = 0; // 清空栈 + stack.push(root); + } else { + // 找到合适的父节点 + while (stack.length > 1 && stack[stack.length - 1].level >= level) { + stack.pop(); + } + + // 添加到父节点 + if (stack.length > 0) { + stack[stack.length - 1].children.push(node); + } + + // 更新栈 + stack.push(node); + } + } else if (trimmed) { + // 累积内容行 + currentContent.push(trimmed); + } + }); + + // 处理最后的内容 + if (currentContent.length > 0 && stack.length > 0) { + const content = currentContent.join('\n').trim(); + if (content) { + const processedContent = processContentIntelligently(content, stack[stack.length - 1], nodeCounter); + nodeCounter = processedContent.nodeCounter; + } + } + + // 如果没有找到任何内容,返回默认根节点 + if (!root) { + root = { + id: 'root', + topic: '根节点', + children: [], + data: {} + }; + } + + return root; +}; + const createAINode = async (parentNode, question, answer) => { try { console.log('开始创建AI节点...', { parentNode, question, answer }); @@ -1245,15 +1455,33 @@ const createAINode = async (parentNode, question, answer) => { const formattedAnswer = formatAnswer(answer); - // 使用API直接创建节点到数据库 - const nodeData = { - title: `问题:${question}\n\n回答:${formattedAnswer}`, // 将问题和回答都存储到title中 - des: `AI追问产生的节点 - ${new Date().toLocaleString()}`, // 标注这是AI追问产生的节点 + // 使用Markdown转JSON逻辑处理AI回复 + const aiMarkdown = `# ${question}\n\n${formattedAnswer}`; + const aiJSON = markdownToJSON(aiMarkdown); + + // 创建AI回复的父节点 + const aiParentNode = { + title: question, + des: `AI追问产生的节点 - ${new Date().toLocaleString()}`, parentId: parentNode.id, isRoot: false }; + + // 准备子节点数据 + const childNodes = []; + if (aiJSON.children && aiJSON.children.length > 0) { + aiJSON.children.forEach(child => { + childNodes.push({ + title: child.topic, + des: '', + parentId: null, // 将在创建父节点后设置 + isRoot: false + }); + }); + } - console.log('准备创建节点数据:', nodeData); + console.log('准备创建AI父节点:', aiParentNode); + console.log('准备创建AI子节点:', childNodes); console.log('当前思维导图ID:', currentMindmapId.value); // 检查是否有思维导图ID @@ -1261,11 +1489,33 @@ const createAINode = async (parentNode, question, answer) => { throw new Error('没有找到当前思维导图ID,无法创建节点'); } - // 调用API创建节点 - const response = await mindmapAPI.addNodes(currentMindmapId.value, [nodeData]); - console.log('API创建节点响应:', response); + // 先创建父节点 + const parentResponse = await mindmapAPI.addNodes(currentMindmapId.value, [aiParentNode]); + console.log('AI父节点创建响应:', parentResponse); - if (response.data && response.data.success) { + if (!parentResponse.data || !parentResponse.data.success) { + throw new Error('AI父节点创建失败'); + } + + // 获取创建的父节点ID + const createdParentId = parentResponse.data.data?.nodes?.[0]?.id; + if (!createdParentId) { + throw new Error('无法获取创建的父节点ID'); + } + + // 设置子节点的父节点ID + childNodes.forEach(child => { + child.parentId = createdParentId; + }); + + // 创建子节点 + let childResponse = null; + if (childNodes.length > 0) { + childResponse = await mindmapAPI.addNodes(currentMindmapId.value, childNodes); + console.log('AI子节点创建响应:', childResponse); + } + + if (parentResponse.data && parentResponse.data.success) { console.log('✅ AI节点创建成功,开始刷新思维导图...'); // 刷新思维导图以显示新节点 @@ -1273,7 +1523,7 @@ const createAINode = async (parentNode, question, answer) => { console.log('✅ AI节点创建并刷新完成'); } else { - throw new Error('API创建节点失败'); + throw new Error('AI父节点创建失败'); } } catch (error) {