feat: 实现对叶子节点的细分、优化ai追问
This commit is contained in:
parent
0a64d46ea3
commit
7b6601e010
Binary file not shown.
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue