feat: 实现对叶子节点的细分、优化ai追问

This commit is contained in:
lixinran 2025-09-08 16:51:12 +08:00
parent 0a64d46ea3
commit 7b6601e010
3 changed files with 298 additions and 17 deletions

Binary file not shown.

View File

@ -77,6 +77,22 @@
</div>
</div>
<!-- 历史记录 -->
<div class="section" v-if="history.length > 0">
<h4>📚 历史记录</h4>
<div class="history-list">
<div
v-for="(item, index) in history"
:key="index"
class="history-item"
@click="loadHistoryItem(item)"
>
<div class="history-title">{{ item.title }}</div>
<div class="history-time">{{ formatTime(item.timestamp) }}</div>
</div>
</div>
</div>
<!-- AI生成的Markdown结果 - 固定显示区域 -->
<div class="section">
<h4>📝 AI生成的Markdown结果</h4>
@ -121,16 +137,16 @@
</div>
</div>
<!-- 快速测试 -->
<!-- 快速测试
<div class="section">
<h4>🧪 快速测试</h4>
<div class="button-group">
<button @click="loadTestData" class="btn-test">📊 加载测试数据</button>
<button @click="clearAll" class="btn-clear">🗑️ 清空所有</button>
</div>
</div>
</div> -->
<!-- 历史记录 -->
<!-- 历史记录
<div class="section" v-if="history.length > 0">
<h4>📚 历史记录</h4>
<div class="history-list">
@ -144,7 +160,7 @@
<div class="history-time">{{ formatTime(item.timestamp) }}</div>
</div>
</div>
</div>
</div> -->
</div>
</div>
</div>
@ -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);
}
});
}
}

View File

@ -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
};
console.log('准备创建节点数据:', nodeData);
// 准备子节点数据
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('准备创建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) {