feat(ai-analyze): 新增 Markdown 文件 AI 分析功能

- 添加 Markdown 文件上传和解析接口
- 实现流式分析和大纲提取功能
- 支持多种分析类型:摘要、大纲、关键点等
- 新增 markdown_ai_service 服务类
- 扩展 LLMService 支持流式调用
- 更新前端 API 接口定义和实现
This commit is contained in:
2026-04-02 11:53:12 +08:00
parent ddf30078f0
commit d189ea9620
6 changed files with 1286 additions and 118 deletions

View File

@@ -166,6 +166,43 @@ export interface AIAnalysisResult {
error?: string;
}
// ==================== Markdown AI 分析类型 ====================
export interface AIMarkdownAnalyzeResult {
success: boolean;
filename?: string;
analysis_type?: string;
section?: string;
word_count?: number;
structure?: {
title_count?: number;
code_block_count?: number;
table_count?: number;
section_count?: number;
};
sections?: MarkdownSection[];
analysis?: string;
error?: string;
}
export interface MarkdownSection {
number: string;
title: string;
level: number;
content_preview?: string;
line_start: number;
line_end?: number;
subsections?: MarkdownSection[];
}
export interface MarkdownOutlineResult {
success: boolean;
outline?: MarkdownSection[];
error?: string;
}
export type MarkdownAnalysisType = 'summary' | 'outline' | 'key_points' | 'questions' | 'tags' | 'qa' | 'statistics' | 'section';
export interface AIExcelAnalyzeResult {
success: boolean;
excel?: {
@@ -842,6 +879,159 @@ export const aiApi = {
}
},
/**
* 上传并使用 AI 分析 Markdown 文件
*/
async analyzeMarkdown(
file: File,
options: {
analysisType?: MarkdownAnalysisType;
userPrompt?: string;
sectionNumber?: string;
} = {}
): Promise<AIMarkdownAnalyzeResult> {
const formData = new FormData();
formData.append('file', file);
const params = new URLSearchParams();
if (options.analysisType) {
params.append('analysis_type', options.analysisType);
}
if (options.userPrompt) {
params.append('user_prompt', options.userPrompt);
}
if (options.sectionNumber) {
params.append('section_number', options.sectionNumber);
}
const url = `${BACKEND_BASE_URL}/ai/analyze/md?${params.toString()}`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Markdown AI 分析失败');
}
return await response.json();
} catch (error) {
console.error('Markdown AI 分析失败:', error);
throw error;
}
},
/**
* 流式分析 Markdown 文件 (SSE)
*/
async analyzeMarkdownStream(
file: File,
options: {
analysisType?: MarkdownAnalysisType;
userPrompt?: string;
sectionNumber?: string;
} = {},
onChunk?: (chunk: { type: string; delta?: string; error?: string }) => void
): Promise<string> {
const formData = new FormData();
formData.append('file', file);
const params = new URLSearchParams();
if (options.analysisType) {
params.append('analysis_type', options.analysisType);
}
if (options.userPrompt) {
params.append('user_prompt', options.userPrompt);
}
if (options.sectionNumber) {
params.append('section_number', options.sectionNumber);
}
const url = `${BACKEND_BASE_URL}/ai/analyze/md/stream?${params.toString()}`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Markdown AI 流式分析失败');
}
const reader = response.body?.getReader();
if (!reader) throw new Error('无法读取响应流');
const decoder = new TextDecoder();
let fullResponse = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const parsed = JSON.parse(data);
if (parsed.type === 'content' && parsed.delta) {
fullResponse += parsed.delta;
onChunk?.({ type: 'content', delta: parsed.delta });
} else if (parsed.type === 'done') {
fullResponse = parsed.full_response || fullResponse;
} else if (parsed.error) {
onChunk?.({ type: 'error', error: parsed.error });
}
} catch {
// Ignore parse errors for incomplete JSON
}
}
}
}
return fullResponse;
} catch (error) {
console.error('Markdown AI 流式分析失败:', error);
throw error;
}
},
/**
* 获取 Markdown 文档大纲(分章节信息)
*/
async getMarkdownOutline(file: File): Promise<MarkdownOutlineResult> {
const formData = new FormData();
formData.append('file', file);
const url = `${BACKEND_BASE_URL}/ai/analyze/md/outline`;
try {
const response = await fetch(url, {
method: 'GET',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '获取 Markdown 大纲失败');
}
return await response.json();
} catch (error) {
console.error('获取 Markdown 大纲失败:', error);
throw error;
}
},
/**
* 生成统计信息和图表
*/