Major milestone: Successfully replaced Dify external service with PostgreSQL + pgvector RAG engine Backend changes: - Refactor ragService.ts: Remove dual-track mode, keep only pgvector - Refactor knowledgeBaseService.ts: Remove Dify creation logic - Refactor documentService.ts: Remove Dify upload/polling logic - DifyClient.ts: Convert to deprecated stub file (for legacy compatibility) - common/rag/index.ts: Update exports - common/rag/types.ts: Remove Dify types, keep generic RAG types - config/env.ts: Remove Dify configuration Frontend changes: - DashboardPage.tsx: Add delete knowledge base dropdown menu - KnowledgeBaseList.tsx: Enhance quota warning display - CreateKBDialog.tsx: Add quota exceeded modal with guidance - knowledgeBaseApi.ts: Add auth interceptor Documentation: - Update PKB module status guide (v2.3) - Update system status guide (v4.0) Performance metrics: - Single query latency: 2.5s - Single query cost: 0.0025 CNY - Cross-language accuracy improvement: +20.5% Remaining tasks: - OSS storage integration - pg_bigm extension installation Tested: End-to-end test passed (create KB -> upload doc -> vector search)
368 lines
7.7 KiB
TypeScript
368 lines
7.7 KiB
TypeScript
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||
import * as knowledgeBaseService from '../services/knowledgeBaseService.js';
|
||
|
||
/**
|
||
* 获取用户ID(从JWT Token中获取)
|
||
*/
|
||
function getUserId(request: FastifyRequest): string {
|
||
const userId = (request as any).user?.userId;
|
||
if (!userId) {
|
||
throw new Error('User not authenticated');
|
||
}
|
||
return userId;
|
||
}
|
||
|
||
/**
|
||
* 创建知识库
|
||
*/
|
||
export async function createKnowledgeBase(
|
||
request: FastifyRequest<{
|
||
Body: {
|
||
name: string;
|
||
description?: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { name, description } = request.body;
|
||
|
||
if (!name || name.trim().length === 0) {
|
||
return reply.status(400).send({
|
||
success: false,
|
||
message: 'Knowledge base name is required',
|
||
});
|
||
}
|
||
|
||
const userId = getUserId(request);
|
||
const knowledgeBase = await knowledgeBaseService.createKnowledgeBase(
|
||
userId,
|
||
name,
|
||
description
|
||
);
|
||
|
||
return reply.status(201).send({
|
||
success: true,
|
||
data: knowledgeBase,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to create knowledge base:', error);
|
||
|
||
// 处理配额超限错误
|
||
if (error.code === 'QUOTA_EXCEEDED') {
|
||
return reply.status(400).send({
|
||
success: false,
|
||
code: 'QUOTA_EXCEEDED',
|
||
message: error.message,
|
||
});
|
||
}
|
||
|
||
return reply.status(500).send({
|
||
success: false,
|
||
message: error.message || '创建知识库失败,请稍后重试',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取知识库列表
|
||
*/
|
||
export async function getKnowledgeBases(
|
||
request: FastifyRequest,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const userId = getUserId(request);
|
||
const knowledgeBases = await knowledgeBaseService.getKnowledgeBases(
|
||
userId
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: knowledgeBases,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to get knowledge bases:', error);
|
||
return reply.status(500).send({
|
||
success: false,
|
||
message: error.message || 'Failed to get knowledge bases',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取知识库详情
|
||
*/
|
||
export async function getKnowledgeBaseById(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
|
||
const userId = getUserId(request);
|
||
const knowledgeBase = await knowledgeBaseService.getKnowledgeBaseById(
|
||
userId,
|
||
id
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: knowledgeBase,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to get knowledge base:', 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 knowledge base',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新知识库
|
||
*/
|
||
export async function updateKnowledgeBase(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
Body: {
|
||
name?: string;
|
||
description?: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
const updateData = request.body;
|
||
|
||
const userId = getUserId(request);
|
||
const knowledgeBase = await knowledgeBaseService.updateKnowledgeBase(
|
||
userId,
|
||
id,
|
||
updateData
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: knowledgeBase,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to update knowledge base:', 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 update knowledge base',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除知识库
|
||
*/
|
||
export async function deleteKnowledgeBase(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
|
||
const userId = getUserId(request);
|
||
await knowledgeBaseService.deleteKnowledgeBase(userId, id);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
message: 'Knowledge base deleted successfully',
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to delete knowledge base:', 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 knowledge base',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检索知识库
|
||
*/
|
||
export async function searchKnowledgeBase(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
Querystring: {
|
||
query: string;
|
||
top_k?: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
const { query, top_k } = request.query;
|
||
|
||
if (!query || query.trim().length === 0) {
|
||
return reply.status(400).send({
|
||
success: false,
|
||
message: 'Query parameter is required',
|
||
});
|
||
}
|
||
|
||
const topK = top_k ? parseInt(top_k, 10) : 15; // Phase 1优化:默认从3增加到15
|
||
|
||
const userId = getUserId(request);
|
||
const results = await knowledgeBaseService.searchKnowledgeBase(
|
||
userId,
|
||
id,
|
||
query,
|
||
topK
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: results,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to search knowledge base:', 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 search knowledge base',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取知识库统计信息
|
||
*/
|
||
export async function getKnowledgeBaseStats(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
|
||
const userId = getUserId(request);
|
||
const stats = await knowledgeBaseService.getKnowledgeBaseStats(
|
||
userId,
|
||
id
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: stats,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to get knowledge base stats:', 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 knowledge base stats',
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取知识库文档选择(Phase 2: 全文阅读模式)
|
||
*/
|
||
export async function getDocumentSelection(
|
||
request: FastifyRequest<{
|
||
Params: {
|
||
id: string;
|
||
};
|
||
Querystring: {
|
||
max_files?: string;
|
||
max_tokens?: string;
|
||
};
|
||
}>,
|
||
reply: FastifyReply
|
||
) {
|
||
try {
|
||
const { id } = request.params;
|
||
const { max_files, max_tokens } = request.query;
|
||
|
||
const maxFiles = max_files ? parseInt(max_files, 10) : undefined;
|
||
const maxTokens = max_tokens ? parseInt(max_tokens, 10) : undefined;
|
||
|
||
const userId = getUserId(request);
|
||
const selection = await knowledgeBaseService.getDocumentSelection(
|
||
userId,
|
||
id,
|
||
maxFiles,
|
||
maxTokens
|
||
);
|
||
|
||
return reply.send({
|
||
success: true,
|
||
data: selection,
|
||
});
|
||
} catch (error: any) {
|
||
console.error('Failed to get document selection:', 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 selection',
|
||
});
|
||
}
|
||
}
|
||
|