feat(pkb): Complete PKB module frontend migration with V3 design

Summary:
- Implement PKB Dashboard and Workspace pages based on V3 prototype
- Add single-layer header with integrated Tab navigation
- Implement 3 work modes: Full Text, Deep Read, Batch Processing
- Integrate Ant Design X Chat component for AI conversations
- Create BatchModeComplete with template selection and document processing
- Add compact work mode selector with dropdown design

Backend:
- Migrate PKB controllers and services to /modules/pkb structure
- Register v2 API routes at /api/v2/pkb/knowledge
- Maintain dual API routes for backward compatibility

Technical details:
- Use Zustand for state management
- Handle SSE streaming responses for AI chat
- Support document selection for Deep Read mode
- Implement batch processing with progress tracking

Known issues:
- Batch processing API integration pending
- Knowledge assets page navigation needs optimization

Status: Frontend functional, pending refinement
This commit is contained in:
2026-01-06 22:15:42 +08:00
parent b31255031e
commit 5a17d096a7
226 changed files with 14899 additions and 224 deletions

View File

@@ -0,0 +1,314 @@
import type { FastifyRequest, FastifyReply } from 'fastify';
import * as documentService from '../services/documentService.js';
// Mock用户ID实际应从JWT token中获取
const MOCK_USER_ID = 'user-mock-001';
/**
* 上传文档
*/
export async function uploadDocument(
request: FastifyRequest<{
Params: {
kbId: string;
};
}>,
reply: FastifyReply
) {
try {
const { kbId } = request.params;
console.log(`📤 开始上传文档到知识库: ${kbId}`);
// 获取上传的文件
const data = await request.file();
if (!data) {
console.error('❌ 没有接收到文件');
return reply.status(400).send({
success: false,
message: 'No file uploaded',
});
}
console.log(`📄 接收到文件: ${data.filename}, 类型: ${data.mimetype}`);
const file = await data.toBuffer();
const filename = data.filename;
const fileType = data.mimetype;
const fileSizeBytes = file.length;
// 文件大小限制10MB
const maxSize = 10 * 1024 * 1024;
console.log(`📊 文件大小: ${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB (限制: 10MB)`);
if (fileSizeBytes > maxSize) {
console.error(`❌ 文件太大: ${(fileSizeBytes / 1024 / 1024).toFixed(2)}MB`);
return reply.status(400).send({
success: false,
message: 'File size exceeds 10MB limit',
});
}
// 文件类型限制
const allowedTypes = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
'text/markdown',
];
console.log(`🔍 检查文件类型: ${fileType}`);
if (!allowedTypes.includes(fileType)) {
console.error(`❌ 不支持的文件类型: ${fileType}`);
return reply.status(400).send({
success: false,
message: 'File type not supported. Allowed: PDF, DOC, DOCX, TXT, MD',
});
}
// 上传文档这里fileUrl暂时为空实际应该上传到对象存储
console.log(`⚙️ 调用文档服务上传文件...`);
const document = await documentService.uploadDocument(
MOCK_USER_ID,
kbId,
file,
filename,
fileType,
fileSizeBytes,
'' // fileUrl - 可以上传到OSS后填入
);
console.log(`✅ 文档上传成功: ${document.id}`);
return reply.status(201).send({
success: true,
data: document,
});
} catch (error: any) {
console.error('❌ 文档上传失败:', error.message);
console.error('错误详情:', error);
if (error.message.includes('not found') || error.message.includes('access denied')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
if (error.message.includes('limit exceeded')) {
return reply.status(400).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to upload document',
});
}
}
/**
* 获取文档列表
*/
export async function getDocuments(
request: FastifyRequest<{
Params: {
kbId: string;
};
}>,
reply: FastifyReply
) {
try {
const { kbId } = request.params;
const documents = await documentService.getDocuments(MOCK_USER_ID, kbId);
return reply.send({
success: true,
data: documents,
});
} catch (error: any) {
console.error('Failed to get documents:', error);
if (error.message.includes('not found')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to get documents',
});
}
}
/**
* 获取文档详情
*/
export async function getDocumentById(
request: FastifyRequest<{
Params: {
id: string;
};
}>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const document = await documentService.getDocumentById(MOCK_USER_ID, id);
return reply.send({
success: true,
data: document,
});
} catch (error: any) {
console.error('Failed to get document:', error);
if (error.message.includes('not found')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to get document',
});
}
}
/**
* 删除文档
*/
export async function deleteDocument(
request: FastifyRequest<{
Params: {
id: string;
};
}>,
reply: FastifyReply
) {
try {
const { id } = request.params;
await documentService.deleteDocument(MOCK_USER_ID, id);
return reply.send({
success: true,
message: 'Document deleted successfully',
});
} catch (error: any) {
console.error('Failed to delete document:', error);
if (error.message.includes('not found')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to delete document',
});
}
}
/**
* 重新处理文档
*/
export async function reprocessDocument(
request: FastifyRequest<{
Params: {
id: string;
};
}>,
reply: FastifyReply
) {
try {
const { id } = request.params;
await documentService.reprocessDocument(MOCK_USER_ID, id);
return reply.send({
success: true,
message: 'Document reprocessing started',
});
} catch (error: any) {
console.error('Failed to reprocess document:', error);
if (error.message.includes('not found')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to reprocess document',
});
}
}
/**
* Phase 2: 获取文档全文(用于逐篇精读模式)
*/
export async function getDocumentFullText(
request: FastifyRequest<{
Params: {
id: string;
};
}>,
reply: FastifyReply
) {
try {
const { id } = request.params;
const document = await documentService.getDocumentById(MOCK_USER_ID, id);
// 返回完整的文档信息
return reply.send({
success: true,
data: {
documentId: document.id,
filename: document.filename,
fileType: document.fileType,
fileSizeBytes: document.fileSizeBytes,
extractedText: (document as any).extractedText || null,
charCount: (document as any).charCount || null,
tokensCount: document.tokensCount || null,
extractionMethod: (document as any).extractionMethod || null,
extractionQuality: (document as any).extractionQuality || null,
language: (document as any).language || null,
metadata: {
uploadedAt: document.uploadedAt,
processedAt: document.processedAt,
status: document.status,
},
},
});
} catch (error: any) {
console.error('Failed to get document full text:', error);
if (error.message.includes('not found')) {
return reply.status(404).send({
success: false,
message: error.message,
});
}
return reply.status(500).send({
success: false,
message: error.message || 'Failed to get document full text',
});
}
}