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:
2025-11-16 15:43:39 +08:00
parent 11325f88a7
commit 0fe6821a89
52 changed files with 7324 additions and 109 deletions

View 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,
};

View File

@@ -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,
}

View File

@@ -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,

View File

@@ -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对象
},
};

View 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,
};