fix(pkb): fix create KB and upload issues - remove simulated upload, fix department mapping, add upload modal

Fixed issues:
- Remove simulateUpload function from DashboardPage Step 3
- Map department to description field when creating KB
- Add upload modal in WorkspacePage knowledge assets tab
- Fix DocumentUpload import path (../../stores to ../stores)

Known issue: Dify API validation error during document upload (file uploaded but DB record failed, needs investigation)

Testing: KB creation works, upload dialog opens correctly
This commit is contained in:
2026-01-13 13:17:20 +08:00
parent d595037316
commit 4088275290
280 changed files with 4344 additions and 150 deletions

View File

@@ -42,3 +42,6 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2

View File

@@ -268,6 +268,9 @@

View File

@@ -218,3 +218,6 @@ https://iit.xunzhengyixue.com/api/v1/iit/health

View File

@@ -147,3 +147,6 @@ https://iit.xunzhengyixue.com/api/v1/iit/health

View File

@@ -48,3 +48,6 @@

View File

@@ -308,3 +308,6 @@ npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts

View File

@@ -170,3 +170,6 @@ npm run dev

View File

@@ -47,3 +47,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -41,3 +41,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -36,3 +36,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -68,3 +68,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -31,3 +31,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -72,3 +72,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -19,3 +19,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -107,3 +107,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -78,3 +78,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -64,3 +64,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -106,3 +106,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -17,3 +17,6 @@ VALUES (
) )
ON CONFLICT (id) DO NOTHING; ON CONFLICT (id) DO NOTHING;

View File

@@ -49,3 +49,6 @@ VALUES (
) )
ON CONFLICT (id) DO NOTHING; ON CONFLICT (id) DO NOTHING;

View File

@@ -63,6 +63,9 @@ WHERE table_schema = 'dc_schema'

View File

@@ -101,6 +101,9 @@ ORDER BY ordinal_position;

View File

@@ -114,6 +114,9 @@ runMigration()

View File

@@ -48,6 +48,9 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名

View File

@@ -75,6 +75,9 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创

View File

@@ -115,6 +115,9 @@ Write-Host ""

View File

@@ -225,6 +225,9 @@ function extractCodeBlocks(obj, blocks = []) {

View File

@@ -26,3 +26,6 @@ CREATE TABLE IF NOT EXISTS platform_schema.job_common (
policy text policy text
); );

View File

@@ -100,3 +100,6 @@ CREATE OR REPLACE FUNCTION platform_schema.delete_queue(queue_name text) RETURNS
END; END;
$$; $$;

View File

@@ -244,6 +244,9 @@ checkDCTables();

View File

@@ -1,3 +1,6 @@
-- Create capability_schema for Prompt Management System -- Create capability_schema for Prompt Management System
CREATE SCHEMA IF NOT EXISTS capability_schema; CREATE SCHEMA IF NOT EXISTS capability_schema;

View File

@@ -196,6 +196,9 @@ createAiHistoryTable()

View File

@@ -183,6 +183,9 @@ createToolCTable()

View File

@@ -180,6 +180,9 @@ createToolCTable()

View File

@@ -111,3 +111,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -331,3 +331,6 @@ runTests().catch(error => {

View File

@@ -77,3 +77,6 @@ async function testAPI() {
testAPI().catch(console.error); testAPI().catch(console.error);

View File

@@ -296,3 +296,6 @@ verifySchemas()

View File

@@ -235,7 +235,7 @@ export async function logout(
/** /**
* 获取当前用户可访问的模块 * <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>û<EFBFBD><EFBFBD>ɷ<EFBFBD><EFBFBD>ʵ<EFBFBD>ģ<EFBFBD><EFBFBD>
* *
* GET /api/v1/auth/me/modules * GET /api/v1/auth/me/modules
*/ */
@@ -248,11 +248,11 @@ export async function getUserModules(
return reply.status(401).send({ return reply.status(401).send({
success: false, success: false,
error: 'Unauthorized', error: 'Unauthorized',
message: '未认证', message: 'δ<EFBFBD><EFBFBD>֤',
}); });
} }
// SUPER_ADMIN PROMPT_ENGINEER 可以访问所有模块 // SUPER_ADMIN <EFBFBD><EFBFBD> PROMPT_ENGINEER <EFBFBD><EFBFBD><EFBFBD>Է<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD>
if (request.user.role === 'SUPER_ADMIN' || request.user.role === 'PROMPT_ENGINEER') { if (request.user.role === 'SUPER_ADMIN' || request.user.role === 'PROMPT_ENGINEER') {
const { moduleService } = await import('./module.service.js'); const { moduleService } = await import('./module.service.js');
const allModules = await moduleService.getAllModules(); const allModules = await moduleService.getAllModules();
@@ -270,8 +270,8 @@ export async function getUserModules(
data: result.modules, data: result.modules,
}); });
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : '获取用户模块失败'; const message = error instanceof Error ? error.message : '<EFBFBD><EFBFBD>ȡ<EFBFBD>û<EFBFBD>ģ<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>';
logger.error('获取用户模块失败', { error: message, userId: request.user?.userId }); logger.error('<EFBFBD><EFBFBD>ȡ<EFBFBD>û<EFBFBD>ģ<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>', { error: message, userId: request.user?.userId });
return reply.status(500).send({ return reply.status(500).send({
success: false, success: false,

View File

@@ -184,3 +184,6 @@ export class JWTService {
// 导出单例 // 导出单例
export const jwtService = new JWTService(); export const jwtService = new JWTService();

View File

@@ -312,6 +312,9 @@ export function getBatchItems<T>(

View File

@@ -100,3 +100,4 @@ export function getAllFallbackCodes(): string[] {

View File

@@ -69,3 +69,4 @@ export interface VariableValidation {

View File

@@ -75,3 +75,4 @@ export async function moduleRoutes(fastify: FastifyInstance) {
}); });
} }

View File

@@ -105,3 +105,4 @@ export interface PaginatedResponse<T> {
totalPages: number; totalPages: number;
} }

View File

@@ -9,6 +9,17 @@ import { logger } from '../../../common/logging/index.js';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { startScreeningTask } from '../services/screeningService.js'; import { startScreeningTask } from '../services/screeningService.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;
}
/** /**
* 导入文献从Excel或JSON * 导入文献从Excel或JSON
*/ */
@@ -17,7 +28,7 @@ export async function importLiteratures(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId, literatures } = request.body; const { projectId, literatures } = request.body;
// 验证项目归属 // 验证项目归属
@@ -90,7 +101,7 @@ export async function importLiteraturesFromExcel(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
// 获取上传的文件 // 获取上传的文件
const data = await request.file(); const data = await request.file();
@@ -176,7 +187,7 @@ export async function getLiteratures(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
const { page = 1, limit = 50 } = request.query; const { page = 1, limit = 50 } = request.query;
@@ -239,7 +250,7 @@ export async function deleteLiterature(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { literatureId } = request.params; const { literatureId } = request.params;
// 验证文献归属 // 验证文献归属

View File

@@ -7,16 +7,26 @@ import { CreateScreeningProjectDto } from '../types/index.js';
import { prisma } from '../../../config/database.js'; import { prisma } from '../../../config/database.js';
import { logger } from '../../../common/logging/index.js'; import { logger } from '../../../common/logging/index.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 createProject( export async function createProject(
request: FastifyRequest<{ Body: CreateScreeningProjectDto & { userId?: string } }>, request: FastifyRequest<{ Body: CreateScreeningProjectDto }>,
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
// 临时测试模式优先从JWT获取否则从请求体获取 const userId = getUserId(request);
const userId = (request as any).userId || (request.body as any).userId || 'asl-test-user-001';
const { projectName, picoCriteria, inclusionCriteria, exclusionCriteria, screeningConfig } = request.body; const { projectName, picoCriteria, inclusionCriteria, exclusionCriteria, screeningConfig } = request.body;
// 验证必填字段 // 验证必填字段
@@ -65,7 +75,7 @@ export async function createProject(
*/ */
export async function getProjects(request: FastifyRequest, reply: FastifyReply) { export async function getProjects(request: FastifyRequest, reply: FastifyReply) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const projects = await prisma.aslScreeningProject.findMany({ const projects = await prisma.aslScreeningProject.findMany({
where: { userId }, where: { userId },
@@ -100,7 +110,7 @@ export async function getProjectById(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
const project = await prisma.aslScreeningProject.findFirst({ const project = await prisma.aslScreeningProject.findFirst({
@@ -148,7 +158,7 @@ export async function updateProject(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
const updateData = request.body; const updateData = request.body;
@@ -190,7 +200,7 @@ export async function deleteProject(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
// 验证项目归属 // 验证项目归属

View File

@@ -6,6 +6,17 @@ import { FastifyRequest, FastifyReply } from 'fastify';
import { prisma } from '../../../config/database.js'; import { prisma } from '../../../config/database.js';
import { logger } from '../../../common/logging/index.js'; import { logger } from '../../../common/logging/index.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;
}
/** /**
* 获取筛选任务进度 * 获取筛选任务进度
* GET /api/v1/asl/projects/:projectId/screening-task * GET /api/v1/asl/projects/:projectId/screening-task
@@ -15,7 +26,7 @@ export async function getScreeningTask(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
// 验证项目归属 // 验证项目归属
@@ -74,7 +85,7 @@ export async function getScreeningResults(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
const page = parseInt(request.query.page || '1', 10); const page = parseInt(request.query.page || '1', 10);
const pageSize = parseInt(request.query.pageSize || '50', 10); const pageSize = parseInt(request.query.pageSize || '50', 10);
@@ -175,7 +186,7 @@ export async function getScreeningResultDetail(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { resultId } = request.params; const { resultId } = request.params;
const result = await prisma.aslScreeningResult.findUnique({ const result = await prisma.aslScreeningResult.findUnique({
@@ -241,7 +252,7 @@ export async function reviewScreeningResult(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { resultId } = request.params; const { resultId } = request.params;
const { decision, note } = request.body; const { decision, note } = request.body;
@@ -327,7 +338,7 @@ export async function getProjectStatistics(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
const { projectId } = request.params; const { projectId } = request.params;
// 1. 验证项目归属 // 1. 验证项目归属

View File

@@ -348,6 +348,9 @@ runTests().catch((error) => {

View File

@@ -327,6 +327,9 @@ Content-Type: application/json

View File

@@ -18,6 +18,17 @@ import { FulltextScreeningService } from '../services/FulltextScreeningService.j
// 初始化服务 // 初始化服务
const screeningService = new FulltextScreeningService(); const screeningService = new FulltextScreeningService();
/**
* 获取用户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;
}
// ==================== Zod验证Schema ==================== // ==================== Zod验证Schema ====================
/** /**
@@ -79,7 +90,7 @@ export async function createTask(
const { projectId, literatureIds, modelA, modelB, promptVersion } = validated; const { projectId, literatureIds, modelA, modelB, promptVersion } = validated;
// 获取当前用户ID测试模式 // 获取当前用户ID测试模式
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
logger.info('Creating fulltext screening task', { logger.info('Creating fulltext screening task', {
projectId, projectId,
@@ -204,7 +215,7 @@ export async function getTaskProgress(
) { ) {
try { try {
const { taskId } = request.params; const { taskId } = request.params;
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
// 获取任务详情 // 获取任务详情
const task = await prisma.aslFulltextScreeningTask.findFirst({ const task = await prisma.aslFulltextScreeningTask.findFirst({
@@ -318,7 +329,7 @@ export async function getTaskResults(
) { ) {
try { try {
const { taskId } = request.params; const { taskId } = request.params;
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
// 参数验证 // 参数验证
const validated = GetResultsQuerySchema.parse(request.query); const validated = GetResultsQuerySchema.parse(request.query);
@@ -507,7 +518,7 @@ export async function updateDecision(
) { ) {
try { try {
const { resultId } = request.params; const { resultId } = request.params;
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
// 参数验证 // 参数验证
const validated = UpdateDecisionSchema.parse(request.body); const validated = UpdateDecisionSchema.parse(request.body);
@@ -589,7 +600,7 @@ export async function exportExcel(
) { ) {
try { try {
const { taskId } = request.params; const { taskId } = request.params;
const userId = (request as any).userId || 'asl-test-user-001'; const userId = getUserId(request);
// 验证任务权限 // 验证任务权限
const task = await prisma.aslFulltextScreeningTask.findFirst({ const task = await prisma.aslFulltextScreeningTask.findFirst({

View File

@@ -267,5 +267,6 @@ export const conflictDetectionService = new ConflictDetectionService();

View File

@@ -217,5 +217,6 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \

View File

@@ -271,5 +271,6 @@ export const streamAIController = new StreamAIController();

View File

@@ -180,3 +180,6 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {

View File

@@ -114,3 +114,6 @@ checkTableStructure();

View File

@@ -101,3 +101,6 @@ checkProjectConfig().catch(console.error);

View File

@@ -83,3 +83,6 @@ main();

View File

@@ -540,3 +540,6 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback

View File

@@ -175,3 +175,6 @@ console.log('');

View File

@@ -492,3 +492,6 @@ export const patientWechatService = new PatientWechatService();

View File

@@ -137,3 +137,6 @@ testDifyIntegration().catch(error => {

View File

@@ -166,3 +166,6 @@ testIitDatabase()

View File

@@ -152,3 +152,6 @@ if (hasError) {

View File

@@ -178,3 +178,6 @@ async function testUrlVerification() {

View File

@@ -259,3 +259,6 @@ main().catch((error) => {

View File

@@ -143,3 +143,6 @@ Write-Host ""

View File

@@ -236,3 +236,6 @@ export interface CachedProtocolRules {

View File

@@ -13,6 +13,17 @@ import { executeBatchTask, retryFailedDocuments, BatchProgress } from '../servic
import { prisma } from '../../../config/database.js'; import { prisma } from '../../../config/database.js';
import { ModelType } from '../../../common/llm/adapters/types.js'; import { ModelType } from '../../../common/llm/adapters/types.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;
}
// ==================== 类型定义 ==================== // ==================== 类型定义 ====================
interface ExecuteBatchBody { interface ExecuteBatchBody {
@@ -40,8 +51,7 @@ export async function executeBatch(
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
// TODO: 从JWT获取userId const userId = getUserId(request);
const userId = 'user-mock-001';
const { const {
kb_id, kb_id,
@@ -365,7 +375,7 @@ export async function retryFailed(
) { ) {
try { try {
const { taskId } = request.params; const { taskId } = request.params;
const userId = 'user-mock-001'; // TODO: 从JWT获取 const userId = getUserId(request);
// 获取WebSocket实例 // 获取WebSocket实例
const io = (request.server as any).io; const io = (request.server as any).io;

View File

@@ -1,8 +1,16 @@
import type { FastifyRequest, FastifyReply } from 'fastify'; import type { FastifyRequest, FastifyReply } from 'fastify';
import * as documentService from '../services/documentService.js'; import * as documentService from '../services/documentService.js';
// Mock用户ID实际应从JWT token中获取 /**
const MOCK_USER_ID = 'user-mock-001'; * 获取用户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;
}
/** /**
* 上传文档 * 上传文档
@@ -69,8 +77,9 @@ export async function uploadDocument(
// 上传文档这里fileUrl暂时为空实际应该上传到对象存储 // 上传文档这里fileUrl暂时为空实际应该上传到对象存储
console.log(`⚙️ 调用文档服务上传文件...`); console.log(`⚙️ 调用文档服务上传文件...`);
const userId = getUserId(request);
const document = await documentService.uploadDocument( const document = await documentService.uploadDocument(
MOCK_USER_ID, userId,
kbId, kbId,
file, file,
filename, filename,
@@ -123,7 +132,8 @@ export async function getDocuments(
try { try {
const { kbId } = request.params; const { kbId } = request.params;
const documents = await documentService.getDocuments(MOCK_USER_ID, kbId); const userId = getUserId(request);
const documents = await documentService.getDocuments(userId, kbId);
return reply.send({ return reply.send({
success: true, success: true,
@@ -160,7 +170,8 @@ export async function getDocumentById(
try { try {
const { id } = request.params; const { id } = request.params;
const document = await documentService.getDocumentById(MOCK_USER_ID, id); const userId = getUserId(request);
const document = await documentService.getDocumentById(userId, id);
return reply.send({ return reply.send({
success: true, success: true,
@@ -197,7 +208,8 @@ export async function deleteDocument(
try { try {
const { id } = request.params; const { id } = request.params;
await documentService.deleteDocument(MOCK_USER_ID, id); const userId = getUserId(request);
await documentService.deleteDocument(userId, id);
return reply.send({ return reply.send({
success: true, success: true,
@@ -234,7 +246,8 @@ export async function reprocessDocument(
try { try {
const { id } = request.params; const { id } = request.params;
await documentService.reprocessDocument(MOCK_USER_ID, id); const userId = getUserId(request);
await documentService.reprocessDocument(userId, id);
return reply.send({ return reply.send({
success: true, success: true,
@@ -271,7 +284,8 @@ export async function getDocumentFullText(
try { try {
const { id } = request.params; const { id } = request.params;
const document = await documentService.getDocumentById(MOCK_USER_ID, id); const userId = getUserId(request);
const document = await documentService.getDocumentById(userId, id);
// 返回完整的文档信息 // 返回完整的文档信息
return reply.send({ return reply.send({

View File

@@ -1,8 +1,16 @@
import type { FastifyRequest, FastifyReply } from 'fastify'; import type { FastifyRequest, FastifyReply } from 'fastify';
import * as knowledgeBaseService from '../services/knowledgeBaseService.js'; import * as knowledgeBaseService from '../services/knowledgeBaseService.js';
// Mock用户ID实际应从JWT token中获取 /**
const MOCK_USER_ID = 'user-mock-001'; * 获取用户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;
}
/** /**
* 创建知识库 * 创建知识库
@@ -26,8 +34,9 @@ export async function createKnowledgeBase(
}); });
} }
const userId = getUserId(request);
const knowledgeBase = await knowledgeBaseService.createKnowledgeBase( const knowledgeBase = await knowledgeBaseService.createKnowledgeBase(
MOCK_USER_ID, userId,
name, name,
description description
); );
@@ -49,12 +58,13 @@ export async function createKnowledgeBase(
* 获取知识库列表 * 获取知识库列表
*/ */
export async function getKnowledgeBases( export async function getKnowledgeBases(
_request: FastifyRequest, request: FastifyRequest,
reply: FastifyReply reply: FastifyReply
) { ) {
try { try {
const userId = getUserId(request);
const knowledgeBases = await knowledgeBaseService.getKnowledgeBases( const knowledgeBases = await knowledgeBaseService.getKnowledgeBases(
MOCK_USER_ID userId
); );
return reply.send({ return reply.send({
@@ -84,8 +94,9 @@ export async function getKnowledgeBaseById(
try { try {
const { id } = request.params; const { id } = request.params;
const userId = getUserId(request);
const knowledgeBase = await knowledgeBaseService.getKnowledgeBaseById( const knowledgeBase = await knowledgeBaseService.getKnowledgeBaseById(
MOCK_USER_ID, userId,
id id
); );
@@ -129,8 +140,9 @@ export async function updateKnowledgeBase(
const { id } = request.params; const { id } = request.params;
const updateData = request.body; const updateData = request.body;
const userId = getUserId(request);
const knowledgeBase = await knowledgeBaseService.updateKnowledgeBase( const knowledgeBase = await knowledgeBaseService.updateKnowledgeBase(
MOCK_USER_ID, userId,
id, id,
updateData updateData
); );
@@ -170,7 +182,8 @@ export async function deleteKnowledgeBase(
try { try {
const { id } = request.params; const { id } = request.params;
await knowledgeBaseService.deleteKnowledgeBase(MOCK_USER_ID, id); const userId = getUserId(request);
await knowledgeBaseService.deleteKnowledgeBase(userId, id);
return reply.send({ return reply.send({
success: true, success: true,
@@ -221,8 +234,9 @@ export async function searchKnowledgeBase(
const topK = top_k ? parseInt(top_k, 10) : 15; // Phase 1优化默认从3增加到15 const topK = top_k ? parseInt(top_k, 10) : 15; // Phase 1优化默认从3增加到15
const userId = getUserId(request);
const results = await knowledgeBaseService.searchKnowledgeBase( const results = await knowledgeBaseService.searchKnowledgeBase(
MOCK_USER_ID, userId,
id, id,
query, query,
topK topK
@@ -263,8 +277,9 @@ export async function getKnowledgeBaseStats(
try { try {
const { id } = request.params; const { id } = request.params;
const userId = getUserId(request);
const stats = await knowledgeBaseService.getKnowledgeBaseStats( const stats = await knowledgeBaseService.getKnowledgeBaseStats(
MOCK_USER_ID, userId,
id id
); );
@@ -311,8 +326,9 @@ export async function getDocumentSelection(
const maxFiles = max_files ? parseInt(max_files, 10) : undefined; const maxFiles = max_files ? parseInt(max_files, 10) : undefined;
const maxTokens = max_tokens ? parseInt(max_tokens, 10) : undefined; const maxTokens = max_tokens ? parseInt(max_tokens, 10) : undefined;
const userId = getUserId(request);
const selection = await knowledgeBaseService.getDocumentSelection( const selection = await knowledgeBaseService.getDocumentSelection(
MOCK_USER_ID, userId,
id, id,
maxFiles, maxFiles,
maxTokens maxTokens

View File

@@ -51,3 +51,4 @@ export default async function healthRoutes(fastify: FastifyInstance) {

View File

@@ -29,9 +29,14 @@ export async function createKnowledgeBase(
} }
// 2. 在Dify中创建Dataset // 2. 在Dify中创建Dataset
// Dify API name字段限制避免特殊字符保持简洁
const sanitizedName = name
.replace(/[^\u4e00-\u9fa5a-zA-Z0-9_-]/g, '_') // 移除特殊字符
.substring(0, 50); // 限制长度
const difyDataset = await difyClient.createDataset({ const difyDataset = await difyClient.createDataset({
name: `${userId}_${name}_${Date.now()}`, name: `kb_${sanitizedName}_${Date.now()}`, // 简化格式
description: description || `Knowledge base for user ${userId}`, description: description?.substring(0, 200) || '', // 限制描述长度
indexing_technique: 'high_quality', indexing_technique: 'high_quality',
}); });

View File

@@ -127,3 +127,6 @@ Content-Type: application/json

View File

@@ -112,3 +112,6 @@ Write-Host " - 删除任务: DELETE $BaseUrl/api/v2/rvw/tasks/{taskId}" -Foregr

View File

@@ -15,16 +15,16 @@ import { ModelType } from '../../../common/llm/adapters/types.js';
import * as reviewService from '../services/reviewService.js'; import * as reviewService from '../services/reviewService.js';
import { AgentType } from '../types/index.js'; import { AgentType } from '../types/index.js';
// TODO: 集成JWT认证后移除
const MOCK_USER_ID = 'user-mock-001';
/** /**
* 获取用户ID暂时使用Mock待JWT集成 * 获取用户ID从JWT Token中获取
* 如果未认证则抛出错误
*/ */
function getUserId(request: FastifyRequest): string { function getUserId(request: FastifyRequest): string {
// TODO: 从JWT token中获取 const userId = (request as any).user?.userId;
// return request.user?.id; if (!userId) {
return MOCK_USER_ID; throw new Error('User not authenticated');
}
return userId;
} }
// ==================== 任务创建 ==================== // ==================== 任务创建 ====================
@@ -121,12 +121,20 @@ export async function createTask(
}, },
}); });
} catch (error) { } catch (error) {
logger.error('[RVW:Controller] 上传失败', { const errorMessage = error instanceof Error ? error.message : 'Unknown error';
error: error instanceof Error ? error.message : 'Unknown error' logger.error('[RVW:Controller] 上传失败', { error: errorMessage });
});
// 区分认证错误和其他错误
if (errorMessage === 'User not authenticated') {
return reply.status(401).send({
success: false,
message: '用户未认证,请重新登录',
});
}
return reply.status(500).send({ return reply.status(500).send({
success: false, success: false,
message: error instanceof Error ? error.message : '上传失败', message: errorMessage || '上传失败',
}); });
} }
} }

View File

@@ -26,3 +26,6 @@ export * from './services/utils.js';

View File

@@ -1,36 +1,44 @@
/** /**
* RVW稿件审查模块 - 稿约规范性评估服务 * RVW稿件审查模块 - 稿约规范性评估服务
* @module rvw/services/editorialService * @module rvw/services/editorialService
*
* Phase 3.5.5 改造:使用 PromptService 替代文件读取
* - 支持灰度预览(调试者看 DRAFT普通用户看 ACTIVE
* - 三级容灾(数据库→缓存→兜底)
*/ */
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js'; import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
import { ModelType } from '../../../common/llm/adapters/types.js'; import { ModelType } from '../../../common/llm/adapters/types.js';
import { logger } from '../../../common/logging/index.js'; import { logger } from '../../../common/logging/index.js';
import { prisma } from '../../../config/database.js';
import { getPromptService } from '../../../common/prompt/index.js';
import { EditorialReview } from '../types/index.js'; import { EditorialReview } from '../types/index.js';
import { parseJSONFromLLMResponse } from './utils.js'; import { parseJSONFromLLMResponse } from './utils.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Prompt文件路径
const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_editorial_system.txt');
/** /**
* 稿约规范性评估 * 稿约规范性评估
* @param text 稿件文本 * @param text 稿件文本
* @param modelType 模型类型 * @param modelType 模型类型
* @param userId 用户ID用于灰度预览判断
* @returns 评估结果 * @returns 评估结果
*/ */
export async function reviewEditorialStandards( export async function reviewEditorialStandards(
text: string, text: string,
modelType: ModelType = 'deepseek-v3' modelType: ModelType = 'deepseek-v3',
userId?: string
): Promise<EditorialReview> { ): Promise<EditorialReview> {
try { try {
// 1. 取系统Prompt // 1. 从 PromptService 获取系统Prompt(支持灰度预览)
const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8'); const promptService = getPromptService(prisma);
const { content: systemPrompt, isDraft } = await promptService.get(
'RVW_EDITORIAL',
{},
{ userId }
);
if (isDraft) {
logger.info('[RVW:Editorial] 使用 DRAFT 版本 Prompt调试模式', { userId });
}
// 2. 构建消息 // 2. 构建消息
const messages = [ const messages = [
@@ -71,3 +79,4 @@ export async function reviewEditorialStandards(

View File

@@ -1,36 +1,44 @@
/** /**
* RVW稿件审查模块 - 方法学评估服务 * RVW稿件审查模块 - 方法学评估服务
* @module rvw/services/methodologyService * @module rvw/services/methodologyService
*
* Phase 3.5.5 改造:使用 PromptService 替代文件读取
* - 支持灰度预览(调试者看 DRAFT普通用户看 ACTIVE
* - 三级容灾(数据库→缓存→兜底)
*/ */
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js'; import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
import { ModelType } from '../../../common/llm/adapters/types.js'; import { ModelType } from '../../../common/llm/adapters/types.js';
import { logger } from '../../../common/logging/index.js'; import { logger } from '../../../common/logging/index.js';
import { prisma } from '../../../config/database.js';
import { getPromptService } from '../../../common/prompt/index.js';
import { MethodologyReview } from '../types/index.js'; import { MethodologyReview } from '../types/index.js';
import { parseJSONFromLLMResponse } from './utils.js'; import { parseJSONFromLLMResponse } from './utils.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Prompt文件路径
const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_methodology_system.txt');
/** /**
* 方法学评估 * 方法学评估
* @param text 稿件文本 * @param text 稿件文本
* @param modelType 模型类型 * @param modelType 模型类型
* @param userId 用户ID用于灰度预览判断
* @returns 评估结果 * @returns 评估结果
*/ */
export async function reviewMethodology( export async function reviewMethodology(
text: string, text: string,
modelType: ModelType = 'deepseek-v3' modelType: ModelType = 'deepseek-v3',
userId?: string
): Promise<MethodologyReview> { ): Promise<MethodologyReview> {
try { try {
// 1. 取系统Prompt // 1. 从 PromptService 获取系统Prompt(支持灰度预览)
const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8'); const promptService = getPromptService(prisma);
const { content: systemPrompt, isDraft } = await promptService.get(
'RVW_METHODOLOGY',
{},
{ userId }
);
if (isDraft) {
logger.info('[RVW:Methodology] 使用 DRAFT 版本 Prompt调试模式', { userId });
}
// 2. 构建消息 // 2. 构建消息
const messages = [ const messages = [
@@ -71,3 +79,4 @@ export async function reviewMethodology(

View File

@@ -117,3 +117,6 @@ export function validateAgentSelection(agents: string[]): void {

View File

@@ -78,7 +78,8 @@ export function registerReviewWorker() {
logger.info('[reviewWorker] Running editorial review', { taskId }); logger.info('[reviewWorker] Running editorial review', { taskId });
console.log(' 🔍 运行稿约规范性智能体...'); console.log(' 🔍 运行稿约规范性智能体...');
editorialResult = await reviewEditorialStandards(extractedText, modelType); // ✅ Phase 3.5.5: 传递 userId 支持灰度预览
editorialResult = await reviewEditorialStandards(extractedText, modelType, userId);
logger.info('[reviewWorker] Editorial review completed', { logger.info('[reviewWorker] Editorial review completed', {
taskId, taskId,
@@ -97,7 +98,8 @@ export function registerReviewWorker() {
logger.info('[reviewWorker] Running methodology review', { taskId }); logger.info('[reviewWorker] Running methodology review', { taskId });
console.log(' 🔬 运行方法学智能体...'); console.log(' 🔬 运行方法学智能体...');
methodologyResult = await reviewMethodology(extractedText, modelType); // ✅ Phase 3.5.5: 传递 userId 支持灰度预览
methodologyResult = await reviewMethodology(extractedText, modelType, userId);
logger.info('[reviewWorker] Methodology review completed', { logger.info('[reviewWorker] Methodology review completed', {
taskId, taskId,

View File

@@ -413,6 +413,9 @@ SET session_replication_role = 'origin';

View File

@@ -115,6 +115,9 @@ WHERE key = 'verify_test';

View File

@@ -258,6 +258,9 @@ verifyDatabase()

View File

@@ -48,6 +48,9 @@ export {}

View File

@@ -71,6 +71,9 @@ Write-Host "✅ 完成!" -ForegroundColor Green

View File

@@ -1,2 +1,5 @@
SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') ORDER BY schema_name; SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') ORDER BY schema_name;

View File

@@ -163,3 +163,6 @@ DELETE {{baseUrl}}/api/v2/pkb/knowledge/knowledge-bases/{{testKbId}}

View File

@@ -358,6 +358,9 @@ runAdvancedTests().catch(error => {

View File

@@ -424,6 +424,9 @@ runAllTests()

View File

@@ -382,6 +382,9 @@ runAllTests()

View File

@@ -20,3 +20,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -18,3 +18,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -30,3 +30,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -19,3 +19,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

View File

@@ -159,3 +159,6 @@ main()
.catch(console.error) .catch(console.error)
.finally(() => prisma.$disconnect()); .finally(() => prisma.$disconnect());

Binary file not shown.

View File

@@ -166,6 +166,9 @@ Set-Location ..

View File

@@ -608,6 +608,9 @@ async saveProcessedData(recordId, newData) {

Some files were not shown because too many files have changed in this diff Show More