🧪 快速测试
@@ -440,7 +440,7 @@ const generateMarkdown = async () => {
};
// 调用AI API生成Markdown
-const callAIMarkdownAPI = async (systemPrompt, userPrompt) => {
+const callAIMarkdownAPI = async (systemPrompt, userPrompt, retryCount = 0) => {
const defaultSystemPrompt = `你是一位Markdown格式转换专家。你的任务是将用户提供的文章内容精确转换为结构化的Markdown格式。请遵循以下步骤:
提取主标题: 识别文章最顶层的主标题(通常为文章题目或书名),并使用Markdown的 # 级别表示。
@@ -515,19 +515,52 @@ Level 4 标题用 #####
const data = await response.json();
console.log('📡 AI API响应:', data);
+ let markdownContent = '';
if (data.success && data.markdown) {
console.log('✅ 从success.markdown获取内容');
- return data.markdown;
+ markdownContent = data.markdown;
} else if (data.markdown) {
console.log('✅ 从markdown字段获取内容');
- return data.markdown;
+ markdownContent = data.markdown;
} else if (data.content) {
console.log('✅ 从content字段获取内容');
- return data.content;
+ markdownContent = data.content;
} else {
console.error('❌ AI API响应格式错误:', data);
return '生成失败,请重试';
}
+
+ // 检查内容是否完整
+ console.log('📝 获取到的Markdown内容长度:', markdownContent.length);
+ console.log('📝 内容预览:', markdownContent.substring(0, 200) + '...');
+ console.log('📝 内容结尾:', '...' + markdownContent.substring(markdownContent.length - 100));
+
+ // 检查是否以不完整的句子结尾
+ const isTruncated = markdownContent.trim().endsWith('-') ||
+ markdownContent.trim().endsWith('•') ||
+ markdownContent.trim().endsWith('*') ||
+ markdownContent.trim().endsWith('**') ||
+ markdownContent.trim().endsWith('###') ||
+ markdownContent.trim().endsWith('##') ||
+ markdownContent.trim().endsWith('#') ||
+ markdownContent.trim().endsWith('时') ||
+ markdownContent.trim().endsWith('的') ||
+ markdownContent.trim().endsWith('和') ||
+ markdownContent.trim().endsWith('或');
+
+ if (isTruncated && retryCount < 2) {
+ console.warn(`⚠️ 检测到内容可能被截断,进行第${retryCount + 1}次重试`);
+ showNotification(`检测到内容可能被截断,正在重试...(${retryCount + 1}/2)`, 'warning');
+
+ // 等待1秒后重试
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ return await callAIMarkdownAPI(systemPrompt, userPrompt, retryCount + 1);
+ } else if (isTruncated) {
+ console.warn('⚠️ 内容可能被截断,但已达到最大重试次数');
+ showNotification('内容可能被截断,请尝试上传较小的文档或稍后重试', 'warning');
+ }
+
+ return markdownContent;
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('AI API请求超时(2分钟),文档可能较复杂,请重试或尝试上传较小的文档');
@@ -606,7 +639,7 @@ const countNodes = (node) => {
return count;
};
-// Markdown转JSON的核心逻辑
+// Markdown转JSON的核心逻辑 - 智能层次化版本
const markdownToJSON = (markdown) => {
const lines = markdown.split('\n');
let root = null;
@@ -624,10 +657,9 @@ const markdownToJSON = (markdown) => {
if (currentContent.length > 0 && stack.length > 0) {
const content = currentContent.join('\n').trim();
if (content) {
- // 将内容直接添加到当前节点的标题中,并清理Markdown语法
- const lastNode = stack[stack.length - 1];
- const formattedContent = formatMarkdownToText(content);
- lastNode.topic = lastNode.topic + '\n\n' + formattedContent;
+ // 智能处理内容:检测是否需要创建子节点
+ const processedContent = processContentIntelligently(content, stack[stack.length - 1], nodeCounter);
+ nodeCounter = processedContent.nodeCounter;
}
currentContent = [];
}
@@ -647,17 +679,6 @@ const markdownToJSON = (markdown) => {
data: {}
};
- // 如果有累积的内容,将其添加到标题中
- if (currentContent.length > 0) {
- const content = currentContent.join('\n').trim();
- if (content) {
- // 格式化内容,将Markdown转换为结构化的文本
- const formattedContent = formatMarkdownToText(content);
- node.topic = cleanTitle + '\n\n' + formattedContent;
- }
- currentContent = [];
- }
-
// 如果是第一个节点(最高级别),设为根节点
if (level === 1 && !root) {
root = node;
@@ -678,48 +699,17 @@ const markdownToJSON = (markdown) => {
stack.push(node);
}
} else if (trimmed) {
- // 非标题行,累积内容
+ // 累积内容行
currentContent.push(trimmed);
}
-
- // 检测列表项
- const listMatch = trimmed.match(/^[-*+]\s+(.+)$/);
- if (listMatch) {
- const title = listMatch[1].trim();
- // 处理列表项中的Markdown语法,确保内容可读
- const cleanTitle = formatMarkdownToText(title);
- const node = {
- id: `node_${nodeCounter++}`,
- topic: cleanTitle,
- children: [],
- level: stack.length > 0 ? stack[stack.length - 1].level + 1 : 1,
- data: {}
- };
-
- if (stack.length > 0) {
- stack[stack.length - 1].children.push(node);
- } else if (!root) {
- // 如果没有根节点,创建一个
- root = {
- id: `node_${nodeCounter++}`,
- topic: '主题',
- children: [node],
- level: 0,
- data: {}
- };
- stack.push(root);
- }
- }
});
// 处理最后的内容
if (currentContent.length > 0 && stack.length > 0) {
const content = currentContent.join('\n').trim();
if (content) {
- // 将最后的内容添加到最后一个节点的标题中,并清理Markdown语法
- const lastNode = stack[stack.length - 1];
- const formattedContent = formatMarkdownToText(content);
- lastNode.topic = lastNode.topic + '\n\n' + formattedContent;
+ const processedContent = processContentIntelligently(content, stack[stack.length - 1], nodeCounter);
+ nodeCounter = processedContent.nodeCounter;
}
}
@@ -736,6 +726,80 @@ const markdownToJSON = (markdown) => {
return root;
};
+// 智能处理内容,检测是否需要创建子节点
+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 formattedContent = formatMarkdownToText(finalContent);
+ parentNode.topic = parentNode.topic + '\n\n' + formattedContent;
+ }
+ }
+
+ return { nodeCounter: currentNodeCounter };
+};
+
// 复制Markdown
const copyMarkdown = async () => {
if (!markdownContent.value) {
@@ -838,11 +902,13 @@ const previewMindmap = async () => {
processingMessage.value = '';
showNotification('思维导图已保存成功!', 'success');
- // 清空输入框和文件
- aiPrompt.value = '';
- markdownContent.value = '';
- convertedJSON.value = '';
+ // 注释掉自动清空功能,保留数据用于调试
+ // 用户可以通过"清空"按钮手动清空数据
+ // aiPrompt.value = '';
+ // markdownContent.value = '';
+ // convertedJSON.value = '';
+ // 只清空文件上传,保留生成的内容
uploadedFile.value = null;
if (fileInput.value) {
fileInput.value.value = '';
diff --git a/frontend/src/components/MindMap.vue b/frontend/src/components/MindMap.vue
index 67daffe..2d98549 100644
--- a/frontend/src/components/MindMap.vue
+++ b/frontend/src/components/MindMap.vue
@@ -70,8 +70,38 @@