feat(frontend): add batch processing and review features
- Add batch processing API and mode - Add deep read mode for full-text analysis - Add document selector and switcher components - Add review page with editorial and methodology assessment - Add capacity indicator and usage info modal - Add custom hooks for batch tasks and chat modes - Update layouts and routing - Add TypeScript types for chat features
This commit is contained in:
146
frontend/src/api/batchApi.ts
Normal file
146
frontend/src/api/batchApi.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Phase 3: 批处理模式 - API封装
|
||||
*/
|
||||
|
||||
import request from './request';
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
export interface BatchTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
output_fields: Array<{
|
||||
key: string;
|
||||
label: string;
|
||||
type: string;
|
||||
description?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ExecuteBatchParams {
|
||||
kb_id: string;
|
||||
document_ids: string[];
|
||||
template_type: 'preset' | 'custom';
|
||||
template_id?: string;
|
||||
custom_prompt?: string;
|
||||
model_type: string;
|
||||
task_name?: string;
|
||||
}
|
||||
|
||||
export interface BatchTask {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'processing' | 'completed' | 'failed';
|
||||
total_documents: number;
|
||||
completed_count: number;
|
||||
failed_count: number;
|
||||
model_type: string;
|
||||
started_at: string;
|
||||
completed_at?: string;
|
||||
duration_seconds?: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface BatchResult {
|
||||
id: string;
|
||||
index: number;
|
||||
document_id: string;
|
||||
document_name: string;
|
||||
status: 'success' | 'failed';
|
||||
data: any;
|
||||
raw_output?: string;
|
||||
error_message?: string;
|
||||
processing_time_ms?: number;
|
||||
tokens_used?: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface BatchTaskWithResults {
|
||||
task: BatchTask;
|
||||
results: BatchResult[];
|
||||
}
|
||||
|
||||
// ==================== API函数 ====================
|
||||
|
||||
/**
|
||||
* 获取所有预设模板
|
||||
*/
|
||||
export async function getTemplates(): Promise<BatchTemplate[]> {
|
||||
const response = await request.get('/batch/templates');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行批处理任务
|
||||
*/
|
||||
export async function executeBatch(params: ExecuteBatchParams): Promise<{
|
||||
task_id: string;
|
||||
status: string;
|
||||
websocket_event: string;
|
||||
}> {
|
||||
const response = await request.post('/batch/execute', params);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态
|
||||
*/
|
||||
export async function getTask(taskId: string): Promise<BatchTask> {
|
||||
const response = await request.get(`/batch/tasks/${taskId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务结果
|
||||
*/
|
||||
export async function getTaskResults(taskId: string): Promise<BatchTaskWithResults> {
|
||||
const response = await request.get(`/batch/tasks/${taskId}/results`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试失败的文档
|
||||
*/
|
||||
export async function retryFailed(taskId: string): Promise<void> {
|
||||
await request.post(`/batch/tasks/${taskId}/retry-failed`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询任务状态直到完成
|
||||
*/
|
||||
export async function pollTaskUntilComplete(
|
||||
taskId: string,
|
||||
onProgress?: (task: BatchTask) => void,
|
||||
maxAttempts: number = 120, // 最多10分钟(120次 * 5秒)
|
||||
): Promise<BatchTask> {
|
||||
let attempts = 0;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
const task = await getTask(taskId);
|
||||
|
||||
if (onProgress) {
|
||||
onProgress(task);
|
||||
}
|
||||
|
||||
if (task.status === 'completed' || task.status === 'failed') {
|
||||
return task;
|
||||
}
|
||||
|
||||
// 等待5秒后再次查询
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
throw new Error('任务执行超时');
|
||||
}
|
||||
|
||||
export default {
|
||||
getTemplates,
|
||||
executeBatch,
|
||||
getTask,
|
||||
getTaskResults,
|
||||
retryFailed,
|
||||
pollTaskUntilComplete,
|
||||
};
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface SendChatMessageData {
|
||||
content: string
|
||||
modelType: string
|
||||
knowledgeBaseIds?: string[]
|
||||
documentIds?: string[] // Phase 2: 逐篇精读模式 - 限定文档范围(RAG检索)
|
||||
fullTextDocumentIds?: string[] // Phase 2: 全文阅读模式 - 传递完整全文
|
||||
conversationId?: string
|
||||
}
|
||||
|
||||
@@ -137,3 +139,4 @@ export default {
|
||||
deleteConversation,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,11 +10,14 @@ export const getApiInfo = () => {
|
||||
return request.get('/')
|
||||
}
|
||||
|
||||
// TODO: Day 9+ 添加更多API
|
||||
// - 项目管理API
|
||||
// - 对话API
|
||||
// - 知识库API
|
||||
// - 用户API
|
||||
// 导出各模块API
|
||||
export * from './projectApi'
|
||||
export * from './conversationApi'
|
||||
export * from './knowledgeBaseApi'
|
||||
export * from './agentApi'
|
||||
export * from './chatApi'
|
||||
export * from './batchApi'
|
||||
export * from './reviewApi'
|
||||
|
||||
export default {
|
||||
healthCheck,
|
||||
|
||||
@@ -181,6 +181,32 @@ export const documentApi = {
|
||||
async reprocess(id: string): Promise<void> {
|
||||
await api.post(`/documents/${id}/reprocess`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Phase 2: 获取文档全文(用于逐篇精读模式)
|
||||
*/
|
||||
async getFullText(id: string): Promise<any> {
|
||||
const response = await api.get(`/documents/${id}/full-text`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Phase 2: 文档选择API(用于全文阅读模式)
|
||||
*/
|
||||
export const documentSelectionApi = {
|
||||
/**
|
||||
* 获取知识库的文档选择结果
|
||||
*/
|
||||
async getSelection(kbId: string, maxFiles?: number, maxTokens?: number): Promise<any> {
|
||||
const params = new URLSearchParams();
|
||||
if (maxFiles) params.append('max_files', maxFiles.toString());
|
||||
if (maxTokens) params.append('max_tokens', maxTokens.toString());
|
||||
|
||||
const url = `/knowledge-bases/${kbId}/document-selection${params.toString() ? '?' + params.toString() : ''}`;
|
||||
const response = await api.get(url);
|
||||
return response.data.data; // 修复:返回内层的data对象
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
348
frontend/src/api/reviewApi.ts
Normal file
348
frontend/src/api/reviewApi.ts
Normal file
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* 稿件审查功能 - API封装
|
||||
*/
|
||||
|
||||
import request from './request';
|
||||
import axios from 'axios';
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
/**
|
||||
* 稿约规范性评估 - 单项
|
||||
*/
|
||||
export interface EditorialItem {
|
||||
criterion: string;
|
||||
status: 'pass' | 'warning' | 'fail';
|
||||
score: number;
|
||||
issues: string[];
|
||||
suggestions: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 稿约规范性评估结果
|
||||
*/
|
||||
export interface EditorialReview {
|
||||
overall_score: number;
|
||||
summary: string;
|
||||
items: EditorialItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法学评估 - 问题项
|
||||
*/
|
||||
export interface MethodologyIssue {
|
||||
type: string;
|
||||
severity: 'major' | 'minor';
|
||||
description: string;
|
||||
location: string;
|
||||
suggestion: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法学评估 - 部分
|
||||
*/
|
||||
export interface MethodologyPart {
|
||||
part: string;
|
||||
score: number;
|
||||
issues: MethodologyIssue[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法学评估结果
|
||||
*/
|
||||
export interface MethodologyReview {
|
||||
overall_score: number;
|
||||
summary: string;
|
||||
parts: MethodologyPart[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 审查任务状态
|
||||
*/
|
||||
export type ReviewTaskStatus =
|
||||
| 'pending' // 等待处理
|
||||
| 'extracting' // 提取文档文本
|
||||
| 'reviewing_editorial' // 稿约规范性评估
|
||||
| 'reviewing_methodology' // 方法学评估
|
||||
| 'completed' // 完成
|
||||
| 'failed'; // 失败
|
||||
|
||||
/**
|
||||
* 审查任务
|
||||
*/
|
||||
export interface ReviewTask {
|
||||
id: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
status: ReviewTaskStatus;
|
||||
wordCount?: number;
|
||||
overallScore?: number;
|
||||
modelUsed?: string;
|
||||
createdAt: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
durationSeconds?: number;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整审查报告
|
||||
*/
|
||||
export interface ReviewReport {
|
||||
taskId: string;
|
||||
fileName: string;
|
||||
wordCount?: number;
|
||||
modelUsed?: string;
|
||||
overallScore?: number;
|
||||
editorialReview?: EditorialReview;
|
||||
methodologyReview?: MethodologyReview;
|
||||
completedAt?: string;
|
||||
durationSeconds?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务列表项(简化版)
|
||||
*/
|
||||
export interface ReviewTaskListItem {
|
||||
id: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
status: ReviewTaskStatus;
|
||||
overallScore?: number;
|
||||
modelUsed?: string;
|
||||
createdAt: string;
|
||||
completedAt?: string;
|
||||
durationSeconds?: number;
|
||||
wordCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务列表响应
|
||||
*/
|
||||
export interface ReviewTaskListResponse {
|
||||
tasks: ReviewTaskListItem[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传参数
|
||||
*/
|
||||
export interface UploadManuscriptParams {
|
||||
file: File;
|
||||
modelType?: 'deepseek-v3' | 'qwen3-72b' | 'qwen-long';
|
||||
}
|
||||
|
||||
// ==================== API函数 ====================
|
||||
|
||||
/**
|
||||
* 上传稿件并开始审查
|
||||
*/
|
||||
export async function uploadManuscript(params: UploadManuscriptParams): Promise<{
|
||||
taskId: string;
|
||||
fileName: string;
|
||||
status: ReviewTaskStatus;
|
||||
createdAt: string;
|
||||
}> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', params.file);
|
||||
formData.append('modelType', params.modelType || 'deepseek-v3');
|
||||
|
||||
// 使用axios直接调用,因为FormData需要特殊处理
|
||||
const response = await axios.post('/api/v1/review/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态
|
||||
*/
|
||||
export async function getTaskStatus(taskId: string): Promise<ReviewTask> {
|
||||
const response = await request.get(`/review/tasks/${taskId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审查报告(完整)
|
||||
*/
|
||||
export async function getTaskReport(taskId: string): Promise<ReviewReport> {
|
||||
const response = await request.get(`/review/tasks/${taskId}/report`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务列表
|
||||
*/
|
||||
export async function getTaskList(page: number = 1, limit: number = 20): Promise<ReviewTaskListResponse> {
|
||||
const response = await request.get('/review/tasks', {
|
||||
params: { page, limit },
|
||||
});
|
||||
|
||||
return {
|
||||
tasks: response.data,
|
||||
pagination: response.pagination,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
*/
|
||||
export async function deleteTask(taskId: string): Promise<void> {
|
||||
await request.delete(`/review/tasks/${taskId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询任务状态直到完成
|
||||
* @param taskId 任务ID
|
||||
* @param onProgress 进度回调
|
||||
* @param maxAttempts 最大尝试次数(默认60次,即5分钟)
|
||||
* @returns 完成的任务
|
||||
*/
|
||||
export async function pollTaskUntilComplete(
|
||||
taskId: string,
|
||||
onProgress?: (task: ReviewTask) => void,
|
||||
maxAttempts: number = 60,
|
||||
): Promise<ReviewTask> {
|
||||
let attempts = 0;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
const task = await getTaskStatus(taskId);
|
||||
|
||||
if (onProgress) {
|
||||
onProgress(task);
|
||||
}
|
||||
|
||||
if (task.status === 'completed' || task.status === 'failed') {
|
||||
return task;
|
||||
}
|
||||
|
||||
// 等待5秒后再次查询
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
throw new Error('任务执行超时(超过5分钟)');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态的中文描述
|
||||
*/
|
||||
export function getStatusText(status: ReviewTaskStatus): string {
|
||||
const statusMap: Record<ReviewTaskStatus, string> = {
|
||||
pending: '等待处理',
|
||||
extracting: '提取文档文本',
|
||||
reviewing_editorial: '稿约规范性评估',
|
||||
reviewing_methodology: '方法学评估',
|
||||
completed: '评估完成',
|
||||
failed: '评估失败',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态的颜色
|
||||
*/
|
||||
export function getStatusColor(status: ReviewTaskStatus): string {
|
||||
const colorMap: Record<ReviewTaskStatus, string> = {
|
||||
pending: 'default',
|
||||
extracting: 'processing',
|
||||
reviewing_editorial: 'processing',
|
||||
reviewing_methodology: 'processing',
|
||||
completed: 'success',
|
||||
failed: 'error',
|
||||
};
|
||||
return colorMap[status] || 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时长
|
||||
*/
|
||||
export function formatDuration(seconds: number): string {
|
||||
if (seconds < 60) return `${seconds}秒`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}分${remainingSeconds}秒`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评分等级
|
||||
*/
|
||||
export function getScoreLevel(score: number): {
|
||||
level: 'excellent' | 'good' | 'fair' | 'poor';
|
||||
text: string;
|
||||
color: string;
|
||||
} {
|
||||
if (score >= 90) {
|
||||
return { level: 'excellent', text: '优秀', color: '#52c41a' };
|
||||
} else if (score >= 80) {
|
||||
return { level: 'good', text: '良好', color: '#1890ff' };
|
||||
} else if (score >= 60) {
|
||||
return { level: 'fair', text: '及格', color: '#faad14' };
|
||||
} else {
|
||||
return { level: 'poor', text: '不及格', color: '#f5222d' };
|
||||
}
|
||||
}
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
uploadManuscript,
|
||||
getTaskStatus,
|
||||
getTaskReport,
|
||||
getTaskList,
|
||||
deleteTask,
|
||||
pollTaskUntilComplete,
|
||||
getStatusText,
|
||||
getStatusColor,
|
||||
formatFileSize,
|
||||
formatDuration,
|
||||
getScoreLevel,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user