Files
FilesReadSystem/frontend/src/db/backend-api.ts
dj e5d4724e82 【智能助手增强】
- 新增对话历史管理:MongoDB新增conversations集合,存储用户与AI的对话上下文,支持多轮对话意图延续
- 新增对话历史API(conversation.py):GET/DELETE conversation历史、列出所有会话
- 意图解析增强:支持基于对话历史的意图识别,上下文理解更准确
- 字段提取优化:支持"提取文档中的医院数量"等自然语言模式,智能去除"文档中的"前缀
- 文档对比优化:从指令中提取文件名并精确匹配source_docs,支持"对比A和B两个文档"
- 文档摘要优化:使用LLM生成真实AI摘要而非返回原始文档预览

【Word模板填表核心功能】
- Word模板字段生成:空白Word上传后,自动从源文档(Excel/Word/TXT/MD)内容AI生成字段名
- Word模板填表(_fill_docx):将提取数据写入Word模板表格,支持精确匹配、模糊匹配、追加新行
- 数据润色(_polish_word_filled_data):LLM对多行Excel数据进行统计归纳(合计/平均/极值),转化为专业自然语言描述
- 段落格式输出:使用📌字段名+值段落+分隔线(灰色横线)格式,提升可读性
- 导出链打通:fill_template返回filled_file_path,export直接返回已填好的Word文件

【其他修复】
- 修复Word导出Windows文件锁问题:NamedTemporaryFile改为mkstemp+close
- 修复Word方框非法字符:扩展clean_text移除\uFFFD、□等Unicode替代符和零宽字符
- 修复文档对比"需要至少2个文档":从指令提取具体文件名优先匹配而非取前2个
- 修复导出format硬编码:自动识别docx/xlsx格式
- Docx解析器增加备用解析方法和更完整的段落/表格/标题提取
- RAG服务新增MySQL数据源支持
2026-04-15 23:32:55 +08:00

1692 lines
41 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 后端 FastAPI 调用封装
* 基于大语言模型的文档理解与多源数据融合系统
*/
const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:8000/api/v1';
// ==================== 类型定义 ====================
// 文档类型
export type DocumentType = 'docx' | 'xlsx' | 'md' | 'txt';
// 任务状态
export type TaskStatus = 'pending' | 'processing' | 'success' | 'failure';
// 解析选项
export interface DocumentUploadOptions {
docType?: DocumentType;
parseAllSheets?: boolean;
sheetName?: string;
headerRow?: number;
}
// 任务状态响应
export interface TaskStatusResponse {
task_id: string;
status: TaskStatus;
progress: number;
message?: string;
result?: any;
error?: string;
}
// 文档元数据
export interface DocumentMetadata {
doc_id?: string;
filename?: string;
original_filename?: string;
extension?: string;
file_size?: number;
doc_type?: DocumentType;
sheet_count?: number;
sheet_names?: string[];
row_count?: number;
column_count?: number;
columns?: string[];
saved_path?: string;
created_at?: string;
}
// 解析结果
export interface DocumentParseResult {
success: boolean;
data?: {
columns?: string[];
rows?: Record<string, any>[];
row_count?: number;
column_count?: number;
sheets?: Record<string, any>;
content?: string; // 文本内容 (非结构化文档)
};
metadata?: DocumentMetadata;
error?: string;
}
// 上传响应
export interface UploadResponse {
task_id: string;
file_count: number;
message: string;
status_url: string;
}
// 文档库项
export interface DocumentItem {
doc_id: string;
filename: string;
original_filename: string;
doc_type: DocumentType;
file_size: number;
created_at: string;
metadata?: {
row_count?: number;
column_count?: number;
columns?: string[];
};
}
// 表格模板字段
export interface TemplateField {
cell: string;
name: string;
field_type: string;
required: boolean;
hint?: string;
}
// 表格填写结果
export interface FillResult {
success: boolean;
filled_data: Record<string, any>;
fill_details: Array<{
field: string;
value: any;
source: string;
confidence?: number;
}>;
source_doc_count?: number;
error?: string;
}
// ==================== Excel 相关类型 (保留) ====================
export interface ExcelUploadOptions {
parseAllSheets?: boolean;
sheetName?: string;
headerRow?: number;
}
export interface ExcelParseResult {
success: boolean;
data?: {
columns?: string[];
rows?: Record<string, any>[];
row_count?: number;
column_count?: number;
sheets?: Record<string, any>;
};
error?: string;
metadata?: {
filename?: string;
extension?: string;
sheet_count?: number;
sheet_names?: string[];
row_count?: number;
column_count?: number;
columns?: string[];
file_size?: number;
saved_path?: string;
original_filename?: string;
};
}
export interface ExcelExportOptions {
columns?: string[];
sheetName?: string;
}
// ==================== AI 分析相关类型 ====================
export interface AIAnalyzeOptions {
userPrompt?: string;
analysisType?: 'general' | 'summary' | 'statistics' | 'insights';
parseAllSheets?: boolean;
}
export interface ExcelData {
columns?: string[];
rows?: Record<string, any>[];
row_count?: number;
column_count?: number;
}
export interface AIAnalysisResult {
success: boolean;
analysis?: string;
model?: string;
analysisType?: string;
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;
chart_data?: {
tables?: Array<{
description?: string;
columns?: string[];
rows?: string[][];
visualization?: {
statistics?: any;
charts?: any;
distributions?: any;
};
}>;
key_statistics?: Array<{
name?: string;
value?: string;
trend?: string;
description?: string;
}>;
chart_suggestions?: Array<{
chart_type?: string;
title?: string;
data_source?: 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' | 'charts';
export interface AIExcelAnalyzeResult {
success: boolean;
excel?: {
data?: ExcelData;
sheets?: Record<string, ExcelData>;
metadata?: any;
saved_path?: string;
};
analysis?: {
analysis?: string;
model?: string;
analysisType?: string;
is_template?: boolean;
sheets?: Record<string, AIAnalysisResult>;
total_sheets?: number;
successful?: number;
errors?: Record<string, string>;
};
error?: string;
}
// ==================== API 封装 ====================
export const backendApi = {
// ==================== 健康检查 ====================
/**
* 健康检查
*/
async healthCheck(): Promise<{
status: string;
service: string;
databases?: { mysql: string; mongodb: string; redis: string };
}> {
try {
const response = await fetch(`${BACKEND_BASE_URL.replace('/api/v1', '')}/health`);
if (!response.ok) throw new Error('健康检查失败');
return await response.json();
} catch (error) {
console.error('健康检查失败:', error);
throw error;
}
},
// ==================== 文档上传与解析 ====================
/**
* 上传单个文档 (支持 docx/xlsx/md/txt)
* 文件会存入 MySQL (结构化) 和 MongoDB (非结构化),并建立 RAG 索引
*/
async uploadDocument(
file: File,
options: DocumentUploadOptions = {}
): Promise<UploadResponse> {
const formData = new FormData();
formData.append('file', file);
const params = new URLSearchParams();
if (options.docType) {
params.append('doc_type', options.docType);
}
if (options.parseAllSheets === true) {
params.append('parse_all_sheets', 'true');
}
if (options.sheetName) {
params.append('sheet_name', options.sheetName);
}
if (options.headerRow !== undefined) {
params.append('header_row', String(options.headerRow));
}
const url = `${BACKEND_BASE_URL}/upload/document?${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 || '上传失败');
}
return await response.json();
} catch (error) {
console.error('上传文档失败:', error);
throw error;
}
},
/**
* 批量上传文档
*/
async uploadDocuments(
files: File[],
options: DocumentUploadOptions = {}
): Promise<UploadResponse> {
const formData = new FormData();
files.forEach(file => formData.append('files', file));
const params = new URLSearchParams();
if (options.docType) {
params.append('doc_type', options.docType);
}
const url = `${BACKEND_BASE_URL}/upload/documents?${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 || '批量上传失败');
}
return await response.json();
} catch (error) {
console.error('批量上传文档失败:', error);
throw error;
}
},
/**
* 解析已上传的文档
*/
async parseDocument(filePath: string): Promise<DocumentParseResult> {
const url = `${BACKEND_BASE_URL}/upload/document/parse?file_path=${encodeURIComponent(filePath)}`;
try {
const response = await fetch(url, {
method: 'POST',
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '解析失败');
}
return await response.json();
} catch (error) {
console.error('解析文档失败:', error);
throw error;
}
},
// ==================== 任务状态查询 ====================
/**
* 查询任务状态
*/
async getTaskStatus(taskId: string): Promise<TaskStatusResponse> {
const url = `${BACKEND_BASE_URL}/tasks/${taskId}`;
try {
const response = await fetch(url);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '获取任务状态失败');
}
return await response.json();
} catch (error) {
console.error('获取任务状态失败:', error);
throw error;
}
},
/**
* 获取任务历史列表
*/
async getTasks(
limit: number = 50,
skip: number = 0
): Promise<{ success: boolean; tasks: any[]; count: number }> {
const url = `${BACKEND_BASE_URL}/tasks?limit=${limit}&skip=${skip}`;
try {
const response = await fetch(url);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '获取任务列表失败');
}
return await response.json();
} catch (error) {
console.error('获取任务列表失败:', error);
throw error;
}
},
/**
* 删除任务
*/
async deleteTask(taskId: string): Promise<{ success: boolean; deleted: boolean }> {
const url = `${BACKEND_BASE_URL}/tasks/${taskId}`;
try {
const response = await fetch(url, {
method: 'DELETE'
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '删除任务失败');
}
return await response.json();
} catch (error) {
console.error('删除任务失败:', error);
throw error;
}
},
/**
* 轮询任务状态直到完成
*/
async pollTaskStatus(
taskId: string,
onProgress?: (status: TaskStatusResponse) => void,
interval: number = 2000,
maxAttempts: number = 90 // 最多90秒 (比赛要求)
): Promise<TaskStatusResponse> {
return new Promise((resolve, reject) => {
let attempts = 0;
const poll = async () => {
try {
const status = await backendApi.getTaskStatus(taskId);
onProgress?.(status);
if (status.status === 'success') {
resolve(status);
return;
}
if (status.status === 'failure') {
reject(new Error(status.error || '任务失败'));
return;
}
attempts++;
if (attempts >= maxAttempts) {
reject(new Error('任务超时'));
return;
}
setTimeout(poll, interval);
} catch (error) {
reject(error);
}
};
poll();
});
},
// ==================== 文档库管理 ====================
/**
* 获取文档列表
*/
async getDocuments(
docType?: DocumentType,
limit: number = 50
): Promise<{ success: boolean; documents: DocumentItem[] }> {
const params = new URLSearchParams();
if (docType) params.append('doc_type', docType);
params.append('limit', String(limit));
const url = `${BACKEND_BASE_URL}/documents?${params.toString()}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取文档列表失败');
return await response.json();
} catch (error) {
console.error('获取文档列表失败:', error);
throw error;
}
},
/**
* 获取单个文档详情
*/
async getDocument(docId: string): Promise<{
success: boolean;
document?: DocumentItem & { content?: string; structured_data?: any };
error?: string;
}> {
const url = `${BACKEND_BASE_URL}/documents/${docId}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取文档详情失败');
return await response.json();
} catch (error) {
console.error('获取文档详情失败:', error);
throw error;
}
},
/**
* 删除文档
*/
async deleteDocument(docId: string): Promise<{ success: boolean; message: string }> {
const url = `${BACKEND_BASE_URL}/documents/${docId}`;
try {
const response = await fetch(url, {
method: 'DELETE',
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '删除失败');
}
return await response.json();
} catch (error) {
console.error('删除文档失败:', error);
throw error;
}
},
// ==================== RAG 检索 ====================
/**
* 检索相关文档/字段
*/
async searchRAG(
query: string,
topK: number = 5
): Promise<{
success: boolean;
results: Array<{
content: string;
metadata: Record<string, any>;
score: number;
doc_id: string;
}>;
}> {
const url = `${BACKEND_BASE_URL}/rag/search`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, top_k: topK }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '检索失败');
}
return await response.json();
} catch (error) {
console.error('RAG 检索失败:', error);
throw error;
}
},
/**
* 获取 RAG 索引状态
*/
async getRAGStatus(): Promise<{
success: boolean;
vector_count: number;
collections: string[];
}> {
const url = `${BACKEND_BASE_URL}/rag/status`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取 RAG 状态失败');
return await response.json();
} catch (error) {
console.error('获取 RAG 状态失败:', error);
throw error;
}
},
/**
* 重建 RAG 索引
*/
async rebuildRAGIndex(): Promise<{
success: boolean;
message: string;
}> {
const url = `${BACKEND_BASE_URL}/rag/rebuild`;
try {
const response = await fetch(url, {
method: 'POST',
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '重建索引失败');
}
return await response.json();
} catch (error) {
console.error('重建 RAG 索引失败:', error);
throw error;
}
},
// ==================== 表格填写 ====================
/**
* 上传表格模板
*/
async uploadTemplate(file: File): Promise<{
success: boolean;
template_id: string;
fields: TemplateField[];
sheets: string[];
}> {
const formData = new FormData();
formData.append('file', file);
const url = `${BACKEND_BASE_URL}/templates/upload`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '上传模板失败');
}
return await response.json();
} catch (error) {
console.error('上传模板失败:', error);
throw error;
}
},
/**
* 从已上传的模板提取字段定义
*/
async extractTemplateFields(
templateId: string,
fileType: string = 'xlsx'
): Promise<{
success: boolean;
fields: TemplateField[];
}> {
const url = `${BACKEND_BASE_URL}/templates/fields`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_id: templateId,
file_type: fileType,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '提取字段失败');
}
return await response.json();
} catch (error) {
console.error('提取字段失败:', error);
throw error;
}
},
/**
* 联合上传模板和源文档
*/
async uploadTemplateAndSources(
templateFile: File,
sourceFiles: File[]
): Promise<{
success: boolean;
template_id: string;
filename: string;
file_type: string;
fields: TemplateField[];
field_count: number;
source_file_paths: string[];
source_filenames: string[];
task_id: string;
}> {
const formData = new FormData();
formData.append('template_file', templateFile);
sourceFiles.forEach(file => formData.append('source_files', file));
const url = `${BACKEND_BASE_URL}/templates/upload-joint`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '联合上传失败');
}
return await response.json();
} catch (error) {
console.error('联合上传失败:', error);
throw error;
}
},
/**
* 执行表格填写
*/
async fillTemplate(
templateId: string,
templateFields: TemplateField[],
sourceDocIds?: string[],
sourceFilePaths?: string[],
userHint?: string
): Promise<FillResult> {
const url = `${BACKEND_BASE_URL}/templates/fill`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_id: templateId,
template_fields: templateFields,
source_doc_ids: sourceDocIds || [],
source_file_paths: sourceFilePaths || [],
user_hint: userHint || null,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '填写表格失败');
}
return await response.json();
} catch (error) {
console.error('填写表格失败:', error);
throw error;
}
},
/**
* 导出填写后的表格
*/
async exportFilledTemplate(
templateId: string,
filledData: Record<string, any>,
format: 'xlsx' | 'docx' = 'xlsx',
filledFilePath?: string
): Promise<Blob> {
const url = `${BACKEND_BASE_URL}/templates/export`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_id: templateId,
filled_data: filledData,
format,
...(filledFilePath && { filled_file_path: filledFilePath }),
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '导出失败');
}
return await response.blob();
} catch (error) {
console.error('导出表格失败:', error);
throw error;
}
},
/**
* 填充原始模板并导出
*
* 直接打开原始模板文件,将数据填入模板的表格/单元格中,然后导出
* 适用于比赛场景:保持原始模板格式不变
*/
async fillAndExportTemplate(
templatePath: string,
filledData: Record<string, any>,
format: 'xlsx' | 'docx' = 'xlsx'
): Promise<Blob> {
const url = `${BACKEND_BASE_URL}/templates/fill-and-export`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
template_path: templatePath,
filled_data: filledData,
format,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '填充模板失败');
}
return await response.blob();
} catch (error) {
console.error('填充模板失败:', error);
throw error;
}
},
// ==================== Excel 专用接口 (保留兼容) ====================
/**
* 上传并解析 Excel 文件
*/
async uploadExcel(
file: File,
options: ExcelUploadOptions = {}
): Promise<ExcelParseResult> {
const formData = new FormData();
formData.append('file', file);
const params = new URLSearchParams();
if (options.parseAllSheets === true) {
params.append('parse_all_sheets', 'true');
}
if (options.sheetName) {
params.append('sheet_name', options.sheetName);
}
if (options.headerRow !== undefined) {
params.append('header_row', String(options.headerRow));
}
const url = `${BACKEND_BASE_URL}/upload/excel?${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 || '上传失败');
}
return await response.json();
} catch (error) {
console.error('上传 Excel 文件失败:', error);
throw error;
}
},
/**
* 导出 Excel 文件
*/
async exportExcel(
filePath: string,
options: ExcelExportOptions = {}
): Promise<Blob> {
const params = new URLSearchParams();
if (options.sheetName) {
params.append('sheet_name', options.sheetName);
}
if (options.columns && options.columns.length > 0) {
params.append('columns', options.columns.join(','));
}
const url = `${BACKEND_BASE_URL}/upload/excel/export/${encodeURIComponent(filePath)}?${params.toString()}`;
try {
const response = await fetch(url);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '导出失败');
}
return await response.blob();
} catch (error) {
console.error('导出 Excel 文件失败:', error);
throw error;
}
},
/**
* 获取 Excel 文件预览
*/
async getExcelPreview(
filePath: string,
sheetName?: string,
maxRows: number = 10
): Promise<ExcelParseResult> {
const params = new URLSearchParams();
if (sheetName !== undefined) {
params.append('sheet_name', sheetName);
}
params.append('max_rows', String(maxRows));
const url = `${BACKEND_BASE_URL}/upload/excel/preview/${encodeURIComponent(filePath)}?${params.toString()}`;
try {
const response = await fetch(url);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '获取预览失败');
}
return await response.json();
} catch (error) {
console.error('获取预览失败:', error);
throw error;
}
},
/**
* 删除已上传的文件
*/
async deleteUploadedFile(filePath: string): Promise<{ success: boolean; message: string }> {
const url = `${BACKEND_BASE_URL}/upload/file?file_path=${encodeURIComponent(filePath)}`;
try {
const response = await fetch(url, {
method: 'DELETE',
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '删除失败');
}
return await response.json();
} catch (error) {
console.error('删除文件失败:', error);
throw error;
}
},
// ==================== 智能指令 API ====================
/**
* 智能对话(支持多轮对话的指令执行)
*/
async instructionChat(
instruction: string,
docIds?: string[],
context?: Record<string, any>
): Promise<{
success: boolean;
intent: string;
result: Record<string, any>;
message: string;
hint?: string;
}> {
const url = `${BACKEND_BASE_URL}/instruction/chat`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction, doc_ids: docIds, context }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '对话处理失败');
}
return await response.json();
} catch (error) {
console.error('对话处理失败:', error);
throw error;
}
},
/**
* 获取支持的指令类型列表
*/
async getSupportedIntents(): Promise<{
intents: Array<{
intent: string;
name: string;
examples: string[];
params: string[];
}>;
}> {
const url = `${BACKEND_BASE_URL}/instruction/intents`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取指令列表失败');
return await response.json();
} catch (error) {
console.error('获取指令列表失败:', error);
throw error;
}
},
/**
* 执行指令(同步模式)
*/
async executeInstruction(
instruction: string,
docIds?: string[],
context?: Record<string, any>
): Promise<{
success: boolean;
intent: string;
result: Record<string, any>;
message: string;
}> {
const url = `${BACKEND_BASE_URL}/instruction/execute`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction, doc_ids: docIds, context }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '指令执行失败');
}
return await response.json();
} catch (error) {
console.error('指令执行失败:', error);
throw error;
}
},
};
// ==================== AI 分析 API ====================
export interface AIChartRequest {
analysis_text: string;
original_filename?: string;
file_type?: string;
}
export interface VisualizationResult {
success: boolean;
statistics?: {
numeric?: Record<string, any>;
categorical?: Record<string, any>;
};
charts?: {
histograms?: Array<any>;
bar_charts?: Array<any>;
box_plots?: Array<any>;
correlation?: any;
};
distributions?: Record<string, any>;
row_count?: number;
column_count?: number;
error?: string;
}
export const aiApi = {
/**
* 上传并使用 AI 分析 Excel 文件
*/
async analyzeExcel(
file: File,
options: AIAnalyzeOptions = {}
): Promise<AIExcelAnalyzeResult> {
const formData = new FormData();
formData.append('file', file);
const params = new URLSearchParams();
if (options.userPrompt) {
params.append('user_prompt', options.userPrompt);
}
if (options.analysisType) {
params.append('analysis_type', options.analysisType);
}
if (options.parseAllSheets === true) {
params.append('parse_all_sheets', 'true');
}
const url = `${BACKEND_BASE_URL}/ai/analyze/excel?${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 || 'AI 分析失败');
}
return await response.json();
} catch (error) {
console.error('AI 分析失败:', error);
throw error;
}
},
/**
* 对已解析的 Excel 数据进行 AI 分析
*/
async analyzeText(
excelData: ExcelData,
userPrompt: string = '',
analysisType: string = 'general'
): Promise<AIAnalysisResult> {
const url = `${BACKEND_BASE_URL}/ai/analyze/text`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
excel_data: excelData,
user_prompt: userPrompt,
analysis_type: analysisType,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '文本分析失败');
}
return await response.json();
} catch (error) {
console.error('文本分析失败:', error);
throw error;
}
},
/**
* 获取支持的分析类型
*/
async getAnalysisTypes(): Promise<{
types: Array<{ value: string; label: string; description: string }>;
}> {
const url = `${BACKEND_BASE_URL}/ai/analysis/types`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取分析类型失败');
return await response.json();
} catch (error) {
console.error('获取分析类型失败:', error);
throw error;
}
},
/**
* 上传并使用 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: 'POST',
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;
}
},
/**
* 上传并使用 AI 分析 TXT 文本文件,提取结构化数据
*/
async analyzeTxt(
file: File
): Promise<{
success: boolean;
filename?: string;
structured_data?: {
table?: {
columns?: string[];
rows?: string[][];
};
summary?: string;
key_value_pairs?: Array<{ key: string; value: string }>;
numeric_data?: Array<{ name: string; value: number; unit?: string }>;
};
error?: string;
}> {
const formData = new FormData();
formData.append('file', file);
const url = `${BACKEND_BASE_URL}/ai/analyze/txt`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'TXT AI 分析失败');
}
return await response.json();
} catch (error) {
console.error('TXT AI 分析失败:', error);
throw error;
}
},
/**
* 生成统计信息和图表
*/
async generateStatistics(
excelData: ExcelData,
analysisType: string = 'statistics'
): Promise<VisualizationResult> {
const url = `${BACKEND_BASE_URL}/visualization/statistics`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
excel_data: excelData,
analysis_type: analysisType
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '生成图表失败');
}
return await response.json();
} catch (error) {
console.error('生成图表失败:', error);
throw error;
}
},
/**
* 获取支持的图表类型
*/
async getChartTypes(): Promise<{
chart_types: Array<{ value: string; label: string; description: string }>;
}> {
const url = `${BACKEND_BASE_URL}/visualization/chart-types`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取图表类型失败');
return await response.json();
} catch (error) {
console.error('获取图表类型失败:', error);
throw error;
}
},
/**
* 从 AI 分析结果中提取数据并生成图表
*/
async extractAndGenerateCharts(request: AIChartRequest) {
const url = `${BACKEND_BASE_URL}/analysis/extract-and-chart`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '生成图表失败');
}
return await response.json();
} catch (error) {
console.error('生成分析结果图表失败:', error);
throw error;
}
},
/**
* 仅提取结构化数据(调试用)
*/
async analyzeTextOnly(request: AIChartRequest) {
const url = `${BACKEND_BASE_URL}/analysis/analyze-text`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '分析失败');
}
return await response.json();
} catch (error) {
console.error('分析文本失败:', error);
throw error;
}
},
// ==================== Word AI 解析 ====================
/**
* 使用 AI 解析 Word 文档,提取结构化数据
*/
async analyzeWordWithAI(
file: File,
userHint: string = ''
): Promise<{
success: boolean;
type?: string;
headers?: string[];
rows?: string[][];
key_values?: Record<string, string>;
list_items?: string[];
summary?: string;
error?: string;
}> {
const formData = new FormData();
formData.append('file', file);
if (userHint) {
formData.append('user_hint', userHint);
}
const url = `${BACKEND_BASE_URL}/ai/analyze/word`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Word AI 解析失败');
}
return await response.json();
} catch (error) {
console.error('Word AI 解析失败:', error);
throw error;
}
},
/**
* 使用 AI 解析 Word 文档并填写模板
* 一次性完成AI解析 + 填表
*/
async fillTemplateFromWordAI(
file: File,
templateFields: TemplateField[],
userHint: string = ''
): Promise<FillResult> {
const formData = new FormData();
formData.append('file', file);
formData.append('template_fields', JSON.stringify(templateFields));
if (userHint) {
formData.append('user_hint', userHint);
}
const url = `${BACKEND_BASE_URL}/ai/analyze/word/fill-template`;
try {
const response = await fetch(url, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Word AI 填表失败');
}
return await response.json();
} catch (error) {
console.error('Word AI 填表失败:', error);
throw error;
}
},
// ==================== 智能指令 ====================
/**
* 识别自然语言指令的意图
*/
async recognizeIntent(
instruction: string,
docIds?: string[]
): Promise<{
success: boolean;
intent: string;
params: Record<string, any>;
message: string;
}> {
const url = `${BACKEND_BASE_URL}/instruction/recognize`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction, doc_ids: docIds }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '意图识别失败');
}
return await response.json();
} catch (error) {
console.error('意图识别失败:', error);
throw error;
}
},
/**
* 执行自然语言指令
*/
async executeInstruction(
instruction: string,
docIds?: string[],
context?: Record<string, any>
): Promise<{
success: boolean;
intent: string;
result: Record<string, any>;
message: string;
}> {
const url = `${BACKEND_BASE_URL}/instruction/execute`;
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction, doc_ids: docIds, context }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || '指令执行失败');
}
return await response.json();
} catch (error) {
console.error('指令执行失败:', error);
throw error;
}
},
// ==================== 对话历史 API ====================
/**
* 获取对话历史
*/
async getConversationHistory(conversationId: string, limit: number = 20): Promise<{
success: boolean;
messages: Array<{
role: string;
content: string;
intent?: string;
created_at: string;
}>;
}> {
const url = `${BACKEND_BASE_URL}/conversation/${conversationId}/history?limit=${limit}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取对话历史失败');
return await response.json();
} catch (error) {
console.error('获取对话历史失败:', error);
return { success: false, messages: [] };
}
},
/**
* 删除对话历史
*/
async deleteConversation(conversationId: string): Promise<{
success: boolean;
}> {
const url = `${BACKEND_BASE_URL}/conversation/${conversationId}`;
try {
const response = await fetch(url, { method: 'DELETE' });
if (!response.ok) throw new Error('删除对话历史失败');
return await response.json();
} catch (error) {
console.error('删除对话历史失败:', error);
return { success: false };
}
},
/**
* 获取会话列表
*/
async listConversations(limit: number = 50): Promise<{
success: boolean;
conversations: Array<any>;
}> {
const url = `${BACKEND_BASE_URL}/conversation/all?limit=${limit}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('获取会话列表失败');
return await response.json();
} catch (error) {
console.error('获取会话列表失败:', error);
return { success: false, conversations: [] };
}
}
};