feat: 实现对叶子节点的细分、优化ai追问
This commit is contained in:
parent
0a64d46ea3
commit
7b6601e010
Binary file not shown.
|
|
@ -77,6 +77,22 @@
|
||||||
</div>
|
</div>
|
||||||
</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结果 - 固定显示区域 -->
|
<!-- AI生成的Markdown结果 - 固定显示区域 -->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h4>📝 AI生成的Markdown结果</h4>
|
<h4>📝 AI生成的Markdown结果</h4>
|
||||||
|
|
@ -121,16 +137,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 快速测试 -->
|
<!-- 快速测试
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h4>🧪 快速测试</h4>
|
<h4>🧪 快速测试</h4>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button @click="loadTestData" class="btn-test">📊 加载测试数据</button>
|
<button @click="loadTestData" class="btn-test">📊 加载测试数据</button>
|
||||||
<button @click="clearAll" class="btn-clear">🗑️ 清空所有</button>
|
<button @click="clearAll" class="btn-clear">🗑️ 清空所有</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- 历史记录 -->
|
<!-- 历史记录
|
||||||
<div class="section" v-if="history.length > 0">
|
<div class="section" v-if="history.length > 0">
|
||||||
<h4>📚 历史记录</h4>
|
<h4>📚 历史记录</h4>
|
||||||
<div class="history-list">
|
<div class="history-list">
|
||||||
|
|
@ -144,7 +160,7 @@
|
||||||
<div class="history-time">{{ formatTime(item.timestamp) }}</div>
|
<div class="history-time">{{ formatTime(item.timestamp) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -788,12 +804,27 @@ const processContentIntelligently = (content, parentNode, nodeCounter) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有剩余内容,添加到父节点
|
// 如果有剩余内容,按换行切分成并列的子节点
|
||||||
if (remainingContent.length > 0) {
|
if (remainingContent.length > 0) {
|
||||||
const finalContent = remainingContent.join('\n').trim();
|
const finalContent = remainingContent.join('\n').trim();
|
||||||
if (finalContent) {
|
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回答节点
|
// 创建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) => {
|
const createAINode = async (parentNode, question, answer) => {
|
||||||
try {
|
try {
|
||||||
console.log('开始创建AI节点...', { parentNode, question, answer });
|
console.log('开始创建AI节点...', { parentNode, question, answer });
|
||||||
|
|
@ -1245,15 +1455,33 @@ const createAINode = async (parentNode, question, answer) => {
|
||||||
|
|
||||||
const formattedAnswer = formatAnswer(answer);
|
const formattedAnswer = formatAnswer(answer);
|
||||||
|
|
||||||
// 使用API直接创建节点到数据库
|
// 使用Markdown转JSON逻辑处理AI回复
|
||||||
const nodeData = {
|
const aiMarkdown = `# ${question}\n\n${formattedAnswer}`;
|
||||||
title: `问题:${question}\n\n回答:${formattedAnswer}`, // 将问题和回答都存储到title中
|
const aiJSON = markdownToJSON(aiMarkdown);
|
||||||
des: `AI追问产生的节点 - ${new Date().toLocaleString()}`, // 标注这是AI追问产生的节点
|
|
||||||
|
// 创建AI回复的父节点
|
||||||
|
const aiParentNode = {
|
||||||
|
title: question,
|
||||||
|
des: `AI追问产生的节点 - ${new Date().toLocaleString()}`,
|
||||||
parentId: parentNode.id,
|
parentId: parentNode.id,
|
||||||
isRoot: false
|
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);
|
console.log('当前思维导图ID:', currentMindmapId.value);
|
||||||
|
|
||||||
// 检查是否有思维导图ID
|
// 检查是否有思维导图ID
|
||||||
|
|
@ -1261,11 +1489,33 @@ const createAINode = async (parentNode, question, answer) => {
|
||||||
throw new Error('没有找到当前思维导图ID,无法创建节点');
|
throw new Error('没有找到当前思维导图ID,无法创建节点');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用API创建节点
|
// 先创建父节点
|
||||||
const response = await mindmapAPI.addNodes(currentMindmapId.value, [nodeData]);
|
const parentResponse = await mindmapAPI.addNodes(currentMindmapId.value, [aiParentNode]);
|
||||||
console.log('API创建节点响应:', response);
|
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节点创建成功,开始刷新思维导图...');
|
console.log('✅ AI节点创建成功,开始刷新思维导图...');
|
||||||
|
|
||||||
// 刷新思维导图以显示新节点
|
// 刷新思维导图以显示新节点
|
||||||
|
|
@ -1273,7 +1523,7 @@ const createAINode = async (parentNode, question, answer) => {
|
||||||
|
|
||||||
console.log('✅ AI节点创建并刷新完成');
|
console.log('✅ AI节点创建并刷新完成');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('API创建节点失败');
|
throw new Error('AI父节点创建失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue