diff --git a/COMMIT_DAY1.txt b/COMMIT_DAY1.txt index 9a4622a0..b844541e 100644 --- a/COMMIT_DAY1.txt +++ b/COMMIT_DAY1.txt @@ -39,3 +39,5 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2 + + diff --git a/DC模块代码恢复指南.md b/DC模块代码恢复指南.md index 64ba3562..3aae358b 100644 --- a/DC模块代码恢复指南.md +++ b/DC模块代码恢复指南.md @@ -266,6 +266,8 @@ + + diff --git a/SAE_WECHAT_MP_DEPLOY_STEPS.md b/SAE_WECHAT_MP_DEPLOY_STEPS.md index 1c274939..602093a6 100644 --- a/SAE_WECHAT_MP_DEPLOY_STEPS.md +++ b/SAE_WECHAT_MP_DEPLOY_STEPS.md @@ -215,3 +215,5 @@ https://iit.xunzhengyixue.com/api/v1/iit/health + + diff --git a/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md b/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md index 2fd7a8a1..b4739279 100644 --- a/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md +++ b/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md @@ -144,3 +144,5 @@ https://iit.xunzhengyixue.com/api/v1/iit/health + + diff --git a/backend/RESTART_SERVER_NOW.md b/backend/RESTART_SERVER_NOW.md index 3d55a770..7408f25c 100644 --- a/backend/RESTART_SERVER_NOW.md +++ b/backend/RESTART_SERVER_NOW.md @@ -45,3 +45,5 @@ + + diff --git a/backend/WECHAT_MP_CONFIG_READY.md b/backend/WECHAT_MP_CONFIG_READY.md index bac8b054..21f31905 100644 --- a/backend/WECHAT_MP_CONFIG_READY.md +++ b/backend/WECHAT_MP_CONFIG_READY.md @@ -305,3 +305,5 @@ npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts + + diff --git a/backend/WECHAT_MP_QUICK_FIX.md b/backend/WECHAT_MP_QUICK_FIX.md index 51418173..3b23c99a 100644 --- a/backend/WECHAT_MP_QUICK_FIX.md +++ b/backend/WECHAT_MP_QUICK_FIX.md @@ -167,3 +167,5 @@ npm run dev + + diff --git a/backend/migrations/add_data_stats_to_tool_c_session.sql b/backend/migrations/add_data_stats_to_tool_c_session.sql index e5516d6d..1e11e33e 100644 --- a/backend/migrations/add_data_stats_to_tool_c_session.sql +++ b/backend/migrations/add_data_stats_to_tool_c_session.sql @@ -61,6 +61,8 @@ WHERE table_schema = 'dc_schema' + + diff --git a/backend/prisma/manual-migrations/001_add_postgres_cache_and_checkpoint.sql b/backend/prisma/manual-migrations/001_add_postgres_cache_and_checkpoint.sql index ea97c511..df7f4e36 100644 --- a/backend/prisma/manual-migrations/001_add_postgres_cache_and_checkpoint.sql +++ b/backend/prisma/manual-migrations/001_add_postgres_cache_and_checkpoint.sql @@ -99,6 +99,8 @@ ORDER BY ordinal_position; + + diff --git a/backend/prisma/manual-migrations/run-migration-002.ts b/backend/prisma/manual-migrations/run-migration-002.ts index 45cd856f..ee144dab 100644 --- a/backend/prisma/manual-migrations/run-migration-002.ts +++ b/backend/prisma/manual-migrations/run-migration-002.ts @@ -112,6 +112,8 @@ runMigration() + + diff --git a/backend/prisma/migrations/20251208_add_column_mapping/migration.sql b/backend/prisma/migrations/20251208_add_column_mapping/migration.sql index d82bb85b..9994b226 100644 --- a/backend/prisma/migrations/20251208_add_column_mapping/migration.sql +++ b/backend/prisma/migrations/20251208_add_column_mapping/migration.sql @@ -46,6 +46,8 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名 + + diff --git a/backend/prisma/migrations/create_tool_c_session.sql b/backend/prisma/migrations/create_tool_c_session.sql index 7954bbe9..2c253f15 100644 --- a/backend/prisma/migrations/create_tool_c_session.sql +++ b/backend/prisma/migrations/create_tool_c_session.sql @@ -73,6 +73,8 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创 + + diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 761aa08a..39eebf26 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -314,6 +314,7 @@ model ReviewTask { // 🆕 结果摘要(Phase 2新增,用于列表展示) editorialScore Float? @map("editorial_score") + methodologyScore Float? @map("methodology_score") methodologyStatus String? @map("methodology_status") // pass/warn/fail // 🆕 预留字段(暂不使用) diff --git a/backend/rebuild-and-push.ps1 b/backend/rebuild-and-push.ps1 index c5650417..18b1f799 100644 --- a/backend/rebuild-and-push.ps1 +++ b/backend/rebuild-and-push.ps1 @@ -113,6 +113,8 @@ Write-Host "" + + diff --git a/backend/recover-code-from-cursor-db.js b/backend/recover-code-from-cursor-db.js index b7ed326f..5e34e98b 100644 --- a/backend/recover-code-from-cursor-db.js +++ b/backend/recover-code-from-cursor-db.js @@ -223,6 +223,8 @@ function extractCodeBlocks(obj, blocks = []) { + + diff --git a/backend/scripts/check-dc-tables.mjs b/backend/scripts/check-dc-tables.mjs index 8b37e3c5..8db4769d 100644 --- a/backend/scripts/check-dc-tables.mjs +++ b/backend/scripts/check-dc-tables.mjs @@ -242,6 +242,8 @@ checkDCTables(); + + diff --git a/backend/scripts/create-tool-c-ai-history-table.mjs b/backend/scripts/create-tool-c-ai-history-table.mjs index 65b42030..c9bac58d 100644 --- a/backend/scripts/create-tool-c-ai-history-table.mjs +++ b/backend/scripts/create-tool-c-ai-history-table.mjs @@ -194,6 +194,8 @@ createAiHistoryTable() + + diff --git a/backend/scripts/create-tool-c-table.js b/backend/scripts/create-tool-c-table.js index 733e1f21..11ff239c 100644 --- a/backend/scripts/create-tool-c-table.js +++ b/backend/scripts/create-tool-c-table.js @@ -181,6 +181,8 @@ createToolCTable() + + diff --git a/backend/scripts/create-tool-c-table.mjs b/backend/scripts/create-tool-c-table.mjs index 25d0409f..d756fd88 100644 --- a/backend/scripts/create-tool-c-table.mjs +++ b/backend/scripts/create-tool-c-table.mjs @@ -178,6 +178,8 @@ createToolCTable() + + diff --git a/backend/scripts/test-pkb-apis-simple.ts b/backend/scripts/test-pkb-apis-simple.ts index f0178252..f178105a 100644 --- a/backend/scripts/test-pkb-apis-simple.ts +++ b/backend/scripts/test-pkb-apis-simple.ts @@ -328,3 +328,5 @@ runTests().catch(error => { + + diff --git a/backend/scripts/verify-pkb-rvw-schema.ts b/backend/scripts/verify-pkb-rvw-schema.ts index 23a465c2..bd4e521b 100644 --- a/backend/scripts/verify-pkb-rvw-schema.ts +++ b/backend/scripts/verify-pkb-rvw-schema.ts @@ -293,3 +293,5 @@ verifySchemas() + + diff --git a/backend/src/common/jobs/utils.ts b/backend/src/common/jobs/utils.ts index 0b5ab775..9b66b4c5 100644 --- a/backend/src/common/jobs/utils.ts +++ b/backend/src/common/jobs/utils.ts @@ -310,6 +310,8 @@ export function getBatchItems( + + diff --git a/backend/src/index.ts b/backend/src/index.ts index 5f7873f3..8663f933 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -20,6 +20,7 @@ import { registerTestRoutes } from './test-platform-api.js'; import { registerScreeningWorkers } from './modules/asl/services/screeningWorker.js'; import { registerExtractionWorkers } from './modules/dc/tool-b/workers/extractionWorker.js'; import { registerParseExcelWorker } from './modules/dc/tool-c/workers/parseExcelWorker.js'; +import { registerReviewWorker } from './modules/rvw/workers/reviewWorker.js'; import { jobQueue } from './common/jobs/index.js'; @@ -183,6 +184,10 @@ const start = async () => { registerParseExcelWorker(); logger.info('✅ DC Tool C parse excel worker registered'); + // 注册RVW审稿Worker + registerReviewWorker(); + logger.info('✅ RVW review worker registered'); + // 注册IIT Manager Workers await initIitManager(); logger.info('✅ IIT Manager workers registered'); @@ -201,6 +206,7 @@ const start = async () => { console.log(' - asl_screening_batch (文献筛选批次处理)'); console.log(' - dc_extraction_batch (数据提取批次处理)'); console.log(' - dc_toolc_parse_excel (Tool C Excel解析)'); + console.log(' - rvw_review_task (稿件审查任务)'); console.log(' - iit_quality_check (IIT质控+企微推送)'); console.log(' - iit_redcap_poll (IIT REDCap轮询)'); console.log('='.repeat(60) + '\n'); diff --git a/backend/src/modules/asl/fulltext-screening/__tests__/api-integration-test.ts b/backend/src/modules/asl/fulltext-screening/__tests__/api-integration-test.ts index 27b3120d..908a56b0 100644 --- a/backend/src/modules/asl/fulltext-screening/__tests__/api-integration-test.ts +++ b/backend/src/modules/asl/fulltext-screening/__tests__/api-integration-test.ts @@ -346,6 +346,8 @@ runTests().catch((error) => { + + diff --git a/backend/src/modules/asl/fulltext-screening/__tests__/e2e-real-test-v2.ts b/backend/src/modules/asl/fulltext-screening/__tests__/e2e-real-test-v2.ts index dcda1348..8cca739e 100644 --- a/backend/src/modules/asl/fulltext-screening/__tests__/e2e-real-test-v2.ts +++ b/backend/src/modules/asl/fulltext-screening/__tests__/e2e-real-test-v2.ts @@ -287,6 +287,8 @@ runTest() + + diff --git a/backend/src/modules/asl/fulltext-screening/__tests__/fulltext-screening-api.http b/backend/src/modules/asl/fulltext-screening/__tests__/fulltext-screening-api.http index 57f66d08..09951d3d 100644 --- a/backend/src/modules/asl/fulltext-screening/__tests__/fulltext-screening-api.http +++ b/backend/src/modules/asl/fulltext-screening/__tests__/fulltext-screening-api.http @@ -325,6 +325,8 @@ Content-Type: application/json + + diff --git a/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts b/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts index e32c93ab..00738968 100644 --- a/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts +++ b/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts @@ -261,6 +261,8 @@ export const conflictDetectionService = new ConflictDetectionService(); + + diff --git a/backend/src/modules/dc/tool-c/README.md b/backend/src/modules/dc/tool-c/README.md index fdb9c9a8..818cf903 100644 --- a/backend/src/modules/dc/tool-c/README.md +++ b/backend/src/modules/dc/tool-c/README.md @@ -211,6 +211,8 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \ + + diff --git a/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts b/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts index 7dcfe384..54e59ce7 100644 --- a/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts +++ b/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts @@ -265,6 +265,8 @@ export const streamAIController = new StreamAIController(); + + diff --git a/backend/src/modules/iit-manager/agents/SessionMemory.ts b/backend/src/modules/iit-manager/agents/SessionMemory.ts index 485311b3..b9131ed8 100644 --- a/backend/src/modules/iit-manager/agents/SessionMemory.ts +++ b/backend/src/modules/iit-manager/agents/SessionMemory.ts @@ -177,3 +177,5 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', { + + diff --git a/backend/src/modules/iit-manager/check-iit-table-structure.ts b/backend/src/modules/iit-manager/check-iit-table-structure.ts index 6128e91f..747ebe58 100644 --- a/backend/src/modules/iit-manager/check-iit-table-structure.ts +++ b/backend/src/modules/iit-manager/check-iit-table-structure.ts @@ -111,3 +111,5 @@ checkTableStructure(); + + diff --git a/backend/src/modules/iit-manager/check-project-config.ts b/backend/src/modules/iit-manager/check-project-config.ts index b8652ce6..45e000d8 100644 --- a/backend/src/modules/iit-manager/check-project-config.ts +++ b/backend/src/modules/iit-manager/check-project-config.ts @@ -98,3 +98,5 @@ checkProjectConfig().catch(console.error); + + diff --git a/backend/src/modules/iit-manager/check-test-project-in-db.ts b/backend/src/modules/iit-manager/check-test-project-in-db.ts index ad07a5bf..2e4f0111 100644 --- a/backend/src/modules/iit-manager/check-test-project-in-db.ts +++ b/backend/src/modules/iit-manager/check-test-project-in-db.ts @@ -80,3 +80,5 @@ main(); + + diff --git a/backend/src/modules/iit-manager/docs/微信服务号接入指南.md b/backend/src/modules/iit-manager/docs/微信服务号接入指南.md index e37fdfa1..b6227bbd 100644 --- a/backend/src/modules/iit-manager/docs/微信服务号接入指南.md +++ b/backend/src/modules/iit-manager/docs/微信服务号接入指南.md @@ -537,3 +537,5 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback + + diff --git a/backend/src/modules/iit-manager/generate-wechat-tokens.ts b/backend/src/modules/iit-manager/generate-wechat-tokens.ts index 445d4b36..87feaba3 100644 --- a/backend/src/modules/iit-manager/generate-wechat-tokens.ts +++ b/backend/src/modules/iit-manager/generate-wechat-tokens.ts @@ -172,3 +172,5 @@ console.log(''); + + diff --git a/backend/src/modules/iit-manager/services/PatientWechatService.ts b/backend/src/modules/iit-manager/services/PatientWechatService.ts index 9d9be2c7..0c461457 100644 --- a/backend/src/modules/iit-manager/services/PatientWechatService.ts +++ b/backend/src/modules/iit-manager/services/PatientWechatService.ts @@ -489,3 +489,5 @@ export const patientWechatService = new PatientWechatService(); + + diff --git a/backend/src/modules/iit-manager/test-chatservice-dify.ts b/backend/src/modules/iit-manager/test-chatservice-dify.ts index 941461f3..2cd7d4d3 100644 --- a/backend/src/modules/iit-manager/test-chatservice-dify.ts +++ b/backend/src/modules/iit-manager/test-chatservice-dify.ts @@ -134,3 +134,5 @@ testDifyIntegration().catch(error => { + + diff --git a/backend/src/modules/iit-manager/test-iit-database.ts b/backend/src/modules/iit-manager/test-iit-database.ts index 831c3c6b..231395bc 100644 --- a/backend/src/modules/iit-manager/test-iit-database.ts +++ b/backend/src/modules/iit-manager/test-iit-database.ts @@ -163,3 +163,5 @@ testIitDatabase() + + diff --git a/backend/src/modules/iit-manager/test-patient-wechat-config.ts b/backend/src/modules/iit-manager/test-patient-wechat-config.ts index 830f1d92..4526d099 100644 --- a/backend/src/modules/iit-manager/test-patient-wechat-config.ts +++ b/backend/src/modules/iit-manager/test-patient-wechat-config.ts @@ -149,3 +149,5 @@ if (hasError) { + + diff --git a/backend/src/modules/iit-manager/test-patient-wechat-url-verify.ts b/backend/src/modules/iit-manager/test-patient-wechat-url-verify.ts index 0f009338..13258e79 100644 --- a/backend/src/modules/iit-manager/test-patient-wechat-url-verify.ts +++ b/backend/src/modules/iit-manager/test-patient-wechat-url-verify.ts @@ -175,3 +175,5 @@ async function testUrlVerification() { + + diff --git a/backend/src/modules/iit-manager/test-redcap-query-from-db.ts b/backend/src/modules/iit-manager/test-redcap-query-from-db.ts index 873872d2..f676349b 100644 --- a/backend/src/modules/iit-manager/test-redcap-query-from-db.ts +++ b/backend/src/modules/iit-manager/test-redcap-query-from-db.ts @@ -256,3 +256,5 @@ main().catch((error) => { + + diff --git a/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 b/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 index a6904604..e897aa23 100644 --- a/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 +++ b/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 @@ -140,3 +140,5 @@ Write-Host "" + + diff --git a/backend/src/modules/iit-manager/types/index.ts b/backend/src/modules/iit-manager/types/index.ts index dced30cb..f42ce43a 100644 --- a/backend/src/modules/iit-manager/types/index.ts +++ b/backend/src/modules/iit-manager/types/index.ts @@ -233,3 +233,5 @@ export interface CachedProtocolRules { + + diff --git a/backend/src/modules/pkb/routes/health.ts b/backend/src/modules/pkb/routes/health.ts index 8820e92a..3b0f7841 100644 --- a/backend/src/modules/pkb/routes/health.ts +++ b/backend/src/modules/pkb/routes/health.ts @@ -46,3 +46,5 @@ export default async function healthRoutes(fastify: FastifyInstance) { + + diff --git a/backend/src/modules/rvw/__tests__/api.http b/backend/src/modules/rvw/__tests__/api.http index 21d9b43a..2d628423 100644 --- a/backend/src/modules/rvw/__tests__/api.http +++ b/backend/src/modules/rvw/__tests__/api.http @@ -124,3 +124,5 @@ Content-Type: application/json GET {{baseUrl}}/api/v1/review/tasks/{{taskId}}/report Content-Type: application/json + + diff --git a/backend/src/modules/rvw/__tests__/test-api.ps1 b/backend/src/modules/rvw/__tests__/test-api.ps1 index 5cb35c86..ca59c9c3 100644 --- a/backend/src/modules/rvw/__tests__/test-api.ps1 +++ b/backend/src/modules/rvw/__tests__/test-api.ps1 @@ -109,3 +109,5 @@ Write-Host " - 查看报告: GET $BaseUrl/api/v2/rvw/tasks/{taskId}/report" -Fo Write-Host " - 批量运行: POST $BaseUrl/api/v2/rvw/tasks/batch/run" -ForegroundColor Gray Write-Host " - 删除任务: DELETE $BaseUrl/api/v2/rvw/tasks/{taskId}" -ForegroundColor Gray + + diff --git a/backend/src/modules/rvw/controllers/reviewController.ts b/backend/src/modules/rvw/controllers/reviewController.ts index 99572f42..8c8aeae9 100644 --- a/backend/src/modules/rvw/controllers/reviewController.ts +++ b/backend/src/modules/rvw/controllers/reviewController.ts @@ -136,6 +136,8 @@ export async function createTask( /** * 运行审查(选择智能体) * POST /api/v2/rvw/tasks/:taskId/run + * + * ✅ Platform-Only架构:返回 jobId 供前端轮询 */ export async function runReview( request: FastifyRequest<{ @@ -151,11 +153,16 @@ export async function runReview( logger.info('[RVW:Controller] 运行审查', { taskId, agents }); - await reviewService.runReview({ taskId, agents, userId }); + // ✅ 返回 jobId(Platform-Only架构) + const { jobId } = await reviewService.runReview({ taskId, agents, userId }); return reply.send({ success: true, message: '审查任务已启动', + data: { + taskId, + jobId, // ✅ 供前端轮询状态 + }, }); } catch (error) { logger.error('[RVW:Controller] 运行审查失败', { diff --git a/backend/src/modules/rvw/index.ts b/backend/src/modules/rvw/index.ts index 0ea0ddc5..c27b7c53 100644 --- a/backend/src/modules/rvw/index.ts +++ b/backend/src/modules/rvw/index.ts @@ -23,3 +23,5 @@ export * from './types/index.js'; // 导出工具函数 export * from './services/utils.js'; + + diff --git a/backend/src/modules/rvw/routes/index.ts b/backend/src/modules/rvw/routes/index.ts index 7c7ef7ca..a610d0a2 100644 --- a/backend/src/modules/rvw/routes/index.ts +++ b/backend/src/modules/rvw/routes/index.ts @@ -44,3 +44,5 @@ export default async function rvwRoutes(fastify: FastifyInstance) { fastify.post('/tasks/batch/run', reviewController.batchRunReview); } + + diff --git a/backend/src/modules/rvw/services/editorialService.ts b/backend/src/modules/rvw/services/editorialService.ts index baeae7f6..b9080053 100644 --- a/backend/src/modules/rvw/services/editorialService.ts +++ b/backend/src/modules/rvw/services/editorialService.ts @@ -68,3 +68,5 @@ export async function reviewEditorialStandards( } } + + diff --git a/backend/src/modules/rvw/services/methodologyService.ts b/backend/src/modules/rvw/services/methodologyService.ts index 1844d57c..26f00499 100644 --- a/backend/src/modules/rvw/services/methodologyService.ts +++ b/backend/src/modules/rvw/services/methodologyService.ts @@ -68,3 +68,5 @@ export async function reviewMethodology( } } + + diff --git a/backend/src/modules/rvw/services/reviewService.ts b/backend/src/modules/rvw/services/reviewService.ts index b076bc33..148f5c8b 100644 --- a/backend/src/modules/rvw/services/reviewService.ts +++ b/backend/src/modules/rvw/services/reviewService.ts @@ -15,6 +15,7 @@ import { prisma } from '../../../config/database.js'; import { extractionClient } from '../../../common/document/ExtractionClient.js'; import { ModelType } from '../../../common/llm/adapters/types.js'; import { logger } from '../../../common/logging/index.js'; +import { jobQueue } from '../../../common/jobs/index.js'; import { Prisma } from '@prisma/client'; import { AgentType, @@ -124,9 +125,15 @@ async function extractDocumentAsync(taskId: string, file: Buffer, filename: stri /** * 运行审查(核心功能) - * 支持选择1个或2个智能体 + * + * ✅ Platform-Only架构: + * - 使用 pg-boss 队列处理审查任务 + * - API 立即返回 jobId + * - 后台 Worker 执行实际审查 + * + * @returns jobId 供前端轮询状态 */ -export async function runReview(params: RunReviewParams): Promise { +export async function runReview(params: RunReviewParams): Promise<{ jobId: string }> { const { taskId, agents, userId } = params; // 验证智能体选择 @@ -149,91 +156,38 @@ export async function runReview(params: RunReviewParams): Promise { throw new Error('文档尚未提取完成,请稍后再试'); } - const startTime = Date.now(); + // 更新任务状态为reviewing + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'reviewing', + selectedAgents: agents, + startedAt: new Date(), + // 清除之前的结果(如果重新运行) + editorialReview: Prisma.JsonNull, + methodologyReview: Prisma.JsonNull, + overallScore: null, + errorMessage: null, + }, + }); - try { - // 更新任务状态 - await prisma.reviewTask.update({ - where: { id: taskId }, - data: { - status: 'reviewing', - startedAt: new Date(), - // 清除之前的结果(如果重新运行) - editorialReview: Prisma.JsonNull, - methodologyReview: Prisma.JsonNull, - overallScore: null, - errorMessage: null, - }, - }); + // ✅ 推送任务到 pg-boss 队列(Platform-Only架构) + const job = await jobQueue.push('rvw_review_task', { + taskId, + userId, + agents, + extractedText: task.extractedText, + modelType: (task.modelUsed || 'deepseek-v3') as ModelType, + }); - logger.info('[RVW] 开始审查', { taskId, agents }); + logger.info('[RVW] 审查任务已推送到队列', { + taskId, + jobId: job.id, + agents, + }); - // 获取模型类型 - const modelType = (task.modelUsed || 'deepseek-v3') as ModelType; - - // 运行选中的智能体 - let editorialResult: EditorialReview | null = null; - let methodologyResult: MethodologyReview | null = null; - - if (agents.includes('editorial')) { - logger.info('[RVW] 运行稿约规范性智能体', { taskId }); - editorialResult = await reviewEditorialStandards(task.extractedText, modelType); - } - - if (agents.includes('methodology')) { - logger.info('[RVW] 运行方法学智能体', { taskId }); - methodologyResult = await reviewMethodology(task.extractedText, modelType); - } - - // 计算综合分数 - const editorialScore = editorialResult?.overall_score ?? null; - const methodologyScore = methodologyResult?.overall_score ?? null; - const overallScore = calculateOverallScore(editorialScore, methodologyScore, agents); - - // 计算耗时 - const endTime = Date.now(); - const durationSeconds = Math.floor((endTime - startTime) / 1000); - - // 更新任务结果(使用新字段) - await prisma.reviewTask.update({ - where: { id: taskId }, - data: { - status: 'completed', - editorialReview: editorialResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull, - methodologyReview: methodologyResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull, - overallScore, - // 🆕 使用新字段存储摘要信息 - editorialScore: editorialScore, - methodologyStatus: getMethodologyStatus(methodologyResult), - completedAt: new Date(), - durationSeconds, - }, - }); - - logger.info('[RVW] 审查完成', { - taskId, - agents, - editorialScore, - methodologyScore, - overallScore, - durationSeconds, - }); - } catch (error) { - logger.error('[RVW] 审查失败', { - taskId, - error: error instanceof Error ? error.message : 'Unknown error' - }); - - await prisma.reviewTask.update({ - where: { id: taskId }, - data: { - status: 'failed', - errorMessage: error instanceof Error ? error.message : 'Review failed', - }, - }); - - throw error; - } + // ✅ 立即返回 jobId(不阻塞请求) + return { jobId: job.id }; } /** @@ -324,6 +278,7 @@ export async function getTaskList(params: TaskListParams): Promise('rvw_review_task', async (job: Job) => { + const { taskId, userId, agents, extractedText, modelType } = job.data; + const startTime = Date.now(); + + logger.info('[reviewWorker] Processing review job', { + jobId: job.id, + taskId, + userId, + agents, + textLength: extractedText.length, + }); + + console.log(`\n📝 处理审查任务`); + console.log(` Job ID: ${job.id}`); + console.log(` Task ID: ${taskId}`); + console.log(` 智能体: ${agents.join(', ')}`); + console.log(` 文本长度: ${extractedText.length} 字符`); + + try { + // ======================================== + // 1. 运行选中的智能体 + // ======================================== + let editorialResult: EditorialReview | null = null; + let methodologyResult: MethodologyReview | null = null; + + if (agents.includes('editorial')) { + // 更新进度状态 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { status: 'reviewing_editorial' }, + }); + + logger.info('[reviewWorker] Running editorial review', { taskId }); + console.log(' 🔍 运行稿约规范性智能体...'); + + editorialResult = await reviewEditorialStandards(extractedText, modelType); + + logger.info('[reviewWorker] Editorial review completed', { + taskId, + score: editorialResult?.overall_score, + }); + console.log(` ✅ 稿约规范性完成,得分: ${editorialResult?.overall_score}`); + } + + if (agents.includes('methodology')) { + // 更新进度状态 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { status: 'reviewing_methodology' }, + }); + + logger.info('[reviewWorker] Running methodology review', { taskId }); + console.log(' 🔬 运行方法学智能体...'); + + methodologyResult = await reviewMethodology(extractedText, modelType); + + logger.info('[reviewWorker] Methodology review completed', { + taskId, + score: methodologyResult?.overall_score, + }); + console.log(` ✅ 方法学评估完成,得分: ${methodologyResult?.overall_score}`); + } + + // ======================================== + // 2. 计算综合分数 + // ======================================== + const editorialScore = editorialResult?.overall_score ?? null; + const methodologyScore = methodologyResult?.overall_score ?? null; + const overallScore = calculateOverallScore(editorialScore, methodologyScore, agents); + + // 计算耗时 + const endTime = Date.now(); + const durationSeconds = Math.floor((endTime - startTime) / 1000); + + // ======================================== + // 3. 更新任务结果 + // ======================================== + logger.info('[reviewWorker] Updating task result', { taskId }); + + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'completed', + editorialReview: editorialResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull, + methodologyReview: methodologyResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull, + overallScore, + editorialScore: editorialScore, + methodologyScore: methodologyScore, + methodologyStatus: getMethodologyStatus(methodologyResult), + completedAt: new Date(), + durationSeconds, + }, + }); + + logger.info('[reviewWorker] ✅ Review completed', { + jobId: job.id, + taskId, + agents, + editorialScore, + methodologyScore, + overallScore, + durationSeconds, + }); + + console.log('\n✅ 审查完成:'); + console.log(` Task ID: ${taskId}`); + console.log(` 综合得分: ${overallScore}`); + console.log(` 耗时: ${durationSeconds}秒`); + + return { + taskId, + overallScore, + editorialScore, + methodologyScore, + durationSeconds, + success: true, + }; + } catch (error: any) { + logger.error('[reviewWorker] ❌ Review failed', { + jobId: job.id, + taskId, + error: error.message, + stack: error.stack, + }); + + console.error(`\n❌ 审查失败: ${error.message}`); + + // 更新任务状态为失败 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'failed', + errorMessage: error.message || 'Review failed', + }, + }); + + // 抛出错误,让 pg-boss 处理重试 + throw error; + } + }); + + logger.info('[reviewWorker] ✅ Worker registered: rvw_review_task'); +} + + diff --git a/backend/src/tests/README.md b/backend/src/tests/README.md index def1e294..1a23e3b9 100644 --- a/backend/src/tests/README.md +++ b/backend/src/tests/README.md @@ -411,6 +411,8 @@ SET session_replication_role = 'origin'; + + diff --git a/backend/src/tests/verify-test1-database.sql b/backend/src/tests/verify-test1-database.sql index ab529306..b9a03cdf 100644 --- a/backend/src/tests/verify-test1-database.sql +++ b/backend/src/tests/verify-test1-database.sql @@ -113,6 +113,8 @@ WHERE key = 'verify_test'; + + diff --git a/backend/src/tests/verify-test1-database.ts b/backend/src/tests/verify-test1-database.ts index 6e54755a..b47f1888 100644 --- a/backend/src/tests/verify-test1-database.ts +++ b/backend/src/tests/verify-test1-database.ts @@ -256,6 +256,8 @@ verifyDatabase() + + diff --git a/backend/src/types/global.d.ts b/backend/src/types/global.d.ts index 3cb6bc1b..fe78be02 100644 --- a/backend/src/types/global.d.ts +++ b/backend/src/types/global.d.ts @@ -46,6 +46,8 @@ export {} + + diff --git a/backend/sync-dc-database.ps1 b/backend/sync-dc-database.ps1 index f43fe2be..c903367e 100644 --- a/backend/sync-dc-database.ps1 +++ b/backend/sync-dc-database.ps1 @@ -69,6 +69,8 @@ Write-Host "✅ 完成!" -ForegroundColor Green + + diff --git a/backend/test-pkb-migration.http b/backend/test-pkb-migration.http index 49f2698f..161d6f3b 100644 --- a/backend/test-pkb-migration.http +++ b/backend/test-pkb-migration.http @@ -160,3 +160,5 @@ DELETE {{baseUrl}}/api/v2/pkb/knowledge/knowledge-bases/{{testKbId}} + + diff --git a/backend/test-tool-c-advanced-scenarios.mjs b/backend/test-tool-c-advanced-scenarios.mjs index 58f3890d..904e9dde 100644 --- a/backend/test-tool-c-advanced-scenarios.mjs +++ b/backend/test-tool-c-advanced-scenarios.mjs @@ -356,6 +356,8 @@ runAdvancedTests().catch(error => { + + diff --git a/backend/test-tool-c-day2.mjs b/backend/test-tool-c-day2.mjs index 9cb5a31a..21da50d6 100644 --- a/backend/test-tool-c-day2.mjs +++ b/backend/test-tool-c-day2.mjs @@ -422,6 +422,8 @@ runAllTests() + + diff --git a/backend/test-tool-c-day3.mjs b/backend/test-tool-c-day3.mjs index a6cc0a1a..47fa6035 100644 --- a/backend/test-tool-c-day3.mjs +++ b/backend/test-tool-c-day3.mjs @@ -380,6 +380,8 @@ runAllTests() + + diff --git a/deploy-to-sae.ps1 b/deploy-to-sae.ps1 index 00552f9d..efb2d032 100644 --- a/deploy-to-sae.ps1 +++ b/deploy-to-sae.ps1 @@ -164,6 +164,8 @@ Set-Location .. + + diff --git a/docs/00-系统总体设计/00-系统当前状态与开发指南.md b/docs/00-系统总体设计/00-系统当前状态与开发指南.md index 8ed4df2b..590a9f96 100644 --- a/docs/00-系统总体设计/00-系统当前状态与开发指南.md +++ b/docs/00-系统总体设计/00-系统当前状态与开发指南.md @@ -1,10 +1,10 @@ # AIclinicalresearch 系统当前状态与开发指南 -> **文档版本:** v2.9 +> **文档版本:** v3.0 > **创建日期:** 2025-11-28 > **维护者:** 开发团队 -> **最后更新:** 2026-01-07 -> **重大进展:** 🎉 **RVW稿件审查模块开发完成(85%)!** - 后端迁移+数据库扩展+前端重构全部完成 +> **最后更新:** 2026-01-10 +> **重大进展:** 🎉 **RVW稿件审查模块开发完成(90%)!** - 后端迁移+数据库扩展+前端重构+Word导出全部完成 > **部署状态:** ✅ 生产环境运行中 | 公网地址:http://8.140.53.236/ > **文档目的:** 快速了解系统当前状态,为新AI助手提供上下文 @@ -45,7 +45,7 @@ | **IIT** | IIT Manager Agent | AI驱动IIT研究助手 - 智能质控+REDCap集成 | ⭐⭐⭐⭐⭐ | 🎉 **Phase 1.5完成(60%)- AI对话+REDCap数据集成** | **P0** | | **SSA** | 智能统计分析 | 队列/预测模型/RCT分析 | ⭐⭐⭐⭐⭐ | 📋 规划中 | P2 | | **ST** | 统计分析工具 | 100+轻量化统计工具 | ⭐⭐⭐⭐ | 📋 规划中 | P2 | -| **RVW** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | ✅ **开发完成(85%)** | P3 | +| **RVW** | 稿件审查系统 | 方法学评估、审稿流程、Word导出 | ⭐⭐⭐⭐ | ✅ **开发完成(90%)** | P3 | --- @@ -815,7 +815,7 @@ npm run dev # http://localhost:3000 - **总计**:约 85,000 行 ### 模块完成度 -- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(85%,Phase 1-3完成) +- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(90%,Phase 1-5完成,支持Word导出) - 🚧 **开发中**:PKB(90%,核心功能完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成) - 📋 **未开始**:SSA、ST @@ -953,8 +953,8 @@ if (items.length >= 50) { --- -**文档版本**:v2.9 -**最后更新**:2026-01-07 +**文档版本**:v3.0 +**最后更新**:2026-01-10 **下次更新**:RVW生产环境部署 或 ASL智能文献筛选模块启动 --- @@ -1020,7 +1020,7 @@ if (items.length >= 50) { --- -**RVW稿件审查模块开发完成(2026-01-07)**: +**RVW稿件审查模块开发完成(2026-01-07 ~ 2026-01-10)**: ### Phase 1:后端模块迁移与扩展 - ✅ 创建 `backend/src/modules/rvw/` 模块结构 @@ -1029,24 +1029,36 @@ if (items.length >= 50) { - ✅ 实现批量运行API(batchRunReviewTasks) - ✅ 替换 console.log 为 logger 服务 - ✅ 注册 v2 API路由(/api/v2/rvw) +- ✅ 实现 pg-boss 异步任务处理(reviewWorker) ### Phase 2:数据库字段扩展 -- ✅ 添加 selectedAgents、editorialScore、methodologyStatus 字段 +- ✅ 添加 selectedAgents、editorialScore、methodologyScore、methodologyStatus 字段 - ✅ 添加 picoExtract、isArchived、archivedAt 字段 - ✅ 使用 prisma db push 同步到数据库 ### Phase 3:前端重构(frontend-v2) -- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行) -- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作) -- ✅ 实现 ReportDetail 组件(双标签页切换) +- ✅ 创建 `frontend-v2/src/modules/rvw/` 完整模块目录结构 +- ✅ 实现 Dashboard 页面(任务列表、筛选、批量操作) +- ✅ 实现 TaskDetail 组件(审稿进度条、实时状态轮询) +- ✅ 实现 EditorialReport/MethodologyReport 组件 - ✅ 实现 AgentModal 组件(智能体选择弹窗) - ✅ 注册到 moduleRegistry.ts -- ✅ 添加顶部导航"预审稿"入口 + +### Phase 4:集成测试与Bug修复(2026-01-10) +- ✅ 修复方法学分数不显示问题 +- ✅ 修复只选方法学时详情页不显示报告问题 +- ✅ 完整测试单智能体和双智能体审稿流程 + +### Phase 5:报告导出(2026-01-10) +- ✅ 安装 docx 和 file-saver 库 +- ✅ 实现 Word 文档导出功能 +- ✅ 支持结构化报告(标题、基本信息、稿约规范性、方法学评估) **技术亮点**: - 🔥 **新旧API兼容**:v1 + v2 API同时运行 - 🔥 **智能体可选**:用户可选择运行稿约规范性/方法学/两者 -- 🔥 **批量操作**:支持多选任务批量运行 +- 🔥 **异步任务处理**:使用 pg-boss 队列处理长时间审稿任务 +- 🔥 **Word导出**:使用 docx 库生成专业格式的审稿报告 - 🔥 **云原生改造**:使用 logger 服务,遵循开发规范 -**模块进度**:85%完成(Phase 1-3) +**模块进度**:90%完成(Phase 1-5) diff --git a/docs/02-通用能力层/Postgres-Only异步任务处理指南.md b/docs/02-通用能力层/Postgres-Only异步任务处理指南.md index e3ecac8e..bf78b96b 100644 --- a/docs/02-通用能力层/Postgres-Only异步任务处理指南.md +++ b/docs/02-通用能力层/Postgres-Only异步任务处理指南.md @@ -606,6 +606,8 @@ async saveProcessedData(recordId, newData) { + + diff --git a/docs/02-通用能力层/通用能力层技术债务清单.md b/docs/02-通用能力层/通用能力层技术债务清单.md index 0c60b246..d3250420 100644 --- a/docs/02-通用能力层/通用能力层技术债务清单.md +++ b/docs/02-通用能力层/通用能力层技术债务清单.md @@ -793,6 +793,8 @@ export const AsyncProgressBar: React.FC = ({ + + diff --git a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md index 0cc773a2..23c3b611 100644 --- a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md +++ b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md @@ -1286,6 +1286,8 @@ interface FulltextScreeningResult { + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md index 3b2168d3..bd7870ec 100644 --- a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md +++ b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md @@ -400,6 +400,8 @@ GET /api/v1/asl/fulltext-screening/tasks/:taskId/export + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md index ebf6155b..083d73c9 100644 --- a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md +++ b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md @@ -343,6 +343,8 @@ Linter错误:0个 + + diff --git a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-11-23_Day5_全文复筛API开发.md b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-11-23_Day5_全文复筛API开发.md index 2c7dcb0f..d7150d36 100644 --- a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-11-23_Day5_全文复筛API开发.md +++ b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-11-23_Day5_全文复筛API开发.md @@ -502,6 +502,8 @@ Failed to open file '\\tmp\\extraction_service\\temp_10000_test.pdf' + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md index 3f2ccb34..e65942b8 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md @@ -568,6 +568,8 @@ df['creatinine'] = pd.to_numeric(df['creatinine'], errors='coerce') + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md index c920794a..8a42b82b 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md @@ -406,6 +406,8 @@ npm run dev + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md index accfb412..c0e8b7ac 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md @@ -983,6 +983,8 @@ export const aiController = new AIController(); + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md index eb261ebf..9b2531a7 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md @@ -1317,6 +1317,8 @@ npm install react-markdown + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md index 32e8e879..8f1b484b 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md @@ -225,6 +225,8 @@ FMA___基线 | FMA___1个月 | FMA___2个月 + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md index 92699755..35cb759c 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md @@ -383,6 +383,8 @@ formula = "FMA总分(0-100) / 100" + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md index f149e2f5..f9151bc4 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md @@ -217,6 +217,8 @@ async handleFillnaMice(request, reply) { + + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md index 40b2c4b1..f6f53910 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md @@ -189,6 +189,8 @@ method: 'mean' | 'median' | 'mode' | 'constant' | 'ffill' | 'bfill' + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md index 01b63f44..5be05b23 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md @@ -339,6 +339,8 @@ Changes: + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md index 18abb989..ee7cda89 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md @@ -411,6 +411,8 @@ cd path; command + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md index 7956a0fe..66a54187 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md @@ -640,6 +640,8 @@ import { logger } from '../../../../common/logging/index.js'; + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md index 577c2796..7bdeacef 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md @@ -644,6 +644,8 @@ Content-Length: 45234 + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md index ea7bc953..9d146345 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md @@ -296,6 +296,8 @@ Response: + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5_Ant-Design-X重构完成.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5_Ant-Design-X重构完成.md index 464445cc..5a665575 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5_Ant-Design-X重构完成.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5_Ant-Design-X重构完成.md @@ -449,6 +449,8 @@ Response: + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md index a4594d3e..b35f03e1 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md @@ -443,6 +443,8 @@ import { ChatContainer } from '@/shared/components/Chat'; + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md index 9c196435..3a08dac3 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md @@ -353,6 +353,8 @@ const initialMessages = defaultMessages.length > 0 ? defaultMessages : [{ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md index 535b1461..102bd620 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md @@ -393,6 +393,8 @@ python main.py + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md index fcc4abd9..f13f6e3d 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md @@ -641,6 +641,8 @@ http://localhost:5173/data-cleaning/tool-c + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md index 3586040b..3d345869 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md @@ -251,6 +251,8 @@ Day 5 (6-8小时): + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md index 7bb00493..e8ab82bd 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md @@ -429,6 +429,8 @@ Docs: docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建 + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md index 03e87640..d5ed4e3b 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md @@ -404,6 +404,8 @@ const mockAssets: Asset[] = [ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase2-ToolB-Step1-2开发完成-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase2-ToolB-Step1-2开发完成-2025-12-03.md index abaf8d63..a410e4bb 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase2-ToolB-Step1-2开发完成-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase2-ToolB-Step1-2开发完成-2025-12-03.md @@ -388,6 +388,8 @@ frontend-v2/src/modules/dc/ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md index 2e1e74b8..89dc6790 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md @@ -348,6 +348,8 @@ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Tool-B-MVP完成总结-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Tool-B-MVP完成总结-2025-12-03.md index ba0009f7..5f20e9f7 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Tool-B-MVP完成总结-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Tool-B-MVP完成总结-2025-12-03.md @@ -302,6 +302,8 @@ ConflictDetectionService // 冲突检测(字段级对比) + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md index 47667652..aae17e41 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md @@ -351,6 +351,8 @@ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-Round2-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-Round2-2025-12-03.md index 53ffa2b2..5b12b9f8 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-Round2-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-Round2-2025-12-03.md @@ -314,6 +314,8 @@ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md index c95557e5..df991fb0 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md @@ -378,6 +378,8 @@ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md index 47c0974c..7da3ad40 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md @@ -466,6 +466,8 @@ Tool B后端代码**100%复用**了平台通用能力层,无任何重复开发 + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md index c282ffc4..eb870d6e 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md @@ -312,6 +312,8 @@ + + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md index c01abcc2..c43a4cd4 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md @@ -243,6 +243,8 @@ $ node scripts/check-dc-tables.mjs + + diff --git a/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md b/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md index 69882fb4..33b9da78 100644 --- a/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md +++ b/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md @@ -476,6 +476,8 @@ ${fields.map((f, i) => `${i + 1}. ${f.name}:${f.desc}`).join('\n')} + + diff --git a/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md b/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md index ce63eceb..c225194c 100644 --- a/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md +++ b/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md @@ -684,3 +684,5 @@ private async processMessageAsync(xmlData: any) { + + diff --git a/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md b/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md index 07d9b798..7985febd 100644 --- a/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md +++ b/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md @@ -1078,3 +1078,5 @@ async function testIntegration() { + + diff --git a/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md b/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md index 312f29f5..fa58cdf9 100644 --- a/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md +++ b/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md @@ -219,3 +219,5 @@ Content-Type: application/json + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/2026-01-04-Dify知识库集成开发记录.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/2026-01-04-Dify知识库集成开发记录.md index 39cc08fd..fb284b99 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/2026-01-04-Dify知识库集成开发记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/2026-01-04-Dify知识库集成开发记录.md @@ -639,3 +639,5 @@ REDCap API: exportRecords success { recordCount: 1 } + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md index 35e9553d..1938d921 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md @@ -645,3 +645,5 @@ backend/src/modules/iit-manager/ + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md index 39f4693c..8680fc22 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md @@ -795,3 +795,5 @@ CREATE TABLE iit_schema.wechat_tokens ( + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md index 83a33311..5c655128 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md @@ -552,3 +552,5 @@ Day 3 的开发工作虽然遇到了多个技术问题,但最终成功完成 + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Phase1.5-AI对话集成REDCap完成记录.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Phase1.5-AI对话集成REDCap完成记录.md index 46970b7f..ae5c0f28 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Phase1.5-AI对话集成REDCap完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Phase1.5-AI对话集成REDCap完成记录.md @@ -319,3 +319,5 @@ AI: "出生日期:2017-01-04 + + diff --git a/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md b/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md index 82689aba..a4704288 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md @@ -263,3 +263,5 @@ Day 4: REDCap EM(Webhook推送)← 作为增强,而非核心 + + diff --git a/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md b/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md index b16da9e5..089b6191 100644 --- a/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md +++ b/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md @@ -677,3 +677,5 @@ const answer = `根据研究方案[1]和CRF表格[2],纳入标准包括: + + diff --git a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md index 3f330d23..3ca2ce65 100644 --- a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md +++ b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md @@ -357,3 +357,5 @@ const newResults = resultsData.map((docResult: any) => ({ 4. 完善错误处理和用户反馈 + + diff --git a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md index 7170499a..745be27d 100644 --- a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md +++ b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md @@ -230,3 +230,5 @@ const chatApi = axios.create({ + + diff --git a/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md b/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md index 9a6c3c69..c0157584 100644 --- a/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md +++ b/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md @@ -1,10 +1,10 @@ # RVW稿件审查模块 - 当前状态与开发指南 -> **文档版本:** v3.0 +> **文档版本:** v3.1 > **创建日期:** 2026-01-07 -> **最后更新:** 2026-01-07 +> **最后更新:** 2026-01-10 > **维护者:** 开发团队 -> **当前状态:** ✅ **Phase 1-3 完成,前后端功能可用** +> **当前状态:** ✅ **Phase 1-4 完成,前后端功能可用,报告导出完成** > **文档目的:** 快速了解RVW模块状态,为新AI助手提供上下文 --- @@ -20,7 +20,7 @@ | **商业价值** | ⭐⭐⭐⭐⭐ 极高 | | **独立性** | ⭐⭐⭐⭐⭐ 极高(用户群完全不同) | | **目标用户** | 期刊初审编辑 | -| **开发状态** | ✅ **核心功能100%完成,已集成到 frontend-v2** | +| **开发状态** | ✅ **核心功能100%完成,支持Word导出,已集成到 frontend-v2** | ### 核心目标 @@ -77,15 +77,28 @@ backend/src/modules/rvw/ │ ├── editorialService.ts # 稿约规范性评估 │ ├── methodologyService.ts # 方法学评估 │ └── utils.ts # 工具函数 +├── workers/ +│ └── reviewWorker.ts # pg-boss异步任务处理 ├── types/index.ts # TypeScript类型定义 └── __tests__/ # API测试脚本 前端(✅ 已完成): frontend-v2/src/modules/rvw/ -└── index.tsx # 完整模块(~503行) - ├── Dashboard组件 # 审稿工作台(宽表、筛选、批量操作) - ├── ReportDetail组件 # 报告详情(双标签页切换) - └── AgentModal组件 # 智能体选择弹窗 +├── index.tsx # 模块入口 +├── api/index.ts # API接口 +├── types/index.ts # 类型定义 +├── styles/index.css # 样式文件 +├── pages/ +│ └── Dashboard.tsx # 审稿工作台(任务列表、筛选) +└── components/ + ├── Header.tsx # 页头(上传按钮) + ├── Sidebar.tsx # 侧边栏导航 + ├── TaskTable.tsx # 任务列表表格 + ├── TaskDetail.tsx # 任务详情(进度条+报告+Word导出) + ├── EditorialReport.tsx # 稿约规范性报告 + ├── MethodologyReport.tsx # 方法学评估报告 + ├── AgentModal.tsx # 智能体选择弹窗 + └── ScoreRing.tsx # 评分环组件 旧版本(保留兼容): backend/src/legacy/ @@ -112,11 +125,12 @@ backend/prompts/ | Phase 1 | 后端模块迁移与扩展 | ✅ 已完成 | 2026-01-07 | | Phase 2 | 数据库字段扩展 | ✅ 已完成 | 2026-01-07 | | Phase 3 | 前端重构(frontend-v2) | ✅ 已完成 | 2026-01-07 | -| Phase 4 | 集成测试 | 🔧 基本通过 | 2026-01-07 | -| Phase 5 | 系统设置与归档 | ⏸️ 暂缓 | - | -| **总计** | - | **85%** | - | +| Phase 4 | 集成测试与Bug修复 | ✅ 已完成 | 2026-01-10 | +| Phase 5 | 报告导出(Word) | ✅ 已完成 | 2026-01-10 | +| Phase 6 | 系统设置与归档 | ⏸️ 暂缓 | - | +| **总计** | - | **90%** | - | -### Phase 1-3 完成内容 +### Phase 1-5 完成内容 **后端(Phase 1):** - ✅ 创建 `backend/src/modules/rvw/` 模块结构 @@ -125,22 +139,37 @@ backend/prompts/ - ✅ 实现批量运行API(batchRunReviewTasks) - ✅ 替换 console.log 为 logger 服务 - ✅ 注册 v2 API路由(/api/v2/rvw) +- ✅ 实现 pg-boss 异步任务处理(reviewWorker) **数据库(Phase 2):** - ✅ 添加 selectedAgents 字段(String[]) - ✅ 添加 editorialScore 字段(Float?) +- ✅ 添加 methodologyScore 字段(Float?)- 2026-01-10新增 - ✅ 添加 methodologyStatus 字段(String?) - ✅ 添加 picoExtract 字段(Json?) - ✅ 添加 isArchived、archivedAt 字段(归档支持) - ✅ 使用 prisma db push 同步到数据库 **前端(Phase 3):** -- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行) -- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作) -- ✅ 实现 ReportDetail 组件(双标签页:稿约规范性/方法学) +- ✅ 创建 `frontend-v2/src/modules/rvw/` 模块目录结构 +- ✅ 实现 Dashboard 页面(任务列表、筛选、批量操作) +- ✅ 实现 TaskDetail 组件(审稿进度条、实时状态轮询) +- ✅ 实现 EditorialReport 组件(稿约规范性评估报告) +- ✅ 实现 MethodologyReport 组件(方法学评估报告) - ✅ 实现 AgentModal 组件(智能体选择弹窗) - ✅ 注册到 moduleRegistry.ts -- ✅ 添加顶部导航入口 + +**集成测试与Bug修复(Phase 4):** +- ✅ 修复方法学分数不显示问题(列表页显示"warn"而不是分数) +- ✅ 修复只选方法学时详情页不显示报告问题 +- ✅ 修复 activeTab 默认值导致的显示问题 +- ✅ 完整测试单智能体和双智能体审稿流程 + +**报告导出(Phase 5):** +- ✅ 安装 docx 和 file-saver 库 +- ✅ 实现 Word 文档导出功能 +- ✅ 支持结构化报告(标题、基本信息表格、稿约规范性、方法学评估) +- ✅ 问题和建议使用不同颜色标记 详细任务清单见:[RVW模块迁移计划](./04-开发计划/RVW模块迁移计划.md) @@ -287,8 +316,8 @@ Content-Type: multipart/form-data | 问题 | 当前 | 目标 | 优先级 | |------|------|------|--------| | Schema位置 | public | review_schema | P2 | -| 任务处理 | 直接异步 | jobQueue | P2 | -| 报告导出 | 基础版 | PDF优化 | P3 | +| 任务处理 | ✅ pg-boss | - | ✅ 已完成 | +| 报告导出 | ✅ Word (.docx) | PDF优化 | P3 | --- @@ -308,7 +337,7 @@ Content-Type: multipart/form-data ## 🚀 未来规划 -### ✅ 已完成(2026-01-07) +### ✅ 已完成(2026-01-07 ~ 2026-01-10) - [x] 架构迁移到 modules/rvw(后端) - [x] 架构迁移到 modules/rvw(前端 frontend-v2) @@ -316,11 +345,14 @@ Content-Type: multipart/form-data - [x] 云原生改造(logger服务) - [x] v2 API 路由注册 - [x] 数据库字段扩展 +- [x] pg-boss异步任务处理(2026-01-10) +- [x] Word报告导出(2026-01-10) +- [x] 方法学分数显示修复(2026-01-10) +- [x] 单智能体审稿显示修复(2026-01-10) ### 后续版本 - [ ] Schema迁移到 review_schema -- [ ] 任务队列改造(jobQueue) - [ ] PDF报告导出优化 - [ ] PICO卡片UI实现 - [ ] 历史归档UI实现 @@ -332,7 +364,7 @@ Content-Type: multipart/form-data --- -**文档版本:** v3.0 -**最后更新:** 2026-01-07 -**当前状态:** ✅ Phase 1-3 完成,模块可用 +**文档版本:** v3.1 +**最后更新:** 2026-01-10 +**当前状态:** ✅ Phase 1-5 完成,模块可用,支持Word导出 **下一步:** 生产环境部署测试 或 Schema隔离迁移 diff --git a/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md b/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md index 0fd4d254..5bd1af2a 100644 --- a/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md +++ b/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md @@ -767,3 +767,5 @@ docker exec redcap-apache php /tmp/create-redcap-password.php + + diff --git a/docs/03-业务模块/Redcap/README.md b/docs/03-业务模块/Redcap/README.md index 58eaa722..af78c913 100644 --- a/docs/03-业务模块/Redcap/README.md +++ b/docs/03-业务模块/Redcap/README.md @@ -149,3 +149,5 @@ AIclinicalresearch/redcap-docker-dev/ + + diff --git a/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md b/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md index c91bf6e2..eed37039 100644 --- a/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md +++ b/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md @@ -883,6 +883,8 @@ ACR镜像仓库: + + diff --git a/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md b/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md index d92aaa9a..e356bd39 100644 --- a/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md +++ b/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md @@ -1370,6 +1370,8 @@ SAE应用配置: + + diff --git a/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md b/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md index 2897ec8d..933d0095 100644 --- a/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md +++ b/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md @@ -1186,6 +1186,8 @@ docker exec -e PGPASSWORD="密码" ai-clinical-postgres psql -h RDS地址 -U air + + diff --git a/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md b/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md index b1f1713c..02f44aed 100644 --- a/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md +++ b/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md @@ -597,6 +597,8 @@ scripts/*.ts + + diff --git a/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md b/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md index 460314fd..a8a3b626 100644 --- a/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md +++ b/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md @@ -285,6 +285,8 @@ Node.js后端部署成功后: + + diff --git a/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md b/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md index aaafe37a..25780b41 100644 --- a/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md +++ b/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md @@ -508,6 +508,8 @@ Node.js后端 (SAE) ← http://172.17.173.88:3001 + + diff --git a/docs/05-部署文档/13-Node.js后端-镜像修复记录.md b/docs/05-部署文档/13-Node.js后端-镜像修复记录.md index 56b52eb1..c0742026 100644 --- a/docs/05-部署文档/13-Node.js后端-镜像修复记录.md +++ b/docs/05-部署文档/13-Node.js后端-镜像修复记录.md @@ -223,6 +223,8 @@ curl http://localhost:3001/health + + diff --git a/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md b/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md index 752ea463..17c491a2 100644 --- a/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md +++ b/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md @@ -261,6 +261,8 @@ npm run dev + + diff --git a/docs/05-部署文档/16-前端Nginx-部署成功总结.md b/docs/05-部署文档/16-前端Nginx-部署成功总结.md index 76157e2b..eae24db8 100644 --- a/docs/05-部署文档/16-前端Nginx-部署成功总结.md +++ b/docs/05-部署文档/16-前端Nginx-部署成功总结.md @@ -485,6 +485,8 @@ pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432 + + diff --git a/docs/05-部署文档/17-完整部署实战手册-2025版.md b/docs/05-部署文档/17-完整部署实战手册-2025版.md index b6f94697..8651f787 100644 --- a/docs/05-部署文档/17-完整部署实战手册-2025版.md +++ b/docs/05-部署文档/17-完整部署实战手册-2025版.md @@ -1813,6 +1813,8 @@ curl http://8.140.53.236/ + + diff --git a/docs/05-部署文档/18-部署文档使用指南.md b/docs/05-部署文档/18-部署文档使用指南.md index f7527638..58ef93ac 100644 --- a/docs/05-部署文档/18-部署文档使用指南.md +++ b/docs/05-部署文档/18-部署文档使用指南.md @@ -361,6 +361,8 @@ crpi-cd5ij4pjt65mweeo.cn-beijing.personal.cr.aliyuncs.com/ai-clinical/backend-se + + diff --git a/docs/05-部署文档/19-日常更新快速操作手册.md b/docs/05-部署文档/19-日常更新快速操作手册.md index a7ff4fe3..899ec292 100644 --- a/docs/05-部署文档/19-日常更新快速操作手册.md +++ b/docs/05-部署文档/19-日常更新快速操作手册.md @@ -683,6 +683,8 @@ docker login --username=gofeng117@163.com \ + + diff --git a/docs/05-部署文档/文档修正报告-20251214.md b/docs/05-部署文档/文档修正报告-20251214.md index 5c5af337..c6bd0d1f 100644 --- a/docs/05-部署文档/文档修正报告-20251214.md +++ b/docs/05-部署文档/文档修正报告-20251214.md @@ -494,6 +494,8 @@ NAT网关成本¥100/月,对初创团队是一笔开销 + + diff --git a/docs/07-运维文档/03-SAE环境变量配置指南.md b/docs/07-运维文档/03-SAE环境变量配置指南.md index 1ef1fd1c..86bedc76 100644 --- a/docs/07-运维文档/03-SAE环境变量配置指南.md +++ b/docs/07-运维文档/03-SAE环境变量配置指南.md @@ -399,6 +399,8 @@ curl http://你的SAE地址:3001/health + + diff --git a/docs/07-运维文档/05-Redis缓存与队列的区别说明.md b/docs/07-运维文档/05-Redis缓存与队列的区别说明.md index 2fd441d3..d2445ea0 100644 --- a/docs/07-运维文档/05-Redis缓存与队列的区别说明.md +++ b/docs/07-运维文档/05-Redis缓存与队列的区别说明.md @@ -731,6 +731,8 @@ const job = await queue.getJob(jobId); + + diff --git a/docs/07-运维文档/06-长时间任务可靠性分析.md b/docs/07-运维文档/06-长时间任务可靠性分析.md index 99234a70..9df9aab1 100644 --- a/docs/07-运维文档/06-长时间任务可靠性分析.md +++ b/docs/07-运维文档/06-长时间任务可靠性分析.md @@ -498,6 +498,8 @@ processLiteraturesInBackground(task.id, projectId, testLiteratures); + + diff --git a/docs/07-运维文档/07-Redis使用需求分析(按模块).md b/docs/07-运维文档/07-Redis使用需求分析(按模块).md index 55221aa0..4e8f5a48 100644 --- a/docs/07-运维文档/07-Redis使用需求分析(按模块).md +++ b/docs/07-运维文档/07-Redis使用需求分析(按模块).md @@ -975,6 +975,8 @@ ROI = (¥22,556 - ¥144) / ¥144 × 100% = 15,564% + + diff --git a/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md b/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md index 113c2930..96bb8b5e 100644 --- a/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md +++ b/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md @@ -1032,6 +1032,8 @@ Redis 实例:¥500/月 + + diff --git a/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md b/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md index 8d6db283..205d20e0 100644 --- a/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md +++ b/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md @@ -490,6 +490,8 @@ import { ChatContainer } from '@/shared/components/Chat'; + + diff --git a/docs/08-项目管理/PKB前端问题修复报告.md b/docs/08-项目管理/PKB前端问题修复报告.md index f77fc4d6..8ec70f8b 100644 --- a/docs/08-项目管理/PKB前端问题修复报告.md +++ b/docs/08-项目管理/PKB前端问题修复报告.md @@ -411,3 +411,5 @@ frontend-v2/src/modules/pkb/ + + diff --git a/docs/08-项目管理/PKB前端验证指南.md b/docs/08-项目管理/PKB前端验证指南.md index 9be00afb..0dfcb6e5 100644 --- a/docs/08-项目管理/PKB前端验证指南.md +++ b/docs/08-项目管理/PKB前端验证指南.md @@ -273,3 +273,5 @@ npm run dev + + diff --git a/docs/08-项目管理/PKB功能审查报告-阶段0.md b/docs/08-项目管理/PKB功能审查报告-阶段0.md index d31817b8..cb2e4a2f 100644 --- a/docs/08-项目管理/PKB功能审查报告-阶段0.md +++ b/docs/08-项目管理/PKB功能审查报告-阶段0.md @@ -788,3 +788,5 @@ AIA智能问答模块 + + diff --git a/docs/08-项目管理/PKB和RVW功能迁移计划.md b/docs/08-项目管理/PKB和RVW功能迁移计划.md index aba843e0..61dd966d 100644 --- a/docs/08-项目管理/PKB和RVW功能迁移计划.md +++ b/docs/08-项目管理/PKB和RVW功能迁移计划.md @@ -933,3 +933,5 @@ CREATE INDEX idx_rvw_tasks_created_at ON rvw_schema.review_tasks(created_at); + + diff --git a/docs/08-项目管理/PKB精细化优化报告.md b/docs/08-项目管理/PKB精细化优化报告.md index 144c6a1a..4469cf45 100644 --- a/docs/08-项目管理/PKB精细化优化报告.md +++ b/docs/08-项目管理/PKB精细化优化报告.md @@ -586,3 +586,5 @@ const typography = { + + diff --git a/docs/08-项目管理/PKB迁移-超级安全执行计划.md b/docs/08-项目管理/PKB迁移-超级安全执行计划.md index 0c8758bc..b465e2a8 100644 --- a/docs/08-项目管理/PKB迁移-超级安全执行计划.md +++ b/docs/08-项目管理/PKB迁移-超级安全执行计划.md @@ -898,3 +898,5 @@ app.use('/api/v1/knowledge', (req, res) => { + + diff --git a/docs/08-项目管理/PKB迁移-阶段1完成报告.md b/docs/08-项目管理/PKB迁移-阶段1完成报告.md index e0940edd..cd0d0985 100644 --- a/docs/08-项目管理/PKB迁移-阶段1完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段1完成报告.md @@ -212,3 +212,5 @@ rm -rf src/modules/pkb + + diff --git a/docs/08-项目管理/PKB迁移-阶段2完成报告.md b/docs/08-项目管理/PKB迁移-阶段2完成报告.md index b714f46c..ff5e2eda 100644 --- a/docs/08-项目管理/PKB迁移-阶段2完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段2完成报告.md @@ -387,3 +387,5 @@ GET /api/v2/pkb/batch-tasks/batch/templates + + diff --git a/docs/08-项目管理/PKB迁移-阶段2进行中.md b/docs/08-项目管理/PKB迁移-阶段2进行中.md index 15040c71..65e3e9a5 100644 --- a/docs/08-项目管理/PKB迁移-阶段2进行中.md +++ b/docs/08-项目管理/PKB迁移-阶段2进行中.md @@ -31,3 +31,5 @@ import pkbRoutes from './modules/pkb/routes/index.js'; + + diff --git a/docs/08-项目管理/PKB迁移-阶段3完成报告.md b/docs/08-项目管理/PKB迁移-阶段3完成报告.md index 11e11c13..82beea2b 100644 --- a/docs/08-项目管理/PKB迁移-阶段3完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段3完成报告.md @@ -300,3 +300,5 @@ backend/ + + diff --git a/docs/08-项目管理/PKB迁移-阶段4完成报告.md b/docs/08-项目管理/PKB迁移-阶段4完成报告.md index df5f447d..aebd26d1 100644 --- a/docs/08-项目管理/PKB迁移-阶段4完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段4完成报告.md @@ -511,3 +511,5 @@ const response = await fetch('/api/v2/pkb/batch-tasks/batch/execute', { + + diff --git a/extraction_service/.dockerignore b/extraction_service/.dockerignore index 3b57002a..27476da9 100644 --- a/extraction_service/.dockerignore +++ b/extraction_service/.dockerignore @@ -65,6 +65,8 @@ models/ + + diff --git a/extraction_service/operations/__init__.py b/extraction_service/operations/__init__.py index e24214e8..4983692a 100644 --- a/extraction_service/operations/__init__.py +++ b/extraction_service/operations/__init__.py @@ -53,6 +53,8 @@ __version__ = '1.0.0' + + diff --git a/extraction_service/operations/dropna.py b/extraction_service/operations/dropna.py index 404c9293..e342fd92 100644 --- a/extraction_service/operations/dropna.py +++ b/extraction_service/operations/dropna.py @@ -186,6 +186,8 @@ def get_missing_summary(df: pd.DataFrame) -> dict: + + diff --git a/extraction_service/operations/filter.py b/extraction_service/operations/filter.py index 4051cf46..8217f8ee 100644 --- a/extraction_service/operations/filter.py +++ b/extraction_service/operations/filter.py @@ -146,6 +146,8 @@ def apply_filter( + + diff --git a/extraction_service/operations/unpivot.py b/extraction_service/operations/unpivot.py index e91a0380..0223513f 100644 --- a/extraction_service/operations/unpivot.py +++ b/extraction_service/operations/unpivot.py @@ -310,6 +310,8 @@ def get_unpivot_preview( + + diff --git a/extraction_service/test_dc_api.py b/extraction_service/test_dc_api.py index 9dec4317..fb79cdab 100644 --- a/extraction_service/test_dc_api.py +++ b/extraction_service/test_dc_api.py @@ -320,6 +320,8 @@ if __name__ == "__main__": + + diff --git a/extraction_service/test_execute_simple.py b/extraction_service/test_execute_simple.py index c6e72d7b..62990ce6 100644 --- a/extraction_service/test_execute_simple.py +++ b/extraction_service/test_execute_simple.py @@ -86,6 +86,8 @@ except Exception as e: + + diff --git a/extraction_service/test_module.py b/extraction_service/test_module.py index 8e4c2e4e..eb1ae8e7 100644 --- a/extraction_service/test_module.py +++ b/extraction_service/test_module.py @@ -66,6 +66,8 @@ except Exception as e: + + diff --git a/frontend-v2/.dockerignore b/frontend-v2/.dockerignore index 2ce42d8f..bd97bbbd 100644 --- a/frontend-v2/.dockerignore +++ b/frontend-v2/.dockerignore @@ -85,6 +85,8 @@ vite.config.*.timestamp-* + + diff --git a/frontend-v2/docker-entrypoint.sh b/frontend-v2/docker-entrypoint.sh index 453c84d3..cbbe3835 100644 --- a/frontend-v2/docker-entrypoint.sh +++ b/frontend-v2/docker-entrypoint.sh @@ -52,6 +52,8 @@ exec nginx -g 'daemon off;' + + diff --git a/frontend-v2/nginx.conf b/frontend-v2/nginx.conf index 73cb9f3e..bd1f2d7f 100644 --- a/frontend-v2/nginx.conf +++ b/frontend-v2/nginx.conf @@ -208,6 +208,8 @@ http { + + diff --git a/frontend-v2/package-lock.json b/frontend-v2/package-lock.json index 68a53e52..0daeea70 100644 --- a/frontend-v2/package-lock.json +++ b/frontend-v2/package-lock.json @@ -21,6 +21,8 @@ "dayjs": "^1.11.19", "dexie": "^4.2.1", "diff-match-patch": "^1.0.5", + "docx": "^9.5.1", + "file-saver": "^2.0.5", "immer": "^11.0.0", "lodash": "^4.17.21", "lucide-react": "^0.555.0", @@ -34,6 +36,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@types/file-saver": "^2.0.7", "@types/lodash": "^4.17.21", "@types/node": "^24.10.0", "@types/react": "^19.2.2", @@ -3146,6 +3149,13 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/file-saver": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz", + "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.16.tgz", @@ -4415,6 +4425,12 @@ "node": ">=18" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", @@ -4882,6 +4898,41 @@ "dev": true, "license": "MIT" }, + "node_modules/docx": { + "version": "9.5.1", + "resolved": "https://registry.npmmirror.com/docx/-/docx-9.5.1.tgz", + "integrity": "sha512-ABDI7JEirFD2+bHhOBlsGZxaG1UgZb2M/QMKhLSDGgVNhxDesTCDcP+qoDnDGjZ4EOXTRfUjUgwHVuZ6VSTfWQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.0.1", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5362,6 +5413,12 @@ "node": ">=16.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -5733,6 +5790,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", @@ -5812,6 +5879,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immer": { "version": "11.0.0", "resolved": "https://registry.npmmirror.com/immer/-/immer-11.0.0.tgz", @@ -5849,6 +5922,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", @@ -5992,6 +6071,12 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", @@ -6143,6 +6228,18 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", @@ -6167,6 +6264,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", @@ -6380,6 +6486,12 @@ "node": ">= 0.6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", @@ -6595,6 +6707,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", @@ -6910,6 +7028,12 @@ "node": ">=6" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", @@ -7184,6 +7308,27 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", @@ -7370,6 +7515,15 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", @@ -7474,6 +7628,12 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", @@ -7577,6 +7737,21 @@ "node": ">=0.8" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", @@ -8183,7 +8358,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vite": { @@ -8562,6 +8736,24 @@ "node": ">=0.8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmmirror.com/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend-v2/package.json b/frontend-v2/package.json index 2332c5c5..66561d01 100644 --- a/frontend-v2/package.json +++ b/frontend-v2/package.json @@ -23,6 +23,8 @@ "dayjs": "^1.11.19", "dexie": "^4.2.1", "diff-match-patch": "^1.0.5", + "docx": "^9.5.1", + "file-saver": "^2.0.5", "immer": "^11.0.0", "lodash": "^4.17.21", "lucide-react": "^0.555.0", @@ -36,6 +38,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@types/file-saver": "^2.0.7", "@types/lodash": "^4.17.21", "@types/node": "^24.10.0", "@types/react": "^19.2.2", diff --git a/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx b/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx index f7ff5d42..47a88e2f 100644 --- a/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx +++ b/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx @@ -555,6 +555,8 @@ export default FulltextDetailDrawer; + + diff --git a/frontend-v2/src/modules/dc/hooks/useAssets.ts b/frontend-v2/src/modules/dc/hooks/useAssets.ts index 4499abf3..dbe93900 100644 --- a/frontend-v2/src/modules/dc/hooks/useAssets.ts +++ b/frontend-v2/src/modules/dc/hooks/useAssets.ts @@ -148,6 +148,8 @@ export const useAssets = (activeTab: AssetTabType) => { + + diff --git a/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts b/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts index 415d2fa5..b6cb5df2 100644 --- a/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts +++ b/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts @@ -138,6 +138,8 @@ export const useRecentTasks = () => { + + diff --git a/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx b/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx index 2306d4bf..f3b553c9 100644 --- a/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx +++ b/frontend-v2/src/modules/dc/pages/tool-c/components/DropnaDialog.tsx @@ -337,6 +337,8 @@ export default DropnaDialog; + + diff --git a/frontend-v2/src/modules/dc/pages/tool-c/components/MetricTimePanel.tsx b/frontend-v2/src/modules/dc/pages/tool-c/components/MetricTimePanel.tsx index 7a344de4..98f13e64 100644 --- a/frontend-v2/src/modules/dc/pages/tool-c/components/MetricTimePanel.tsx +++ b/frontend-v2/src/modules/dc/pages/tool-c/components/MetricTimePanel.tsx @@ -422,6 +422,8 @@ export default MetricTimePanel; + + diff --git a/frontend-v2/src/modules/dc/pages/tool-c/components/PivotPanel.tsx b/frontend-v2/src/modules/dc/pages/tool-c/components/PivotPanel.tsx index 6af5c068..ceb097a5 100644 --- a/frontend-v2/src/modules/dc/pages/tool-c/components/PivotPanel.tsx +++ b/frontend-v2/src/modules/dc/pages/tool-c/components/PivotPanel.tsx @@ -308,6 +308,8 @@ export default PivotPanel; + + diff --git a/frontend-v2/src/modules/dc/pages/tool-c/hooks/useSessionStatus.ts b/frontend-v2/src/modules/dc/pages/tool-c/hooks/useSessionStatus.ts index 45fc7f54..33067ca8 100644 --- a/frontend-v2/src/modules/dc/pages/tool-c/hooks/useSessionStatus.ts +++ b/frontend-v2/src/modules/dc/pages/tool-c/hooks/useSessionStatus.ts @@ -108,6 +108,8 @@ export function useSessionStatus({ + + diff --git a/frontend-v2/src/modules/dc/pages/tool-c/types/index.ts b/frontend-v2/src/modules/dc/pages/tool-c/types/index.ts index a049d6d0..b7a660c0 100644 --- a/frontend-v2/src/modules/dc/pages/tool-c/types/index.ts +++ b/frontend-v2/src/modules/dc/pages/tool-c/types/index.ts @@ -100,6 +100,8 @@ export interface DataStats { + + diff --git a/frontend-v2/src/modules/dc/types/portal.ts b/frontend-v2/src/modules/dc/types/portal.ts index 620d6b9e..f12ac94f 100644 --- a/frontend-v2/src/modules/dc/types/portal.ts +++ b/frontend-v2/src/modules/dc/types/portal.ts @@ -96,6 +96,8 @@ export type AssetTabType = 'all' | 'processed' | 'raw'; + + diff --git a/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts b/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts index 0c31b48f..aeaa60cb 100644 --- a/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts +++ b/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts @@ -218,3 +218,5 @@ export const documentSelectionApi = { + + diff --git a/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx b/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx index ab071d54..8faf64f8 100644 --- a/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx +++ b/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx @@ -286,3 +286,5 @@ export default KnowledgePage; + + diff --git a/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts b/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts index d149ac84..0b31e26a 100644 --- a/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts +++ b/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts @@ -224,3 +224,5 @@ export const useKnowledgeBaseStore = create((set, get) => ({ + + diff --git a/frontend-v2/src/modules/pkb/types/workspace.ts b/frontend-v2/src/modules/pkb/types/workspace.ts index 040aa405..b14cf5a7 100644 --- a/frontend-v2/src/modules/pkb/types/workspace.ts +++ b/frontend-v2/src/modules/pkb/types/workspace.ts @@ -41,3 +41,5 @@ export interface BatchTemplate { + + diff --git a/frontend-v2/src/modules/rvw/api/index.ts b/frontend-v2/src/modules/rvw/api/index.ts new file mode 100644 index 00000000..a6026af0 --- /dev/null +++ b/frontend-v2/src/modules/rvw/api/index.ts @@ -0,0 +1,130 @@ +/** + * RVW模块API + */ +import axios from 'axios'; +import type { ReviewTask, ReviewReport, ApiResponse, AgentType } from '../types'; + +const API_BASE = '/api/v2/rvw'; + +// 获取任务列表 +export async function getTasks(status?: string): Promise { + const params = status && status !== 'all' ? { status } : {}; + const response = await axios.get>(`${API_BASE}/tasks`, { params }); + return response.data.data || []; +} + +// 上传稿件 +export async function uploadManuscript(file: File, selectedAgents?: AgentType[]): Promise<{ taskId: string }> { + const formData = new FormData(); + formData.append('file', file); + if (selectedAgents) { + formData.append('selectedAgents', JSON.stringify(selectedAgents)); + } + + const response = await axios.post>(`${API_BASE}/tasks`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + + if (!response.data.success) { + throw new Error(response.data.error || '上传失败'); + } + + return response.data.data!; +} + +// 获取任务详情 +export async function getTask(taskId: string): Promise { + const response = await axios.get>(`${API_BASE}/tasks/${taskId}`); + return response.data.data!; +} + +// 获取任务报告 +export async function getTaskReport(taskId: string): Promise { + const response = await axios.get>(`${API_BASE}/tasks/${taskId}/report`); + return response.data.data!; +} + +// 运行审查任务(返回jobId供轮询) +export async function runTask(taskId: string, agents: AgentType[]): Promise<{ taskId: string; jobId: string }> { + const response = await axios.post>(`${API_BASE}/tasks/${taskId}/run`, { agents }); + if (!response.data.success) { + throw new Error(response.data.error || '运行失败'); + } + return response.data.data!; +} + +// 批量运行审查任务 +export async function batchRunTasks(taskIds: string[], agents: AgentType[]): Promise { + const response = await axios.post>(`${API_BASE}/tasks/batch/run`, { taskIds, agents }); + if (!response.data.success) { + throw new Error(response.data.error || '批量运行失败'); + } +} + +// 删除任务 +export async function deleteTask(taskId: string): Promise { + await axios.delete(`${API_BASE}/tasks/${taskId}`); +} + +// 轮询任务状态 +export async function pollTaskUntilComplete( + taskId: string, + onUpdate?: (task: ReviewTask) => void, + maxAttempts = 120, + interval = 3000 +): Promise { + let attempts = 0; + + while (attempts < maxAttempts) { + const task = await getTask(taskId); + onUpdate?.(task); + + if (task.status === 'completed' || task.status === 'failed') { + return task; + } + + await new Promise(resolve => setTimeout(resolve, interval)); + attempts++; + } + + throw new Error('任务超时'); +} + +// 格式化文件大小 +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 mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}分${secs}秒`; +} + +// 格式化时间 +export function formatTime(dateStr: string): string { + const date = new Date(dateStr); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + + // 小于1分钟 + if (diff < 60 * 1000) return '刚刚'; + + // 小于1小时 + if (diff < 60 * 60 * 1000) { + return `${Math.floor(diff / (60 * 1000))}分钟前`; + } + + // 今天 + if (date.toDateString() === now.toDateString()) { + return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); + } + + // 其他 + return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }); +} + diff --git a/frontend-v2/src/modules/rvw/components/AgentModal.tsx b/frontend-v2/src/modules/rvw/components/AgentModal.tsx new file mode 100644 index 00000000..f119798c --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/AgentModal.tsx @@ -0,0 +1,123 @@ +/** + * 智能体选择弹窗 + */ +import { useState } from 'react'; +import { PlayCircle, X } from 'lucide-react'; +import type { AgentType } from '../types'; + +interface AgentModalProps { + visible: boolean; + taskCount: number; + onClose: () => void; + onConfirm: (agents: AgentType[]) => void; +} + +export default function AgentModal({ visible, taskCount, onClose, onConfirm }: AgentModalProps) { + const [selectedAgents, setSelectedAgents] = useState(['editorial']); + + const toggleAgent = (agent: AgentType) => { + if (selectedAgents.includes(agent)) { + // 至少保留一个 + if (selectedAgents.length > 1) { + setSelectedAgents(selectedAgents.filter(a => a !== agent)); + } + } else { + setSelectedAgents([...selectedAgents, agent]); + } + }; + + const handleConfirm = () => { + // 只调用onConfirm,让调用方控制关闭时机 + onConfirm(selectedAgents); + }; + + if (!visible) return null; + + return ( +
+
+ {/* 头部 */} +
+

+ + 发起智能审稿 +

+ +
+ + {/* 内容 */} +
+

+ {taskCount > 1 ? `已选择 ${taskCount} 个稿件,请选择审稿维度:` : '请选择审稿维度:'} +

+ + {/* 规范性智能体 */} + + + {/* 方法学智能体 */} + +
+ + {/* 底部按钮 */} +
+ + +
+
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/BatchToolbar.tsx b/frontend-v2/src/modules/rvw/components/BatchToolbar.tsx new file mode 100644 index 00000000..b33998ef --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/BatchToolbar.tsx @@ -0,0 +1,43 @@ +/** + * 批量操作浮动工具栏 + */ +import { Play, X } from 'lucide-react'; + +interface BatchToolbarProps { + selectedCount: number; + onRunBatch: () => void; + onClearSelection: () => void; +} + +export default function BatchToolbar({ selectedCount, onRunBatch, onClearSelection }: BatchToolbarProps) { + if (selectedCount === 0) return null; + + return ( +
+
+
+ {selectedCount} +
+ 个文件已选中 +
+ +
+ + + + +
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/EditorialReport.tsx b/frontend-v2/src/modules/rvw/components/EditorialReport.tsx new file mode 100644 index 00000000..7edf3199 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/EditorialReport.tsx @@ -0,0 +1,195 @@ +/** + * 规范性评估报告组件 - 专业版 + */ +import { AlertTriangle, CheckCircle, XCircle, TrendingUp, FileText, Lightbulb } from 'lucide-react'; +import type { EditorialReviewResult } from '../types'; + +interface EditorialReportProps { + data: EditorialReviewResult; +} + +export default function EditorialReport({ data }: EditorialReportProps) { + // 统计各状态数量 + const stats = { + pass: data.items.filter(item => item.status === 'pass').length, + warning: data.items.filter(item => item.status === 'warning').length, + fail: data.items.filter(item => item.status === 'fail').length, + }; + + const getStatusIcon = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': + return ; + case 'warning': + return ; + case 'fail': + return ; + } + }; + + const getStatusLabel = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': return '通过'; + case 'warning': return '警告'; + case 'fail': return '不通过'; + } + }; + + const getStatusColors = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': + return { bg: 'bg-green-50', border: 'border-green-200', text: 'text-green-700', badge: 'bg-green-100 text-green-700' }; + case 'warning': + return { bg: 'bg-amber-50', border: 'border-amber-200', text: 'text-amber-700', badge: 'bg-amber-100 text-amber-700' }; + case 'fail': + return { bg: 'bg-red-50', border: 'border-red-200', text: 'text-red-700', badge: 'bg-red-100 text-red-700' }; + } + }; + + const getScoreGrade = (score: number) => { + if (score >= 90) return { label: '优秀', color: 'text-green-600', bg: 'bg-green-500' }; + if (score >= 80) return { label: '良好', color: 'text-green-600', bg: 'bg-green-500' }; + if (score >= 70) return { label: '中等', color: 'text-amber-600', bg: 'bg-amber-500' }; + if (score >= 60) return { label: '及格', color: 'text-amber-600', bg: 'bg-amber-500' }; + return { label: '不及格', color: 'text-red-600', bg: 'bg-red-500' }; + }; + + const grade = getScoreGrade(data.overall_score); + + return ( +
+ {/* 评分总览卡片 */} +
+
+
+ {/* 分数环 */} +
+
+
+ {data.overall_score} + +
+
+ + {grade.label} + +
+ + {/* 评估摘要 */} +
+
+ +

稿约规范性评估

+
+

{data.summary}

+ + {/* 统计指标 */} +
+
+ + {stats.pass} 项通过 +
+ {stats.warning > 0 && ( +
+ + {stats.warning} 项警告 +
+ )} + {stats.fail > 0 && ( +
+ + {stats.fail} 项不通过 +
+ )} +
+
+
+
+
+ + {/* 检测详情标题 */} +
+ +

检测详情

+ 共 {data.items.length} 项 +
+ + {/* 检测项列表 */} +
+ {data.items.map((item, index) => { + const colors = getStatusColors(item.status); + return ( +
+ {/* 检测项头部 */} +
+
+
+ {getStatusIcon(item.status)} +

{item.criterion}

+
+
+ + {item.score}分 + + + {getStatusLabel(item.status)} + +
+
+
+ + {/* 检测项内容 */} + {(item.issues?.length || item.suggestions?.length) && ( +
+ {/* 问题列表 */} + {item.issues && item.issues.length > 0 && ( +
+

发现问题

+
    + {item.issues.map((issue, i) => ( +
  • + + {issue} +
  • + ))} +
+
+ )} + + {/* 建议 */} + {item.suggestions && item.suggestions.length > 0 && ( +
+
+ +

改进建议

+
+
    + {item.suggestions.map((suggestion, i) => ( +
  • + + {suggestion} +
  • + ))} +
+
+ )} +
+ )} + + {/* 无问题时的简洁显示 */} + {!item.issues?.length && !item.suggestions?.length && item.status === 'pass' && ( +
+ + 该项检测通过,符合规范要求 +
+ )} +
+ ); + })} +
+
+ ); +} diff --git a/frontend-v2/src/modules/rvw/components/FilterChips.tsx b/frontend-v2/src/modules/rvw/components/FilterChips.tsx new file mode 100644 index 00000000..f9a4fb51 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/FilterChips.tsx @@ -0,0 +1,66 @@ +/** + * 筛选Chips组件 + */ +import type { TaskFilters } from '../types'; + +interface FilterChipsProps { + filters: TaskFilters; + counts: { all: number; pending: number; completed: number }; + onFilterChange: (filters: TaskFilters) => void; +} + +export default function FilterChips({ filters, counts, onFilterChange }: FilterChipsProps) { + const statusOptions = [ + { value: 'all' as const, label: '全部', count: counts.all }, + { value: 'pending' as const, label: '待处理', count: counts.pending }, + { value: 'completed' as const, label: '已完成', count: counts.completed }, + ]; + + const timeOptions = [ + { value: 'all' as const, label: '不限' }, + { value: 'today' as const, label: '今天' }, + { value: 'week' as const, label: '近7天' }, + ]; + + return ( +
+ {/* 状态筛选 */} +
+ 状态: + {statusOptions.map(option => ( + + ))} +
+ +
+ + {/* 时间筛选 */} +
+ 时间: + {timeOptions.map(option => ( + + ))} +
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/Header.tsx b/frontend-v2/src/modules/rvw/components/Header.tsx new file mode 100644 index 00000000..8cf751ad --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/Header.tsx @@ -0,0 +1,56 @@ +/** + * Dashboard头部组件 + */ +import { useRef } from 'react'; +import { BrainCircuit, UploadCloud } from 'lucide-react'; + +interface HeaderProps { + onUpload: (files: FileList) => void; +} + +export default function Header({ onUpload }: HeaderProps) { + const fileInputRef = useRef(null); + + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + onUpload(e.target.files); + // 重置input以允许选择相同文件 + e.target.value = ''; + } + }; + + return ( +
+ {/* Logo区域 */} +
+
+ +
+
+

智能审稿系统

+

当前工作区:编辑部初审组

+
+
+ + {/* 上传按钮 */} +
+ + +
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/MethodologyReport.tsx b/frontend-v2/src/modules/rvw/components/MethodologyReport.tsx new file mode 100644 index 00000000..25a08c71 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/MethodologyReport.tsx @@ -0,0 +1,206 @@ +/** + * 方法学评估报告组件 - 专业版 + */ +import { XCircle, AlertTriangle, CheckCircle, Microscope, Lightbulb, MapPin, TrendingUp } from 'lucide-react'; +import type { MethodologyReviewResult } from '../types'; + +interface MethodologyReportProps { + data: MethodologyReviewResult; +} + +export default function MethodologyReport({ data }: MethodologyReportProps) { + // 统计问题数量 + const totalIssues = data.parts.reduce((sum, part) => sum + part.issues.length, 0); + const majorIssues = data.parts.reduce((sum, part) => sum + part.issues.filter(i => i.severity === 'major').length, 0); + const minorIssues = totalIssues - majorIssues; + + const getSeverityStyle = (severity: 'major' | 'minor') => { + return severity === 'major' + ? { icon: , label: '严重', badge: 'bg-red-100 text-red-700 border-red-200' } + : { icon: , label: '轻微', badge: 'bg-amber-100 text-amber-700 border-amber-200' }; + }; + + const getScoreGrade = (score: number) => { + if (score >= 90) return { label: '优秀', color: 'text-green-600', bg: 'bg-green-500' }; + if (score >= 80) return { label: '良好', color: 'text-green-600', bg: 'bg-green-500' }; + if (score >= 70) return { label: '中等', color: 'text-amber-600', bg: 'bg-amber-500' }; + if (score >= 60) return { label: '及格', color: 'text-amber-600', bg: 'bg-amber-500' }; + return { label: '不及格', color: 'text-red-600', bg: 'bg-red-500' }; + }; + + const getOverallStatus = () => { + if (data.overall_score >= 80) return { label: '通过', color: 'text-green-700', bg: 'bg-green-50', border: 'border-green-200' }; + if (data.overall_score >= 60) return { label: '存疑', color: 'text-amber-700', bg: 'bg-amber-50', border: 'border-amber-200' }; + return { label: '不通过', color: 'text-red-700', bg: 'bg-red-50', border: 'border-red-200' }; + }; + + const grade = getScoreGrade(data.overall_score); + const status = getOverallStatus(); + + return ( +
+ {/* 评分总览卡片 */} +
+
+
+ {/* 分数环 */} +
+
+
+ {data.overall_score} + +
+
+ + {grade.label} + +
+ + {/* 评估摘要 */} +
+
+ +

方法学评估

+ + {status.label} + +
+

{data.summary}

+ + {/* 统计指标 */} +
+
+ 共检测 {data.parts.length} 个方面 +
+ {totalIssues === 0 ? ( +
+ + 未发现问题 +
+ ) : ( + <> + {majorIssues > 0 && ( +
+ + {majorIssues} 个严重问题 +
+ )} + {minorIssues > 0 && ( +
+ + {minorIssues} 个轻微问题 +
+ )} + + )} +
+
+
+
+
+ + {/* 分项详情标题 */} +
+ +

分项评估

+ 共 {data.parts.length} 项 +
+ + {/* 分项详情 */} +
+ {data.parts.map((part, partIndex) => ( +
+ {/* 分项头部 */} +
+
+
+ {part.issues.length === 0 ? ( + + ) : ( + + )} +

{part.part}

+
+
+ = 80 ? 'bg-green-100 text-green-700' : + part.score >= 60 ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700' + }`}> + {part.score}分 + + {part.issues.length === 0 ? ( + + + 无问题 + + ) : ( + + {part.issues.length} 个问题 + + )} +
+
+
+ + {/* 问题列表 */} + {part.issues.length > 0 && ( +
+ {part.issues.map((issue, issueIndex) => { + const severity = getSeverityStyle(issue.severity); + return ( +
+
+
{severity.icon}
+
+ {/* 问题标题和严重程度 */} +
+ {issue.type} + + {severity.label} + +
+ + {/* 问题描述 */} +

{issue.description}

+ + {/* 位置信息 */} + {issue.location && ( +
+ + 位置:{issue.location} +
+ )} + + {/* 改进建议 */} + {issue.suggestion && ( +
+
+ +
+

改进建议

+

{issue.suggestion}

+
+
+
+ )} +
+
+
+ ); + })} +
+ )} + + {/* 无问题时的简洁显示 */} + {part.issues.length === 0 && ( +
+ + 该部分未发现方法学问题,符合规范要求 +
+ )} +
+ ))} +
+
+ ); +} diff --git a/frontend-v2/src/modules/rvw/components/ReportDetail.tsx b/frontend-v2/src/modules/rvw/components/ReportDetail.tsx new file mode 100644 index 00000000..ca27b024 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/ReportDetail.tsx @@ -0,0 +1,110 @@ +/** + * 报告详情页组件 + */ +import { useState } from 'react'; +import { ArrowLeft, FileCheck, Tag } from 'lucide-react'; +import type { ReviewReport } from '../types'; +import EditorialReport from './EditorialReport'; +import MethodologyReport from './MethodologyReport'; + +interface ReportDetailProps { + report: ReviewReport; + onBack: () => void; +} + +export default function ReportDetail({ report, onBack }: ReportDetailProps) { + const [activeTab, setActiveTab] = useState<'editorial' | 'methodology'>('editorial'); + + const hasEditorial = !!report.editorialReview; + const hasMethodology = !!report.methodologyReview; + + // 如果只有方法学,默认显示方法学 + const effectiveTab = activeTab === 'editorial' && !hasEditorial && hasMethodology ? 'methodology' : activeTab; + + return ( +
+ {/* 顶部导航栏 */} +
+
+ +
+
+

+ {report.fileName} + {report.overallScore && ( + = 80 ? 'tag-green' : + report.overallScore >= 60 ? 'tag-amber' : 'tag-red' + }`}> + {report.overallScore}分 + + )} +

+
+
+
+ +
+
+ + {/* 内容区域 */} +
+ {/* Tab切换 */} + {(hasEditorial || hasMethodology) && ( +
+ {hasEditorial && ( + + )} + {hasMethodology && ( + + )} +
+ )} + + {/* 报告内容 */} + {effectiveTab === 'editorial' && report.editorialReview && ( + + )} + {effectiveTab === 'methodology' && report.methodologyReview && ( + + )} + + {/* 无数据状态 */} + {!hasEditorial && !hasMethodology && ( +
+ +

暂无评估报告

+
+ )} +
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/ScoreRing.tsx b/frontend-v2/src/modules/rvw/components/ScoreRing.tsx new file mode 100644 index 00000000..3af1fdc3 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/ScoreRing.tsx @@ -0,0 +1,38 @@ +/** + * 评分环组件 + */ + +interface ScoreRingProps { + score: number; + size?: 'small' | 'medium' | 'large'; + showLabel?: boolean; +} + +export default function ScoreRing({ score, size = 'medium', showLabel = true }: ScoreRingProps) { + const sizeStyles = { + small: 'w-12 h-12 text-lg border-4', + medium: 'w-20 h-20 text-2xl border-6', + large: 'w-24 h-24 text-3xl border-8', + }; + + const getScoreStatus = (score: number) => { + if (score >= 80) return { class: 'pass', label: 'Pass', bgColor: 'bg-green-50', borderColor: 'border-green-500', textColor: 'text-green-700' }; + if (score >= 60) return { class: 'warn', label: 'Warning', bgColor: 'bg-amber-50', borderColor: 'border-amber-500', textColor: 'text-amber-700' }; + return { class: 'fail', label: 'Fail', bgColor: 'bg-red-50', borderColor: 'border-red-500', textColor: 'text-red-700' }; + }; + + const status = getScoreStatus(score); + + return ( +
+ {score} + {showLabel && size !== 'small' && ( + {status.label} + )} +
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/Sidebar.tsx b/frontend-v2/src/modules/rvw/components/Sidebar.tsx new file mode 100644 index 00000000..959a0186 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/Sidebar.tsx @@ -0,0 +1,73 @@ +/** + * RVW侧边栏组件 + */ +import { LayoutGrid, Archive, Settings, BrainCircuit } from 'lucide-react'; + +interface SidebarProps { + currentView: 'dashboard' | 'archive'; + onViewChange: (view: 'dashboard' | 'archive') => void; + onSettingsClick?: () => void; +} + +export default function Sidebar({ currentView, onViewChange, onSettingsClick }: SidebarProps) { + return ( + + ); +} + diff --git a/frontend-v2/src/modules/rvw/components/TaskDetail.tsx b/frontend-v2/src/modules/rvw/components/TaskDetail.tsx new file mode 100644 index 00000000..3ce6edd1 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/TaskDetail.tsx @@ -0,0 +1,574 @@ +/** + * 任务详情页组件 + * 支持显示审稿进度和结果 + */ +import { useState, useEffect } from 'react'; +import { ArrowLeft, FileCheck, Clock, AlertCircle, CheckCircle, Loader2, FileText, Bot } from 'lucide-react'; +import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, Table, TableRow, TableCell, WidthType } from 'docx'; +import { saveAs } from 'file-saver'; +import type { ReviewTask, ReviewReport, TaskStatus } from '../types'; +import EditorialReport from './EditorialReport'; +import MethodologyReport from './MethodologyReport'; +import * as api from '../api'; +import { message } from 'antd'; + +interface TaskDetailProps { + task: ReviewTask; + jobId?: string | null; // pg-boss 任务ID(可选,用于更精确的状态轮询) + onBack: () => void; +} + +// 状态信息映射 +const STATUS_INFO: Record = { + pending: { label: '等待开始', color: 'text-slate-500', icon: Clock }, + extracting: { label: '正在提取文档', color: 'text-blue-500', icon: Loader2 }, + reviewing: { label: '正在初始化审查', color: 'text-indigo-500', icon: Loader2 }, + reviewing_editorial: { label: '正在审查稿约规范性', color: 'text-indigo-500', icon: Bot }, + reviewing_methodology: { label: '正在审查方法学', color: 'text-purple-500', icon: Bot }, + completed: { label: '审查完成', color: 'text-green-600', icon: CheckCircle }, + failed: { label: '审查失败', color: 'text-red-500', icon: AlertCircle }, +}; + +// 根据选择的智能体动态生成进度步骤 +const getProgressSteps = (selectedAgents: string[]) => { + const steps = [ + { key: 'upload', label: '上传文档' }, + { key: 'extract', label: '文本提取' }, + ]; + + if (selectedAgents.includes('editorial')) { + steps.push({ key: 'editorial', label: '稿约规范性' }); + } + if (selectedAgents.includes('methodology')) { + steps.push({ key: 'methodology', label: '方法学评估' }); + } + + return steps; +}; + +export default function TaskDetail({ task: initialTask, jobId, onBack }: TaskDetailProps) { + const [task, setTask] = useState(initialTask); + const [report, setReport] = useState(null); + const [activeTab, setActiveTab] = useState<'editorial' | 'methodology'>('editorial'); + const [elapsedTime, setElapsedTime] = useState(0); + + // Suppress unused variable warning - jobId is reserved for future use + void jobId; + + const isProcessing = ['extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'].includes(task.status); + const isCompleted = task.status === 'completed'; + const isFailed = task.status === 'failed'; + + // 轮询任务状态 + useEffect(() => { + if (!isProcessing) return; + + const interval = setInterval(async () => { + try { + const updated = await api.getTask(task.id); + setTask(updated); + + // 如果完成了,加载报告 + if (updated.status === 'completed') { + const reportData = await api.getTaskReport(task.id); + setReport(reportData); + } + } catch (error) { + console.error('更新任务状态失败:', error); + } + }, 2000); + + return () => clearInterval(interval); + }, [task.id, isProcessing]); + + // 计时器 + useEffect(() => { + if (!isProcessing) return; + + const start = Date.now(); + const interval = setInterval(() => { + setElapsedTime(Math.floor((Date.now() - start) / 1000)); + }, 1000); + + return () => clearInterval(interval); + }, [isProcessing]); + + // 完成时加载报告 + useEffect(() => { + if (isCompleted && !report) { + api.getTaskReport(task.id).then(setReport).catch(() => { + message.error('加载报告失败'); + }); + } + }, [isCompleted, task.id, report]); + + // 报告加载后自动设置正确的 Tab + useEffect(() => { + if (report) { + // 优先显示有数据的 Tab + if (report.editorialReview) { + setActiveTab('editorial'); + } else if (report.methodologyReview) { + setActiveTab('methodology'); + } + } + }, [report]); + + // 动态获取进度步骤 + const progressSteps = getProgressSteps(task.selectedAgents || ['editorial', 'methodology']); + + // 获取进度步骤状态 + const getStepStatus = (stepKey: string): 'completed' | 'active' | 'pending' => { + const hasEditorial = task.selectedAgents?.includes('editorial'); + const hasMethodology = task.selectedAgents?.includes('methodology'); + + if (task.status === 'pending') { + return stepKey === 'upload' ? 'completed' : 'pending'; + } + if (task.status === 'extracting') { + if (stepKey === 'upload') return 'completed'; + if (stepKey === 'extract') return 'active'; + return 'pending'; + } + if (task.status === 'reviewing' || task.status === 'reviewing_editorial') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + if (stepKey === 'editorial' && hasEditorial) return 'active'; + return 'pending'; + } + if (task.status === 'reviewing_methodology') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + if (stepKey === 'editorial') return 'completed'; + if (stepKey === 'methodology' && hasMethodology) return 'active'; + return 'pending'; + } + if (task.status === 'completed') return 'completed'; + if (task.status === 'failed') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + return 'pending'; + } + return 'pending'; + }; + + // 导出报告为 Word 文档 + const handleExportReport = async () => { + if (!report) { + message.warning('报告尚未加载完成'); + return; + } + + try { + message.loading({ content: '正在生成Word文档...', key: 'export' }); + + const children: (Paragraph | Table)[] = []; + + // 标题 + children.push( + new Paragraph({ + text: '智能审稿报告', + heading: HeadingLevel.TITLE, + alignment: AlignmentType.CENTER, + spacing: { after: 400 }, + }) + ); + + // 基本信息表格 + children.push( + new Table({ + width: { size: 100, type: WidthType.PERCENTAGE }, + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({ children: [new TextRun({ text: '文件名', bold: true })] })], + width: { size: 25, type: WidthType.PERCENTAGE }, + }), + new TableCell({ + children: [new Paragraph(report.fileName)], + width: { size: 75, type: WidthType.PERCENTAGE }, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({ children: [new TextRun({ text: '综合评分', bold: true })] })], + }), + new TableCell({ + children: [new Paragraph(`${report.overallScore || '-'} 分`)], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({ children: [new TextRun({ text: '审查用时', bold: true })] })], + }), + new TableCell({ + children: [new Paragraph( + report.durationSeconds + ? `${Math.floor(report.durationSeconds / 60)}分${report.durationSeconds % 60}秒` + : '-' + )], + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({ children: [new TextRun({ text: '审查时间', bold: true })] })], + }), + new TableCell({ + children: [new Paragraph(report.completedAt ? new Date(report.completedAt).toLocaleString('zh-CN') : '-')], + }), + ], + }), + ], + }) + ); + + children.push(new Paragraph({ spacing: { after: 300 } })); + + // 稿约规范性评估 + if (report.editorialReview) { + children.push( + new Paragraph({ + text: `一、稿约规范性评估(${report.editorialReview.overall_score}分)`, + heading: HeadingLevel.HEADING_1, + spacing: { before: 400, after: 200 }, + }) + ); + + children.push( + new Paragraph({ + children: [new TextRun({ text: '总体评价:', bold: true }), new TextRun(report.editorialReview.summary)], + spacing: { after: 200 }, + }) + ); + + report.editorialReview.items.forEach((item, i) => { + const statusText = item.status === 'pass' ? '✓通过' : item.status === 'warning' ? '⚠警告' : '✗不通过'; + + children.push( + new Paragraph({ + text: `${i + 1}. ${item.criterion}(${item.score}分)- ${statusText}`, + heading: HeadingLevel.HEADING_2, + spacing: { before: 200, after: 100 }, + }) + ); + + if (item.issues?.length) { + children.push( + new Paragraph({ + children: [new TextRun({ text: '存在问题:', bold: true, color: 'CC0000' })], + }) + ); + item.issues.forEach(issue => { + children.push( + new Paragraph({ + text: `• ${issue}`, + indent: { left: 720 }, + }) + ); + }); + } + + if (item.suggestions?.length) { + children.push( + new Paragraph({ + children: [new TextRun({ text: '修改建议:', bold: true, color: '006600' })], + spacing: { before: 100 }, + }) + ); + item.suggestions.forEach(s => { + children.push( + new Paragraph({ + text: `• ${s}`, + indent: { left: 720 }, + }) + ); + }); + } + }); + } + + // 方法学评估 + if (report.methodologyReview) { + children.push( + new Paragraph({ + text: `二、方法学评估(${report.methodologyReview.overall_score}分)`, + heading: HeadingLevel.HEADING_1, + spacing: { before: 400, after: 200 }, + }) + ); + + children.push( + new Paragraph({ + children: [new TextRun({ text: '总体评价:', bold: true }), new TextRun(report.methodologyReview.summary)], + spacing: { after: 200 }, + }) + ); + + report.methodologyReview.parts.forEach((part) => { + children.push( + new Paragraph({ + text: `${part.part}(${part.score}分)`, + heading: HeadingLevel.HEADING_2, + spacing: { before: 200, after: 100 }, + }) + ); + + if (part.issues.length === 0) { + children.push( + new Paragraph({ + children: [new TextRun({ text: '✓ 未发现问题', color: '006600' })], + indent: { left: 720 }, + }) + ); + } else { + part.issues.forEach(issue => { + const severityText = issue.severity === 'major' ? '【严重】' : '【轻微】'; + const severityColor = issue.severity === 'major' ? 'CC0000' : 'FF9900'; + + children.push( + new Paragraph({ + children: [ + new TextRun({ text: severityText, bold: true, color: severityColor }), + new TextRun({ text: ` ${issue.type}:`, bold: true }), + new TextRun(issue.description), + ], + indent: { left: 720 }, + }) + ); + + if (issue.suggestion) { + children.push( + new Paragraph({ + children: [ + new TextRun({ text: '建议:', bold: true, color: '006600' }), + new TextRun(issue.suggestion), + ], + indent: { left: 1080 }, + spacing: { after: 100 }, + }) + ); + } + }); + } + }); + } + + // 页脚 + children.push( + new Paragraph({ + children: [ + new TextRun({ text: '————————————————————————', color: 'AAAAAA' }), + ], + alignment: AlignmentType.CENTER, + spacing: { before: 600 }, + }) + ); + + children.push( + new Paragraph({ + children: [ + new TextRun({ text: '本报告由AI智能审稿系统自动生成', italics: true, color: '888888', size: 20 }), + ], + alignment: AlignmentType.CENTER, + }) + ); + + // 创建文档 + const doc = new Document({ + sections: [{ + properties: {}, + children, + }], + }); + + // 生成并下载 + const blob = await Packer.toBlob(doc); + const fileName = `审稿报告_${report.fileName.replace(/\.[^.]+$/, '')}.docx`; + saveAs(blob, fileName); + + message.success({ content: '报告已导出为Word文档', key: 'export', duration: 2 }); + } catch (error) { + console.error('导出报告失败:', error); + message.error({ content: '导出失败,请重试', key: 'export', duration: 3 }); + } + }; + + // 格式化时间 + const formatTime = (seconds: number): string => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`; + }; + + const statusInfo = STATUS_INFO[task.status]; + const StatusIcon = statusInfo.icon; + + return ( +
+ {/* 顶部导航栏 */} +
+
+ +
+
+

+ + {task.fileName} +

+
+
+
+ {isCompleted && ( + + )} +
+
+ + {/* 内容区域 */} +
+
+ {/* 进度显示(审查中) */} + {isProcessing && ( +
+ {/* 状态头部 */} +
+ + + {statusInfo.label} + + + 已用时 {formatTime(elapsedTime)} + +
+ + {/* 进度条 - 根据选择的智能体动态显示 */} +
+ {progressSteps.map((step, index) => { + const stepStatus = getStepStatus(step.key); + return ( +
+
+
+ {stepStatus === 'completed' ? ( + + ) : stepStatus === 'active' ? ( + + ) : ( + index + 1 + )} +
+ + {step.label} + +
+ {index < progressSteps.length - 1 && ( +
+ )} +
+ ); + })} +
+ + {/* 提示信息 */} +
+

AI 正在分析您的稿件,这可能需要 1-3 分钟

+

请耐心等待,完成后将自动显示结果

+
+
+ )} + + {/* 失败状态 */} + {isFailed && ( +
+ +

审查失败

+

{task.errorMessage || '未知错误,请重试'}

+
+ )} + + {/* 完成状态 - 显示报告 */} + {isCompleted && report && ( + <> + {/* 分数卡片 */} +
+
+
+

综合评分

+

+ 审查用时 {report.durationSeconds ? formatTime(report.durationSeconds) : '-'} +

+
+
{report.overallScore || '-'}
+
+
+ + {/* Tab切换 */} +
+ {report.editorialReview && ( + + )} + {report.methodologyReview && ( + + )} +
+ + {/* 报告内容 */} + {activeTab === 'editorial' && report.editorialReview && ( + + )} + {activeTab === 'methodology' && report.methodologyReview && ( + + )} + + )} +
+
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/TaskTable.tsx b/frontend-v2/src/modules/rvw/components/TaskTable.tsx new file mode 100644 index 00000000..62153953 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/TaskTable.tsx @@ -0,0 +1,307 @@ +/** + * 任务表格组件 + */ +import { FileText, FileType2, Loader2, Play, Eye, RefreshCw, Trash2 } from 'lucide-react'; +import type { ReviewTask } from '../types'; +import { formatFileSize, formatTime } from '../api'; + +interface TaskTableProps { + tasks: ReviewTask[]; + selectedIds: string[]; + onSelectChange: (ids: string[]) => void; + onViewReport: (task: ReviewTask) => void; + onRunTask: (task: ReviewTask) => void; + onDeleteTask: (task: ReviewTask) => void; +} + +export default function TaskTable({ + tasks, + selectedIds, + onSelectChange, + onViewReport, + onRunTask, + onDeleteTask +}: TaskTableProps) { + const allSelected = tasks.length > 0 && selectedIds.length === tasks.length; + + const toggleSelectAll = () => { + if (allSelected) { + onSelectChange([]); + } else { + onSelectChange(tasks.map(t => t.id)); + } + }; + + const toggleSelect = (id: string) => { + if (selectedIds.includes(id)) { + onSelectChange(selectedIds.filter(i => i !== id)); + } else { + onSelectChange([...selectedIds, id]); + } + }; + + // 获取文件图标 + const getFileIcon = (fileName: string) => { + if (fileName.endsWith('.pdf')) { + return ; + } + return ; + }; + + // 获取文件图标容器样式 + const getFileIconStyle = (fileName: string) => { + if (fileName.endsWith('.pdf')) { + return 'bg-red-50 text-red-600 border-red-100'; + } + return 'bg-blue-50 text-blue-600 border-blue-100'; + }; + + // 渲染智能体标签 + const renderAgentTags = (task: ReviewTask) => { + if (!task.selectedAgents || task.selectedAgents.length === 0) { + return 未运行; + } + + return ( +
+ {task.selectedAgents.includes('editorial') && ( + 规范性 + )} + {task.selectedAgents.includes('methodology') && ( + 方法学 + )} +
+ ); + }; + + // 渲染结果摘要 + const renderResultSummary = (task: ReviewTask) => { + if (task.status === 'pending') { + return 等待发起...; + } + + if (task.status === 'extracting' || task.status === 'reviewing') { + return ( +
+ + {task.status === 'extracting' ? '提取文本中...' : '审查中...'} +
+ ); + } + + if (task.status === 'failed') { + return 失败; + } + + if (task.status === 'completed') { + return ( +
+ {task.editorialScore !== undefined && ( +
+
= 80 ? 'bg-green-500' : task.editorialScore >= 60 ? 'bg-amber-500' : 'bg-red-500'}`} /> + 规范性: + = 80 ? 'text-green-700' : task.editorialScore >= 60 ? 'text-amber-700' : 'text-red-700'}`}> + {task.editorialScore}分 + +
+ )} + {(task.methodologyScore !== undefined || task.methodologyStatus) && ( +
+
= 80 : task.methodologyStatus === '通过') ? 'bg-green-500' : + (task.methodologyScore !== undefined ? task.methodologyScore >= 60 : task.methodologyStatus === '存疑') ? 'bg-amber-500' : 'bg-red-500' + }`} /> + 方法学: + = 80 : task.methodologyStatus === '通过') ? 'text-green-700' : + (task.methodologyScore !== undefined ? task.methodologyScore >= 60 : task.methodologyStatus === '存疑') ? 'text-amber-700' : 'text-red-700' + }`}> + {task.methodologyScore !== undefined ? `${task.methodologyScore}分` : task.methodologyStatus} + +
+ )} +
+ ); + } + + return null; + }; + + // 渲染操作按钮 + const renderActions = (task: ReviewTask) => { + // 待审稿:[开始审稿] [删除] + if (task.status === 'pending') { + return ( +
+ + +
+ ); + } + + // 处理中:[查看进度] + if (['extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'].includes(task.status)) { + return ( + + ); + } + + // 已完成:[查看报告] [重新审稿] [删除] + if (task.status === 'completed') { + return ( +
+ + + +
+ ); + } + + // 失败:[重新审稿] [删除] + if (task.status === 'failed') { + return ( +
+ + +
+ ); + } + + return null; + }; + + if (tasks.length === 0) { + return ( +
+ +

暂无稿件,请上传新稿件开始审查

+
+ ); + } + + return ( +
+ + + + + + + + + + + + + {tasks.map(task => ( + + + + + + + + + ))} + +
+ + 文件名称 / 信息上传时间审稿维度结果摘要操作
+ toggleSelect(task.id)} + className="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer" + /> + +
+
+ {getFileIcon(task.fileName)} +
+
+
task.status === 'completed' && onViewReport(task)} + > + {task.fileName} +
+
+ {formatFileSize(task.fileSize)} + {task.wordCount && ( + <> + + {task.wordCount.toLocaleString()} 字 + + )} +
+
+
+
+ {formatTime(task.createdAt)} + + {renderAgentTags(task)} + + {renderResultSummary(task)} + + {renderActions(task)} +
+
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/components/index.ts b/frontend-v2/src/modules/rvw/components/index.ts new file mode 100644 index 00000000..0e72f0b8 --- /dev/null +++ b/frontend-v2/src/modules/rvw/components/index.ts @@ -0,0 +1,15 @@ +/** + * RVW组件导出 + */ +export { default as Sidebar } from './Sidebar'; +export { default as Header } from './Header'; +export { default as FilterChips } from './FilterChips'; +export { default as TaskTable } from './TaskTable'; +export { default as BatchToolbar } from './BatchToolbar'; +export { default as AgentModal } from './AgentModal'; +export { default as ScoreRing } from './ScoreRing'; +export { default as EditorialReport } from './EditorialReport'; +export { default as MethodologyReport } from './MethodologyReport'; +export { default as ReportDetail } from './ReportDetail'; +export { default as TaskDetail } from './TaskDetail'; + diff --git a/frontend-v2/src/modules/rvw/index.tsx b/frontend-v2/src/modules/rvw/index.tsx index fe3ad4fd..596ed5cf 100644 --- a/frontend-v2/src/modules/rvw/index.tsx +++ b/frontend-v2/src/modules/rvw/index.tsx @@ -5,498 +5,20 @@ * - 稿约评审:评估稿件是否符合期刊投稿要求 * - 方法学评审:评估临床研究的方法学质量 * - * @version Phase 3 - 前端重构 + * @version Phase 3 - 前端重构(迁移到 frontend-v2) */ -import { useState, useEffect } from 'react' -import { useNavigate, Routes, Route, useParams } from 'react-router-dom' -import { - Upload, - Button, - Table, - Tag, - Space, - message, - Card, - Spin, - Modal, - Checkbox, - Progress, - Tabs, - Typography, - Tooltip, - Popconfirm -} from 'antd' -import { - UploadOutlined, - FileTextOutlined, - DeleteOutlined, - EyeOutlined, - PlayCircleOutlined, - ReloadOutlined, - CheckCircleOutlined, - ClockCircleOutlined, - ExclamationCircleOutlined, - DownloadOutlined -} from '@ant-design/icons' -import type { UploadFile, UploadProps } from 'antd' - -const { Title, Text, Paragraph } = Typography -const { TabPane } = Tabs - -// API 基础路径 -const API_BASE = '/api/v2/rvw' - -// 任务类型定义 -interface ReviewTask { - id: string - fileName: string - status: 'pending' | 'processing' | 'completed' | 'failed' - selectedAgents: string[] - editorialScore?: number - methodologyStatus?: string - createdAt: string - updatedAt: string - editorialReview?: any - methodologyReview?: any -} - -// 智能体选择弹窗 -const AgentSelectModal: React.FC<{ - visible: boolean - onCancel: () => void - onConfirm: (agents: string[]) => void - loading?: boolean -}> = ({ visible, onCancel, onConfirm, loading }) => { - const [selected, setSelected] = useState(['editorial', 'methodology']) - - return ( - onConfirm(selected)} - confirmLoading={loading} - okText="开始审稿" - cancelText="取消" - > -
- setSelected(vals as string[])} - > - - -
-
📝 稿约评审智能体
-
评估稿件是否符合期刊投稿要求(11项标准)
-
-
- -
-
🔬 方法学评审智能体
-
评估临床研究的方法学质量(20项检查点)
-
-
-
-
- {selected.length === 0 && ( -
请至少选择一个智能体
- )} -
-
- ) -} - -// 任务列表页面 -const TaskListPage: React.FC = () => { - const navigate = useNavigate() - const [tasks, setTasks] = useState([]) - const [loading, setLoading] = useState(true) - const [uploading, setUploading] = useState(false) - const [showAgentModal, setShowAgentModal] = useState(false) - const [pendingFile, setPendingFile] = useState(null) - const [runningTaskId, setRunningTaskId] = useState(null) - - // 加载任务列表 - const loadTasks = async () => { - try { - setLoading(true) - const res = await fetch(`${API_BASE}/tasks`) - const data = await res.json() - if (data.success) { - setTasks(data.data || []) - } - } catch (error) { - message.error('加载任务列表失败') - } finally { - setLoading(false) - } - } - - useEffect(() => { - loadTasks() - }, []) - - // 上传文件 - const handleUpload = async (agents: string[]) => { - if (!pendingFile || agents.length === 0) return - - try { - setUploading(true) - const formData = new FormData() - formData.append('file', pendingFile) - formData.append('selectedAgents', JSON.stringify(agents)) - - const res = await fetch(`${API_BASE}/tasks`, { - method: 'POST', - body: formData, - }) - const data = await res.json() - - if (data.success) { - message.success('稿件上传成功,开始审稿...') - setShowAgentModal(false) - setPendingFile(null) - loadTasks() - } else { - message.error(data.error || '上传失败') - } - } catch (error) { - message.error('上传失败') - } finally { - setUploading(false) - } - } - - // 删除任务 - const handleDelete = async (taskId: string) => { - try { - const res = await fetch(`${API_BASE}/tasks/${taskId}`, { - method: 'DELETE', - }) - const data = await res.json() - if (data.success) { - message.success('删除成功') - loadTasks() - } - } catch (error) { - message.error('删除失败') - } - } - - // 运行审稿 - const handleRun = async (taskId: string, agents: string[]) => { - try { - setRunningTaskId(taskId) - const res = await fetch(`${API_BASE}/tasks/${taskId}/run`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ selectedAgents: agents }), - }) - const data = await res.json() - if (data.success) { - message.success('审稿任务已启动') - loadTasks() - } - } catch (error) { - message.error('启动审稿失败') - } finally { - setRunningTaskId(null) - } - } - - // 上传配置 - const uploadProps: UploadProps = { - beforeUpload: (file) => { - setPendingFile(file) - setShowAgentModal(true) - return false - }, - showUploadList: false, - accept: '.pdf,.doc,.docx,.txt', - } - - // 状态标签 - const getStatusTag = (status: string) => { - const config: Record = { - pending: { color: 'default', icon: , text: '待审稿' }, - processing: { color: 'processing', icon: , text: '审稿中' }, - completed: { color: 'success', icon: , text: '已完成' }, - failed: { color: 'error', icon: , text: '失败' }, - } - const c = config[status] || config.pending - return {c.text} - } - - // 表格列 - const columns = [ - { - title: '文件名', - dataIndex: 'fileName', - key: 'fileName', - render: (name: string) => ( - - - {name} - - ), - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - width: 120, - render: (status: string) => getStatusTag(status), - }, - { - title: '智能体', - dataIndex: 'selectedAgents', - key: 'selectedAgents', - width: 200, - render: (agents: string[]) => ( - - {agents?.includes('editorial') && 稿约} - {agents?.includes('methodology') && 方法学} - - ), - }, - { - title: '稿约评分', - dataIndex: 'editorialScore', - key: 'editorialScore', - width: 100, - render: (score: number) => score ? `${score}分` : '-', - }, - { - title: '创建时间', - dataIndex: 'createdAt', - key: 'createdAt', - width: 180, - render: (date: string) => new Date(date).toLocaleString('zh-CN'), - }, - { - title: '操作', - key: 'actions', - width: 200, - render: (_: any, record: ReviewTask) => ( - - {record.status === 'completed' && ( - - - - - - -
- - - - - - { - setShowAgentModal(false) - setPendingFile(null) - }} - onConfirm={handleUpload} - loading={uploading} - /> - - ) -} - -// 报告详情页面 -const ReportDetailPage: React.FC = () => { - const { taskId } = useParams<{ taskId: string }>() - const navigate = useNavigate() - const [task, setTask] = useState(null) - const [loading, setLoading] = useState(true) - - useEffect(() => { - const loadReport = async () => { - try { - const res = await fetch(`${API_BASE}/tasks/${taskId}/report`) - const data = await res.json() - if (data.success) { - setTask(data.data) - } - } catch (error) { - message.error('加载报告失败') - } finally { - setLoading(false) - } - } - if (taskId) loadReport() - }, [taskId]) - - if (loading) { - return ( -
- -
- ) - } - - if (!task) { - return ( -
- 报告不存在 -
- -
- ) - } - - return ( -
-
-
- - {task.fileName} -
- -
- - - {task.editorialReview && ( - - -
- 总体评分:{task.editorialScore || 0}分 -
-
- 总体评价 - {task.editorialReview.overallAssessment} -
-
- 详细评审结果 - {task.editorialReview.criteria?.map((item: any, index: number) => ( - -
- {item.name} - - {item.passed ? '通过' : '不通过'} - -
- {item.comment} -
- ))} -
-
- 修改建议 -
    - {task.editorialReview.suggestions?.map((s: string, i: number) => ( -
  • {s}
  • - ))} -
-
-
-
- )} - - {task.methodologyReview && ( - - -
- - 评审结论: - <Tag color={task.methodologyReview.overallConclusion === 'acceptable' ? 'success' : 'warning'}> - {task.methodologyReview.overallConclusion === 'acceptable' ? '可接受' : - task.methodologyReview.overallConclusion === 'needs_revision' ? '需修改' : '不可接受'} - </Tag> - -
-
- 总体评价 - {task.methodologyReview.overallAssessment} -
-
- 检查点结果 - {task.methodologyReview.checkpoints?.map((item: any, index: number) => ( - -
- {item.name} - - {item.status === 'pass' ? '通过' : - item.status === 'fail' ? '不通过' : '部分通过'} - -
- {item.comment} -
- ))} -
-
- 改进建议 -
    - {task.methodologyReview.recommendations?.map((r: string, i: number) => ( -
  • {r}
  • - ))} -
-
-
-
- )} -
-
- ) -} +import { Routes, Route } from 'react-router-dom'; +import Dashboard from './pages/Dashboard'; // 模块主入口 const RVWModule: React.FC = () => { return ( - } /> - } /> + } /> + {/* 可以在这里添加更多路由,如 /report/:taskId */} - ) -} - -export default RVWModule + ); +}; +export default RVWModule; diff --git a/frontend-v2/src/modules/rvw/pages/Dashboard.tsx b/frontend-v2/src/modules/rvw/pages/Dashboard.tsx new file mode 100644 index 00000000..d813d284 --- /dev/null +++ b/frontend-v2/src/modules/rvw/pages/Dashboard.tsx @@ -0,0 +1,284 @@ +/** + * RVW审稿系统 - 主Dashboard页面 + */ +import { useState, useEffect, useCallback } from 'react'; +import { message } from 'antd'; +import { + Sidebar, + Header, + FilterChips, + TaskTable, + BatchToolbar, + AgentModal, + ReportDetail, + TaskDetail, +} from '../components'; +import * as api from '../api'; +import type { ReviewTask, ReviewReport, TaskFilters, AgentType } from '../types'; +import '../styles/index.css'; + +export default function Dashboard() { + // ==================== State ==================== + const [currentView, setCurrentView] = useState<'dashboard' | 'archive'>('dashboard'); + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedIds, setSelectedIds] = useState([]); + const [filters, setFilters] = useState({ status: 'all', timeRange: 'all' }); + const [agentModalVisible, setAgentModalVisible] = useState(false); + const [pendingTaskForRun, setPendingTaskForRun] = useState(null); + + // 报告详情 + const [reportDetail, setReportDetail] = useState(null); + + // 任务详情(支持进度显示) + const [viewingTask, setViewingTask] = useState(null); + const [currentJobId, setCurrentJobId] = useState(null); + + // ==================== 数据加载 ==================== + const loadTasks = useCallback(async () => { + try { + setLoading(true); + const data = await api.getTasks(filters.status !== 'all' ? filters.status : undefined); + + // 时间筛选 + let filtered = data; + if (filters.timeRange === 'today') { + const today = new Date().toDateString(); + filtered = data.filter(t => new Date(t.createdAt).toDateString() === today); + } else if (filters.timeRange === 'week') { + const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; + filtered = data.filter(t => new Date(t.createdAt).getTime() > weekAgo); + } + + setTasks(filtered); + } catch (error) { + console.error('加载任务失败:', error); + message.error('加载任务列表失败'); + } finally { + setLoading(false); + } + }, [filters]); + + useEffect(() => { + loadTasks(); + }, [loadTasks]); + + // 轮询更新进行中的任务 + useEffect(() => { + const processingTasks = tasks.filter(t => + t.status === 'extracting' || t.status === 'reviewing' + ); + + if (processingTasks.length === 0) return; + + const interval = setInterval(async () => { + for (const task of processingTasks) { + try { + const updated = await api.getTask(task.id); + setTasks(prev => prev.map(t => t.id === updated.id ? updated : t)); + } catch (error) { + console.error('更新任务状态失败:', error); + } + } + }, 3000); + + return () => clearInterval(interval); + }, [tasks]); + + // ==================== 统计数据 ==================== + const counts = { + all: tasks.length, + pending: tasks.filter(t => t.status === 'pending').length, + completed: tasks.filter(t => t.status === 'completed').length, + }; + + // ==================== 事件处理 ==================== + const handleUpload = async (files: FileList) => { + const uploadPromises = Array.from(files).map(async (file) => { + try { + message.loading({ content: `正在上传 ${file.name}...`, key: file.name }); + await api.uploadManuscript(file); + message.success({ content: `${file.name} 上传成功`, key: file.name, duration: 2 }); + } catch (error: any) { + message.error({ content: `${file.name} 上传失败: ${error.message}`, key: file.name, duration: 3 }); + } + }); + + await Promise.all(uploadPromises); + loadTasks(); + }; + + const handleRunTask = (task: ReviewTask) => { + setPendingTaskForRun(task); + setAgentModalVisible(true); + }; + + const handleRunBatch = () => { + setPendingTaskForRun(null); // 批量模式 + setAgentModalVisible(true); + }; + + const handleConfirmRun = async (agents: AgentType[]) => { + // 🔥 保存到局部变量,避免onClose后丢失 + const taskToRun = pendingTaskForRun; + + // 立即关闭弹窗 + setAgentModalVisible(false); + setPendingTaskForRun(null); + + try { + if (taskToRun) { + // 单个任务 - 启动后跳转到详情页显示进度 + message.loading({ content: '正在启动审查...', key: 'run' }); + const { jobId } = await api.runTask(taskToRun.id, agents); + message.success({ content: '审查已启动', key: 'run', duration: 2 }); + + // 更新任务状态后跳转到详情页(传递jobId) + const updatedTask = await api.getTask(taskToRun.id); + setCurrentJobId(jobId); + setViewingTask(updatedTask); + return; + } else { + // 批量任务 + const pendingIds = selectedIds.filter(id => { + const task = tasks.find(t => t.id === id); + return task && task.status === 'pending'; + }); + + if (pendingIds.length === 0) { + message.warning('没有待处理的任务'); + return; + } + + message.loading({ content: `正在启动 ${pendingIds.length} 个任务...`, key: 'run' }); + await api.batchRunTasks(pendingIds, agents); + message.success({ content: `${pendingIds.length} 个任务已启动`, key: 'run', duration: 2 }); + setSelectedIds([]); + } + + loadTasks(); + } catch (error: any) { + message.error({ content: error.message || '启动失败', key: 'run', duration: 3 }); + } + }; + + const handleViewReport = async (task: ReviewTask) => { + // 直接使用TaskDetail视图(支持进度和报告) + setViewingTask(task); + }; + + const handleDeleteTask = async (task: ReviewTask) => { + if (!window.confirm(`确定要删除 "${task.fileName}" 吗?`)) { + return; + } + + try { + message.loading({ content: '正在删除...', key: 'delete' }); + await api.deleteTask(task.id); + message.success({ content: '删除成功', key: 'delete', duration: 2 }); + loadTasks(); + } catch (error: any) { + message.error({ content: error.message || '删除失败', key: 'delete', duration: 3 }); + } + }; + + const handleBackToList = () => { + setReportDetail(null); + }; + + // 返回列表并刷新 + const handleBackFromDetail = () => { + setViewingTask(null); + setCurrentJobId(null); + loadTasks(); + }; + + // ==================== 渲染 ==================== + + // 任务详情视图(支持进度显示) + if (viewingTask) { + return ( +
+ + +
+ ); + } + + // 报告详情视图(旧版,保留兼容) + if (reportDetail) { + return ( +
+ + +
+ ); + } + + // 主仪表盘视图 + return ( +
+ + +
+
+ {/* 顶部操作区 */} +
+
+ +
+ + {/* 列表区域 */} +
+ {loading ? ( +
+
+
+ ) : ( + + )} +
+
+
+ + {/* 批量操作工具栏 */} + setSelectedIds([])} + /> + + {/* 智能体选择弹窗 */} + { + setAgentModalVisible(false); + setPendingTaskForRun(null); + }} + onConfirm={handleConfirmRun} + /> +
+ ); +} + diff --git a/frontend-v2/src/modules/rvw/styles/index.css b/frontend-v2/src/modules/rvw/styles/index.css new file mode 100644 index 00000000..fa2a0483 --- /dev/null +++ b/frontend-v2/src/modules/rvw/styles/index.css @@ -0,0 +1,233 @@ +/** + * RVW模块样式 + * 基于原型图 V7 的高保真还原 + */ + +/* ==================== 状态标签 ==================== */ +.tag { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + line-height: 1.5; + border: 1px solid transparent; +} + +.tag-blue { + background: #eff6ff; + color: #1d4ed8; + border-color: #dbeafe; +} + +.tag-purple { + background: #f5f3ff; + color: #6d28d9; + border-color: #ede9fe; +} + +.tag-green { + background: #f0fdf4; + color: #15803d; + border-color: #dcfce7; +} + +.tag-amber { + background: #fffbeb; + color: #b45309; + border-color: #fef3c7; +} + +.tag-red { + background: #fef2f2; + color: #dc2626; + border-color: #fecaca; +} + +.tag-gray { + background: #f8fafc; + color: #64748b; + border-color: #e2e8f0; +} + +/* ==================== 筛选 Chips ==================== */ +.filter-chip { + padding: 4px 12px; + border-radius: 9999px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: 1px solid transparent; + color: #64748b; + background: transparent; +} + +.filter-chip:hover { + background-color: #f1f5f9; + color: #0f172a; +} + +.filter-chip.active { + background-color: #eff6ff; + color: #2563eb; + border-color: #bfdbfe; + font-weight: 600; +} + +/* ==================== 侧边栏 Tooltip ==================== */ +.sidebar-tooltip { + position: absolute; + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 12px; + background: #1e293b; + color: white; + padding: 6px 10px; + border-radius: 6px; + font-size: 12px; + white-space: nowrap; + z-index: 50; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); + opacity: 0; + visibility: hidden; + transition: opacity 0.2s, visibility 0.2s; + pointer-events: none; +} + +.sidebar-btn:hover .sidebar-tooltip { + opacity: 1; + visibility: visible; +} + +/* ==================== 动画 ==================== */ +.fade-in { + animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} + +.slide-up { + animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1); +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* ==================== 表格悬停效果 ==================== */ +.task-table tbody tr { + transition: background-color 0.15s ease; +} + +.task-table tbody tr:hover { + background-color: #f8fafc; +} + +.task-table tbody tr.selected { + background-color: #eff6ff; +} + +/* ==================== 评分环 ==================== */ +.score-circle { + width: 80px; + height: 80px; + border-radius: 50%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.score-circle.pass { + border-color: #22c55e; + background: #f0fdf4; + color: #15803d; +} + +.score-circle.warn { + border-color: #f59e0b; + background: #fffbeb; + color: #b45309; +} + +.score-circle.fail { + border-color: #ef4444; + background: #fef2f2; + color: #dc2626; +} + +/* ==================== 按钮样式 ==================== */ +.btn-primary { + background-color: #4f46e5; + color: white; + font-weight: 600; + padding: 8px 16px; + border-radius: 8px; + transition: all 0.2s; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +.btn-primary:hover { + background-color: #4338ca; + transform: translateY(-1px); +} + +.btn-secondary { + background-color: white; + color: #374151; + font-weight: 500; + padding: 8px 16px; + border-radius: 8px; + border: 1px solid #d1d5db; + transition: all 0.2s; +} + +.btn-secondary:hover { + background-color: #f9fafb; + border-color: #9ca3af; +} + +/* ==================== 滚动条美化 ==================== */ +.overflow-auto::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.overflow-auto::-webkit-scrollbar-track { + background: #f1f5f9; + border-radius: 4px; +} + +.overflow-auto::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +.overflow-auto::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* ==================== 响应式调整 ==================== */ +@media (max-width: 1024px) { + .sidebar-tooltip { + display: none; + } +} + diff --git a/frontend-v2/src/modules/rvw/types/index.ts b/frontend-v2/src/modules/rvw/types/index.ts new file mode 100644 index 00000000..b755f83b --- /dev/null +++ b/frontend-v2/src/modules/rvw/types/index.ts @@ -0,0 +1,95 @@ +/** + * RVW模块类型定义 + */ + +// 任务状态 +export type TaskStatus = + | 'pending' // 待处理 + | 'extracting' // 提取文本中 + | 'reviewing' // 审查中 + | 'reviewing_editorial' // 正在审查稿约规范性 + | 'reviewing_methodology' // 正在审查方法学 + | 'completed' // 已完成 + | 'failed'; // 失败 + +// 智能体类型 +export type AgentType = 'editorial' | 'methodology'; + +// 审查任务 +export interface ReviewTask { + id: string; + fileName: string; + fileSize: number; + status: TaskStatus; + selectedAgents: AgentType[]; + wordCount?: number; + overallScore?: number; + editorialScore?: number; + methodologyScore?: number; // 方法学分数 + methodologyStatus?: string; // 方法学状态(通过/存疑/不通过) + errorMessage?: string; + createdAt: string; + completedAt?: string; + durationSeconds?: number; +} + +// 规范性评估项 +export interface EditorialItem { + criterion: string; + status: 'pass' | 'warning' | 'fail'; + score: number; + issues?: string[]; + suggestions?: string[]; +} + +// 规范性评估结果 +export interface EditorialReviewResult { + 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 MethodologyReviewResult { + overall_score: number; + summary: string; + parts: MethodologyPart[]; +} + +// 完整审查报告 +export interface ReviewReport extends ReviewTask { + editorialReview?: EditorialReviewResult; + methodologyReview?: MethodologyReviewResult; + modelUsed?: string; +} + +// API响应 +export interface ApiResponse { + success: boolean; + data?: T; + message?: string; + error?: string; +} + +// 筛选条件 +export interface TaskFilters { + status: 'all' | 'pending' | 'completed'; + timeRange: 'all' | 'today' | 'week'; +} + diff --git a/frontend-v2/src/shared/components/index.ts b/frontend-v2/src/shared/components/index.ts index f605b9bb..678321f5 100644 --- a/frontend-v2/src/shared/components/index.ts +++ b/frontend-v2/src/shared/components/index.ts @@ -51,6 +51,8 @@ export { default as Placeholder } from './Placeholder'; + + diff --git a/frontend-v2/src/vite-env.d.ts b/frontend-v2/src/vite-env.d.ts index 2e41cc87..0d80ec41 100644 --- a/frontend-v2/src/vite-env.d.ts +++ b/frontend-v2/src/vite-env.d.ts @@ -31,6 +31,8 @@ interface ImportMeta { + + diff --git a/frontend-v2/tsconfig.tsbuildinfo b/frontend-v2/tsconfig.tsbuildinfo index ed543b9b..d2c5d6b8 100644 --- a/frontend-v2/tsconfig.tsbuildinfo +++ b/frontend-v2/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/framework/layout/mainlayout.tsx","./src/framework/layout/topnavigation.tsx","./src/framework/modules/errorboundary.tsx","./src/framework/modules/moduleerrorfallback.tsx","./src/framework/modules/moduleregistry.ts","./src/framework/modules/types.ts","./src/framework/permission/permissioncontext.tsx","./src/framework/permission/index.ts","./src/framework/permission/types.ts","./src/framework/permission/usepermission.ts","./src/framework/router/permissiondenied.tsx","./src/framework/router/routeguard.tsx","./src/framework/router/index.ts","./src/modules/aia/index.tsx","./src/modules/asl/index.tsx","./src/modules/asl/api/index.ts","./src/modules/asl/components/asllayout.tsx","./src/modules/asl/components/conclusiontag.tsx","./src/modules/asl/components/detailreviewdrawer.tsx","./src/modules/asl/components/fulltextdetaildrawer.tsx","./src/modules/asl/components/judgmentbadge.tsx","./src/modules/asl/hooks/usefulltextresults.ts","./src/modules/asl/hooks/usefulltexttask.ts","./src/modules/asl/hooks/usescreeningresults.ts","./src/modules/asl/hooks/usescreeningtask.ts","./src/modules/asl/pages/fulltextprogress.tsx","./src/modules/asl/pages/fulltextresults.tsx","./src/modules/asl/pages/fulltextsettings.tsx","./src/modules/asl/pages/fulltextworkbench.tsx","./src/modules/asl/pages/screeningresults.tsx","./src/modules/asl/pages/screeningworkbench.tsx","./src/modules/asl/pages/titlescreeningsettings.tsx","./src/modules/asl/types/index.ts","./src/modules/asl/utils/excelexport.ts","./src/modules/asl/utils/excelutils.ts","./src/modules/asl/utils/tabletransform.ts","./src/modules/dc/index.tsx","./src/modules/dc/api/toolb.ts","./src/modules/dc/api/toolc.ts","./src/modules/dc/components/assetlibrary.tsx","./src/modules/dc/components/tasklist.tsx","./src/modules/dc/components/toolcard.tsx","./src/modules/dc/hooks/useassets.ts","./src/modules/dc/hooks/userecenttasks.ts","./src/modules/dc/pages/portal.tsx","./src/modules/dc/pages/tool-b/step1upload.tsx","./src/modules/dc/pages/tool-b/step2schema.tsx","./src/modules/dc/pages/tool-b/step3processing.tsx","./src/modules/dc/pages/tool-b/step4verify.tsx","./src/modules/dc/pages/tool-b/step5result.tsx","./src/modules/dc/pages/tool-b/index.tsx","./src/modules/dc/pages/tool-b/components/stepindicator.tsx","./src/modules/dc/pages/tool-c/index.tsx","./src/modules/dc/pages/tool-c/components/binningdialog.tsx","./src/modules/dc/pages/tool-c/components/binningdialog_improved.tsx","./src/modules/dc/pages/tool-c/components/computedialog.tsx","./src/modules/dc/pages/tool-c/components/conditionaldialog.tsx","./src/modules/dc/pages/tool-c/components/datagrid.tsx","./src/modules/dc/pages/tool-c/components/dropnadialog.tsx","./src/modules/dc/pages/tool-c/components/filterdialog.tsx","./src/modules/dc/pages/tool-c/components/header.tsx","./src/modules/dc/pages/tool-c/components/metrictimepanel.tsx","./src/modules/dc/pages/tool-c/components/missingvaluedialog.tsx","./src/modules/dc/pages/tool-c/components/multimetricpanel.tsx","./src/modules/dc/pages/tool-c/components/pivotdialog.tsx","./src/modules/dc/pages/tool-c/components/pivotpanel.tsx","./src/modules/dc/pages/tool-c/components/recodedialog.tsx","./src/modules/dc/pages/tool-c/components/sidebar.tsx","./src/modules/dc/pages/tool-c/components/streamingsteps.tsx","./src/modules/dc/pages/tool-c/components/toolbar.tsx","./src/modules/dc/pages/tool-c/components/transformdialog.tsx","./src/modules/dc/pages/tool-c/components/unpivotpanel.tsx","./src/modules/dc/pages/tool-c/hooks/usesessionstatus.ts","./src/modules/dc/pages/tool-c/types/index.ts","./src/modules/dc/types/portal.ts","./src/modules/pkb/index.tsx","./src/modules/ssa/index.tsx","./src/modules/st/index.tsx","./src/pages/homepage.tsx","./src/shared/components/placeholder.tsx","./src/shared/components/index.ts","./src/shared/components/chat/chatcontainer.tsx","./src/shared/components/chat/codeblockrenderer.tsx","./src/shared/components/chat/messagerenderer.tsx","./src/shared/components/chat/index.ts","./src/shared/components/chat/types.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/framework/layout/mainlayout.tsx","./src/framework/layout/topnavigation.tsx","./src/framework/modules/errorboundary.tsx","./src/framework/modules/moduleerrorfallback.tsx","./src/framework/modules/moduleregistry.ts","./src/framework/modules/types.ts","./src/framework/permission/permissioncontext.tsx","./src/framework/permission/index.ts","./src/framework/permission/types.ts","./src/framework/permission/usepermission.ts","./src/framework/router/permissiondenied.tsx","./src/framework/router/routeguard.tsx","./src/framework/router/index.ts","./src/modules/aia/index.tsx","./src/modules/asl/index.tsx","./src/modules/asl/api/index.ts","./src/modules/asl/components/asllayout.tsx","./src/modules/asl/components/conclusiontag.tsx","./src/modules/asl/components/detailreviewdrawer.tsx","./src/modules/asl/components/fulltextdetaildrawer.tsx","./src/modules/asl/components/judgmentbadge.tsx","./src/modules/asl/hooks/usefulltextresults.ts","./src/modules/asl/hooks/usefulltexttask.ts","./src/modules/asl/hooks/usescreeningresults.ts","./src/modules/asl/hooks/usescreeningtask.ts","./src/modules/asl/pages/fulltextprogress.tsx","./src/modules/asl/pages/fulltextresults.tsx","./src/modules/asl/pages/fulltextsettings.tsx","./src/modules/asl/pages/fulltextworkbench.tsx","./src/modules/asl/pages/screeningresults.tsx","./src/modules/asl/pages/screeningworkbench.tsx","./src/modules/asl/pages/titlescreeningsettings.tsx","./src/modules/asl/types/index.ts","./src/modules/asl/utils/excelexport.ts","./src/modules/asl/utils/excelutils.ts","./src/modules/asl/utils/tabletransform.ts","./src/modules/dc/index.tsx","./src/modules/dc/api/toolb.ts","./src/modules/dc/api/toolc.ts","./src/modules/dc/components/assetlibrary.tsx","./src/modules/dc/components/tasklist.tsx","./src/modules/dc/components/toolcard.tsx","./src/modules/dc/hooks/useassets.ts","./src/modules/dc/hooks/userecenttasks.ts","./src/modules/dc/pages/portal.tsx","./src/modules/dc/pages/tool-b/step1upload.tsx","./src/modules/dc/pages/tool-b/step2schema.tsx","./src/modules/dc/pages/tool-b/step3processing.tsx","./src/modules/dc/pages/tool-b/step4verify.tsx","./src/modules/dc/pages/tool-b/step5result.tsx","./src/modules/dc/pages/tool-b/index.tsx","./src/modules/dc/pages/tool-b/components/stepindicator.tsx","./src/modules/dc/pages/tool-c/index.tsx","./src/modules/dc/pages/tool-c/components/binningdialog.tsx","./src/modules/dc/pages/tool-c/components/binningdialog_improved.tsx","./src/modules/dc/pages/tool-c/components/computedialog.tsx","./src/modules/dc/pages/tool-c/components/conditionaldialog.tsx","./src/modules/dc/pages/tool-c/components/datagrid.tsx","./src/modules/dc/pages/tool-c/components/dropnadialog.tsx","./src/modules/dc/pages/tool-c/components/filterdialog.tsx","./src/modules/dc/pages/tool-c/components/header.tsx","./src/modules/dc/pages/tool-c/components/metrictimepanel.tsx","./src/modules/dc/pages/tool-c/components/missingvaluedialog.tsx","./src/modules/dc/pages/tool-c/components/multimetricpanel.tsx","./src/modules/dc/pages/tool-c/components/pivotdialog.tsx","./src/modules/dc/pages/tool-c/components/pivotpanel.tsx","./src/modules/dc/pages/tool-c/components/recodedialog.tsx","./src/modules/dc/pages/tool-c/components/sidebar.tsx","./src/modules/dc/pages/tool-c/components/streamingsteps.tsx","./src/modules/dc/pages/tool-c/components/toolbar.tsx","./src/modules/dc/pages/tool-c/components/transformdialog.tsx","./src/modules/dc/pages/tool-c/components/unpivotpanel.tsx","./src/modules/dc/pages/tool-c/hooks/usesessionstatus.ts","./src/modules/dc/pages/tool-c/types/index.ts","./src/modules/dc/types/portal.ts","./src/modules/pkb/index.tsx","./src/modules/pkb/api/knowledgebaseapi.ts","./src/modules/pkb/components/createkbdialog.tsx","./src/modules/pkb/components/documentlist.tsx","./src/modules/pkb/components/documentupload.tsx","./src/modules/pkb/components/editkbdialog.tsx","./src/modules/pkb/components/knowledgebaselist.tsx","./src/modules/pkb/components/workspace/batchmode.tsx","./src/modules/pkb/components/workspace/batchmodecomplete.tsx","./src/modules/pkb/components/workspace/deepreadmode.tsx","./src/modules/pkb/components/workspace/fulltextmode.tsx","./src/modules/pkb/components/workspace/workmodeselector.tsx","./src/modules/pkb/hooks/useworkmode.ts","./src/modules/pkb/pages/dashboardpage.tsx","./src/modules/pkb/pages/knowledgepage.tsx","./src/modules/pkb/pages/workspacepage.tsx","./src/modules/pkb/stores/useknowledgebasestore.ts","./src/modules/pkb/types/workspace.ts","./src/modules/rvw/index.tsx","./src/modules/rvw/api/index.ts","./src/modules/rvw/components/agentmodal.tsx","./src/modules/rvw/components/batchtoolbar.tsx","./src/modules/rvw/components/editorialreport.tsx","./src/modules/rvw/components/filterchips.tsx","./src/modules/rvw/components/header.tsx","./src/modules/rvw/components/methodologyreport.tsx","./src/modules/rvw/components/reportdetail.tsx","./src/modules/rvw/components/scorering.tsx","./src/modules/rvw/components/sidebar.tsx","./src/modules/rvw/components/taskdetail.tsx","./src/modules/rvw/components/tasktable.tsx","./src/modules/rvw/components/index.ts","./src/modules/rvw/pages/dashboard.tsx","./src/modules/rvw/types/index.ts","./src/modules/ssa/index.tsx","./src/modules/st/index.tsx","./src/pages/homepage.tsx","./src/shared/components/placeholder.tsx","./src/shared/components/index.ts","./src/shared/components/chat/chatcontainer.tsx","./src/shared/components/chat/codeblockrenderer.tsx","./src/shared/components/chat/messagerenderer.tsx","./src/shared/components/chat/index.ts","./src/shared/components/chat/types.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file diff --git a/frontend/src/pages/rvw/Dashboard.tsx b/frontend/src/pages/rvw/Dashboard.tsx index 8837c485..77c07b31 100644 --- a/frontend/src/pages/rvw/Dashboard.tsx +++ b/frontend/src/pages/rvw/Dashboard.tsx @@ -11,6 +11,7 @@ import { BatchToolbar, AgentModal, ReportDetail, + TaskDetail, } from './components'; import * as api from './api'; import type { ReviewTask, ReviewReport, TaskFilters, AgentType } from './types'; @@ -28,6 +29,10 @@ export default function Dashboard() { // 报告详情 const [reportDetail, setReportDetail] = useState(null); + + // 任务详情(支持进度显示) + const [viewingTask, setViewingTask] = useState(null); + const [currentJobId, setCurrentJobId] = useState(null); // ==================== 数据加载 ==================== const loadTasks = useCallback(async () => { @@ -114,12 +119,25 @@ export default function Dashboard() { }; const handleConfirmRun = async (agents: AgentType[]) => { + // 🔥 保存到局部变量,避免onClose后丢失 + const taskToRun = pendingTaskForRun; + + // 立即关闭弹窗 + setAgentModalVisible(false); + setPendingTaskForRun(null); + try { - if (pendingTaskForRun) { - // 单个任务 + if (taskToRun) { + // 单个任务 - 启动后跳转到详情页显示进度 message.loading({ content: '正在启动审查...', key: 'run' }); - await api.runTask(pendingTaskForRun.id, agents); + const { jobId } = await api.runTask(taskToRun.id, agents); message.success({ content: '审查已启动', key: 'run', duration: 2 }); + + // 更新任务状态后跳转到详情页(传递jobId) + const updatedTask = await api.getTask(taskToRun.id); + setCurrentJobId(jobId); + setViewingTask(updatedTask); + return; } else { // 批量任务 const pendingIds = selectedIds.filter(id => { @@ -145,13 +163,22 @@ export default function Dashboard() { }; const handleViewReport = async (task: ReviewTask) => { + // 直接使用TaskDetail视图(支持进度和报告) + setViewingTask(task); + }; + + const handleDeleteTask = async (task: ReviewTask) => { + if (!window.confirm(`确定要删除 "${task.fileName}" 吗?`)) { + return; + } + try { - message.loading({ content: '加载报告中...', key: 'report' }); - const report = await api.getTaskReport(task.id); - setReportDetail(report); - message.destroy('report'); + message.loading({ content: '正在删除...', key: 'delete' }); + await api.deleteTask(task.id); + message.success({ content: '删除成功', key: 'delete', duration: 2 }); + loadTasks(); } catch (error: any) { - message.error({ content: '加载报告失败', key: 'report', duration: 3 }); + message.error({ content: error.message || '删除失败', key: 'delete', duration: 3 }); } }; @@ -159,9 +186,29 @@ export default function Dashboard() { setReportDetail(null); }; + // 返回列表并刷新 + const handleBackFromDetail = () => { + setViewingTask(null); + setCurrentJobId(null); + loadTasks(); + }; + // ==================== 渲染 ==================== - // 报告详情视图 + // 任务详情视图(支持进度显示) + if (viewingTask) { + return ( +
+ + +
+ ); + } + + // 报告详情视图(旧版,保留兼容) if (reportDetail) { return (
@@ -207,6 +254,7 @@ export default function Dashboard() { onSelectChange={setSelectedIds} onViewReport={handleViewReport} onRunTask={handleRunTask} + onDeleteTask={handleDeleteTask} /> )}
@@ -234,3 +282,4 @@ export default function Dashboard() { ); } + diff --git a/frontend/src/pages/rvw/api.ts b/frontend/src/pages/rvw/api.ts index f6d5b332..399ab883 100644 --- a/frontend/src/pages/rvw/api.ts +++ b/frontend/src/pages/rvw/api.ts @@ -44,17 +44,18 @@ export async function getTaskReport(taskId: string): Promise { return response.data.data!; } -// 运行审查任务 -export async function runTask(taskId: string, agents: AgentType[]): Promise { - const response = await axios.post>(`${API_BASE}/tasks/${taskId}/run`, { agents }); +// 运行审查任务(返回jobId供轮询) +export async function runTask(taskId: string, agents: AgentType[]): Promise<{ taskId: string; jobId: string }> { + const response = await axios.post>(`${API_BASE}/tasks/${taskId}/run`, { agents }); if (!response.data.success) { throw new Error(response.data.error || '运行失败'); } + return response.data.data!; } // 批量运行审查任务 export async function batchRunTasks(taskIds: string[], agents: AgentType[]): Promise { - const response = await axios.post>(`${API_BASE}/tasks/batch-run`, { taskIds, agents }); + const response = await axios.post>(`${API_BASE}/tasks/batch/run`, { taskIds, agents }); if (!response.data.success) { throw new Error(response.data.error || '批量运行失败'); } @@ -127,3 +128,4 @@ export function formatTime(dateStr: string): string { return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }); } + diff --git a/frontend/src/pages/rvw/components/AgentModal.tsx b/frontend/src/pages/rvw/components/AgentModal.tsx index 070622c9..f2acd2b8 100644 --- a/frontend/src/pages/rvw/components/AgentModal.tsx +++ b/frontend/src/pages/rvw/components/AgentModal.tsx @@ -27,8 +27,8 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm }: A }; const handleConfirm = () => { + // 只调用onConfirm,让调用方控制关闭时机 onConfirm(selectedAgents); - onClose(); }; if (!visible) return null; @@ -121,3 +121,4 @@ export default function AgentModal({ visible, taskCount, onClose, onConfirm }: A ); } + diff --git a/frontend/src/pages/rvw/components/BatchToolbar.tsx b/frontend/src/pages/rvw/components/BatchToolbar.tsx index b33998ef..631c9c91 100644 --- a/frontend/src/pages/rvw/components/BatchToolbar.tsx +++ b/frontend/src/pages/rvw/components/BatchToolbar.tsx @@ -41,3 +41,5 @@ export default function BatchToolbar({ selectedCount, onRunBatch, onClearSelecti ); } + + diff --git a/frontend/src/pages/rvw/components/EditorialReport.tsx b/frontend/src/pages/rvw/components/EditorialReport.tsx index 9061c596..e4ba18ab 100644 --- a/frontend/src/pages/rvw/components/EditorialReport.tsx +++ b/frontend/src/pages/rvw/components/EditorialReport.tsx @@ -106,3 +106,5 @@ export default function EditorialReport({ data }: EditorialReportProps) { ); } + + diff --git a/frontend/src/pages/rvw/components/FilterChips.tsx b/frontend/src/pages/rvw/components/FilterChips.tsx index f9a4fb51..5282d0ad 100644 --- a/frontend/src/pages/rvw/components/FilterChips.tsx +++ b/frontend/src/pages/rvw/components/FilterChips.tsx @@ -64,3 +64,5 @@ export default function FilterChips({ filters, counts, onFilterChange }: FilterC ); } + + diff --git a/frontend/src/pages/rvw/components/Header.tsx b/frontend/src/pages/rvw/components/Header.tsx index 8cf751ad..9da9066b 100644 --- a/frontend/src/pages/rvw/components/Header.tsx +++ b/frontend/src/pages/rvw/components/Header.tsx @@ -54,3 +54,5 @@ export default function Header({ onUpload }: HeaderProps) { ); } + + diff --git a/frontend/src/pages/rvw/components/ReportDetail.tsx b/frontend/src/pages/rvw/components/ReportDetail.tsx index ca27b024..f92a5b40 100644 --- a/frontend/src/pages/rvw/components/ReportDetail.tsx +++ b/frontend/src/pages/rvw/components/ReportDetail.tsx @@ -108,3 +108,5 @@ export default function ReportDetail({ report, onBack }: ReportDetailProps) { ); } + + diff --git a/frontend/src/pages/rvw/components/ScoreRing.tsx b/frontend/src/pages/rvw/components/ScoreRing.tsx index 3af1fdc3..10440328 100644 --- a/frontend/src/pages/rvw/components/ScoreRing.tsx +++ b/frontend/src/pages/rvw/components/ScoreRing.tsx @@ -36,3 +36,5 @@ export default function ScoreRing({ score, size = 'medium', showLabel = true }: ); } + + diff --git a/frontend/src/pages/rvw/components/Sidebar.tsx b/frontend/src/pages/rvw/components/Sidebar.tsx index 959a0186..7461951c 100644 --- a/frontend/src/pages/rvw/components/Sidebar.tsx +++ b/frontend/src/pages/rvw/components/Sidebar.tsx @@ -71,3 +71,5 @@ export default function Sidebar({ currentView, onViewChange, onSettingsClick }: ); } + + diff --git a/frontend/src/pages/rvw/components/TaskDetail.tsx b/frontend/src/pages/rvw/components/TaskDetail.tsx new file mode 100644 index 00000000..39b29bb5 --- /dev/null +++ b/frontend/src/pages/rvw/components/TaskDetail.tsx @@ -0,0 +1,295 @@ +/** + * 任务详情页组件 + * 支持显示审稿进度和结果 + */ +import { useState, useEffect } from 'react'; +import { ArrowLeft, FileCheck, Clock, AlertCircle, CheckCircle, Loader2, FileText, Bot } from 'lucide-react'; +import type { ReviewTask, ReviewReport, TaskStatus } from '../types'; +import EditorialReport from './EditorialReport'; +import MethodologyReport from './MethodologyReport'; +import * as api from '../api'; +import { message } from 'antd'; + +interface TaskDetailProps { + task: ReviewTask; + jobId?: string | null; // pg-boss 任务ID(可选,用于更精确的状态轮询) + onBack: () => void; +} + +// 状态信息映射 +const STATUS_INFO: Record = { + pending: { label: '等待开始', color: 'text-slate-500', icon: Clock }, + extracting: { label: '正在提取文档', color: 'text-blue-500', icon: Loader2 }, + reviewing: { label: '正在初始化审查', color: 'text-indigo-500', icon: Loader2 }, + reviewing_editorial: { label: '正在审查稿约规范性', color: 'text-indigo-500', icon: Bot }, + reviewing_methodology: { label: '正在审查方法学', color: 'text-purple-500', icon: Bot }, + completed: { label: '审查完成', color: 'text-green-600', icon: CheckCircle }, + failed: { label: '审查失败', color: 'text-red-500', icon: AlertCircle }, +}; + +// 进度步骤 +const PROGRESS_STEPS = [ + { key: 'upload', label: '上传文档', status: 'completed' as const }, + { key: 'extract', label: '文本提取', status: 'completed' as const }, + { key: 'editorial', label: '稿约规范性', status: 'pending' as const }, + { key: 'methodology', label: '方法学评估', status: 'pending' as const }, +]; + +export default function TaskDetail({ task: initialTask, jobId, onBack }: TaskDetailProps) { + const [task, setTask] = useState(initialTask); + const [report, setReport] = useState(null); + const [activeTab, setActiveTab] = useState<'editorial' | 'methodology'>('editorial'); + const [elapsedTime, setElapsedTime] = useState(0); + + const isProcessing = ['extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'].includes(task.status); + const isCompleted = task.status === 'completed'; + const isFailed = task.status === 'failed'; + + // 轮询任务状态 + useEffect(() => { + if (!isProcessing) return; + + const interval = setInterval(async () => { + try { + const updated = await api.getTask(task.id); + setTask(updated); + + // 如果完成了,加载报告 + if (updated.status === 'completed') { + const reportData = await api.getTaskReport(task.id); + setReport(reportData); + } + } catch (error) { + console.error('更新任务状态失败:', error); + } + }, 2000); + + return () => clearInterval(interval); + }, [task.id, isProcessing]); + + // 计时器 + useEffect(() => { + if (!isProcessing) return; + + const start = Date.now(); + const interval = setInterval(() => { + setElapsedTime(Math.floor((Date.now() - start) / 1000)); + }, 1000); + + return () => clearInterval(interval); + }, [isProcessing]); + + // 完成时加载报告 + useEffect(() => { + if (isCompleted && !report) { + api.getTaskReport(task.id).then(setReport).catch(() => { + message.error('加载报告失败'); + }); + } + }, [isCompleted, task.id, report]); + + // 获取进度步骤状态 + const getStepStatus = (stepKey: string): 'completed' | 'active' | 'pending' => { + if (task.status === 'pending') { + return stepKey === 'upload' ? 'completed' : 'pending'; + } + if (task.status === 'extracting') { + if (stepKey === 'upload') return 'completed'; + if (stepKey === 'extract') return 'active'; + return 'pending'; + } + if (task.status === 'reviewing') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + if (stepKey === 'editorial') return 'active'; + return 'pending'; + } + if (task.status === 'reviewing_editorial') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + if (stepKey === 'editorial') return 'active'; + return 'pending'; + } + if (task.status === 'reviewing_methodology') { + if (['upload', 'extract', 'editorial'].includes(stepKey)) return 'completed'; + if (stepKey === 'methodology') return 'active'; + return 'pending'; + } + if (task.status === 'completed') return 'completed'; + if (task.status === 'failed') { + if (['upload', 'extract'].includes(stepKey)) return 'completed'; + return 'pending'; + } + return 'pending'; + }; + + // 格式化时间 + const formatTime = (seconds: number): string => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return mins > 0 ? `${mins}分${secs}秒` : `${secs}秒`; + }; + + const statusInfo = STATUS_INFO[task.status]; + const StatusIcon = statusInfo.icon; + + return ( +
+ {/* 顶部导航栏 */} +
+
+ +
+
+

+ + {task.fileName} +

+
+
+
+ {isCompleted && ( + + )} +
+
+ + {/* 内容区域 */} +
+
+ {/* 进度显示(审查中) */} + {isProcessing && ( +
+ {/* 状态头部 */} +
+ + + {statusInfo.label} + + + 已用时 {formatTime(elapsedTime)} + +
+ + {/* 进度条 */} +
+ {PROGRESS_STEPS.map((step, index) => { + const stepStatus = getStepStatus(step.key); + return ( +
+
+
+ {stepStatus === 'completed' ? ( + + ) : stepStatus === 'active' ? ( + + ) : ( + index + 1 + )} +
+ + {step.label} + +
+ {index < PROGRESS_STEPS.length - 1 && ( +
+ )} +
+ ); + })} +
+ + {/* 提示信息 */} +
+

AI 正在分析您的稿件,这可能需要 1-3 分钟

+

请耐心等待,完成后将自动显示结果

+
+
+ )} + + {/* 失败状态 */} + {isFailed && ( +
+ +

审查失败

+

{task.errorMessage || '未知错误,请重试'}

+
+ )} + + {/* 完成状态 - 显示报告 */} + {isCompleted && report && ( + <> + {/* 分数卡片 */} +
+
+
+

综合评分

+

+ 审查用时 {report.durationSeconds ? formatTime(report.durationSeconds) : '-'} +

+
+
{report.overallScore || '-'}
+
+
+ + {/* Tab切换 */} +
+ {report.editorialReview && ( + + )} + {report.methodologyReview && ( + + )} +
+ + {/* 报告内容 */} + {activeTab === 'editorial' && report.editorialReview && ( + + )} + {activeTab === 'methodology' && report.methodologyReview && ( + + )} + + )} +
+
+
+ ); +} + diff --git a/frontend/src/pages/rvw/components/TaskTable.tsx b/frontend/src/pages/rvw/components/TaskTable.tsx index ae1d628f..aca7da22 100644 --- a/frontend/src/pages/rvw/components/TaskTable.tsx +++ b/frontend/src/pages/rvw/components/TaskTable.tsx @@ -1,7 +1,7 @@ /** * 任务表格组件 */ -import { FileText, FileType2, Loader2, Play, Eye } from 'lucide-react'; +import { FileText, FileType2, Loader2, Play, Eye, RefreshCw, Trash2 } from 'lucide-react'; import type { ReviewTask } from '../types'; import { formatFileSize, formatTime } from '../api'; @@ -11,6 +11,7 @@ interface TaskTableProps { onSelectChange: (ids: string[]) => void; onViewReport: (task: ReviewTask) => void; onRunTask: (task: ReviewTask) => void; + onDeleteTask: (task: ReviewTask) => void; } export default function TaskTable({ @@ -18,7 +19,8 @@ export default function TaskTable({ selectedIds, onSelectChange, onViewReport, - onRunTask + onRunTask, + onDeleteTask }: TaskTableProps) { const allSelected = tasks.length > 0 && selectedIds.length === tasks.length; @@ -127,36 +129,89 @@ export default function TaskTable({ // 渲染操作按钮 const renderActions = (task: ReviewTask) => { - if (task.status === 'completed') { + // 待审稿:[开始审稿] [删除] + if (task.status === 'pending') { + return ( +
+ + +
+ ); + } + + // 处理中:[查看进度] + if (['extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'].includes(task.status)) { return ( - ); - } - - if (task.status === 'pending') { - return ( - - ); - } - - if (task.status === 'extracting' || task.status === 'reviewing') { - return ( - - 处理中 - + 查看进度 + + ); + } + + // 已完成:[查看报告] [重新审稿] [删除] + if (task.status === 'completed') { + return ( +
+ + + +
+ ); + } + + // 失败:[重新审稿] [删除] + if (task.status === 'failed') { + return ( +
+ + +
); } @@ -250,3 +305,5 @@ export default function TaskTable({ ); } + + diff --git a/frontend/src/pages/rvw/components/index.ts b/frontend/src/pages/rvw/components/index.ts index fc62afab..78dcb90e 100644 --- a/frontend/src/pages/rvw/components/index.ts +++ b/frontend/src/pages/rvw/components/index.ts @@ -11,4 +11,6 @@ export { default as ScoreRing } from './ScoreRing'; export { default as EditorialReport } from './EditorialReport'; export { default as MethodologyReport } from './MethodologyReport'; export { default as ReportDetail } from './ReportDetail'; +export { default as TaskDetail } from './TaskDetail'; + diff --git a/frontend/src/pages/rvw/index.ts b/frontend/src/pages/rvw/index.ts index c75dddc8..71d796ee 100644 --- a/frontend/src/pages/rvw/index.ts +++ b/frontend/src/pages/rvw/index.ts @@ -5,3 +5,5 @@ export { default as RvwDashboard } from './Dashboard'; export * from './types'; export * from './api'; + + diff --git a/frontend/src/pages/rvw/styles.css b/frontend/src/pages/rvw/styles.css index fa2a0483..fd226a76 100644 --- a/frontend/src/pages/rvw/styles.css +++ b/frontend/src/pages/rvw/styles.css @@ -231,3 +231,5 @@ } } + + diff --git a/frontend/src/pages/rvw/types.ts b/frontend/src/pages/rvw/types.ts index 38810399..6c1f6899 100644 --- a/frontend/src/pages/rvw/types.ts +++ b/frontend/src/pages/rvw/types.ts @@ -4,11 +4,13 @@ // 任务状态 export type TaskStatus = - | 'pending' // 待处理 - | 'extracting' // 提取文本中 - | 'reviewing' // 审查中 - | 'completed' // 已完成 - | 'failed'; // 失败 + | 'pending' // 待处理 + | 'extracting' // 提取文本中 + | 'reviewing' // 审查中 + | 'reviewing_editorial' // 正在审查稿约规范性 + | 'reviewing_methodology' // 正在审查方法学 + | 'completed' // 已完成 + | 'failed'; // 失败 // 智能体类型 export type AgentType = 'editorial' | 'methodology'; @@ -90,3 +92,4 @@ export interface TaskFilters { timeRange: 'all' | 'today' | 'week'; } + diff --git a/git-cleanup-redcap.ps1 b/git-cleanup-redcap.ps1 index 42d9161d..85a8e777 100644 --- a/git-cleanup-redcap.ps1 +++ b/git-cleanup-redcap.ps1 @@ -27,3 +27,5 @@ Write-Host "Next step: Run the commit command" -ForegroundColor Cyan + + diff --git a/git-commit-day1.ps1 b/git-commit-day1.ps1 index 9a21753c..3b64e670 100644 --- a/git-commit-day1.ps1 +++ b/git-commit-day1.ps1 @@ -83,3 +83,5 @@ Write-Host "Git commit and push completed!" -ForegroundColor Green + + diff --git a/git-fix-lock.ps1 b/git-fix-lock.ps1 index 185b9b07..6b283fec 100644 --- a/git-fix-lock.ps1 +++ b/git-fix-lock.ps1 @@ -31,3 +31,5 @@ Write-Host "Now you can run git commands again." -ForegroundColor Cyan + + diff --git a/python-microservice/operations/__init__.py b/python-microservice/operations/__init__.py index e24214e8..4983692a 100644 --- a/python-microservice/operations/__init__.py +++ b/python-microservice/operations/__init__.py @@ -53,6 +53,8 @@ __version__ = '1.0.0' + + diff --git a/python-microservice/operations/binning.py b/python-microservice/operations/binning.py index 3ddaec12..9533fd76 100644 --- a/python-microservice/operations/binning.py +++ b/python-microservice/operations/binning.py @@ -160,6 +160,8 @@ def apply_binning( + + diff --git a/python-microservice/operations/filter.py b/python-microservice/operations/filter.py index 4051cf46..8217f8ee 100644 --- a/python-microservice/operations/filter.py +++ b/python-microservice/operations/filter.py @@ -146,6 +146,8 @@ def apply_filter( + + diff --git a/python-microservice/operations/recode.py b/python-microservice/operations/recode.py index 803051d8..21ded433 100644 --- a/python-microservice/operations/recode.py +++ b/python-microservice/operations/recode.py @@ -116,6 +116,8 @@ def apply_recode( + + diff --git a/recover_dc_code.py b/recover_dc_code.py index d2e8f283..7002d92f 100644 --- a/recover_dc_code.py +++ b/recover_dc_code.py @@ -260,6 +260,8 @@ if __name__ == "__main__": + + diff --git a/redcap-docker-dev/.gitattributes b/redcap-docker-dev/.gitattributes index 4f9440f5..ab0e312e 100644 --- a/redcap-docker-dev/.gitattributes +++ b/redcap-docker-dev/.gitattributes @@ -43,3 +43,5 @@ + + diff --git a/redcap-docker-dev/.gitignore b/redcap-docker-dev/.gitignore index b5e0faaf..c885ed65 100644 --- a/redcap-docker-dev/.gitignore +++ b/redcap-docker-dev/.gitignore @@ -74,3 +74,5 @@ Desktop.ini + + diff --git a/redcap-docker-dev/README.md b/redcap-docker-dev/README.md index b9ef16ef..25557fea 100644 --- a/redcap-docker-dev/README.md +++ b/redcap-docker-dev/README.md @@ -375,3 +375,5 @@ docker-compose -f docker-compose.prod.yml up -d + + diff --git a/redcap-docker-dev/docker-compose.prod.yml b/redcap-docker-dev/docker-compose.prod.yml index ec31db04..d78cecc3 100644 --- a/redcap-docker-dev/docker-compose.prod.yml +++ b/redcap-docker-dev/docker-compose.prod.yml @@ -136,3 +136,5 @@ volumes: + + diff --git a/redcap-docker-dev/docker-compose.yml b/redcap-docker-dev/docker-compose.yml index fde115c4..677261b8 100644 --- a/redcap-docker-dev/docker-compose.yml +++ b/redcap-docker-dev/docker-compose.yml @@ -134,3 +134,5 @@ volumes: + + diff --git a/redcap-docker-dev/env.template b/redcap-docker-dev/env.template index 718fbab9..4a187ab5 100644 --- a/redcap-docker-dev/env.template +++ b/redcap-docker-dev/env.template @@ -70,3 +70,5 @@ PMA_UPLOAD_LIMIT=50M + + diff --git a/redcap-docker-dev/scripts/clean-redcap.ps1 b/redcap-docker-dev/scripts/clean-redcap.ps1 index f3222c4d..a501b64f 100644 --- a/redcap-docker-dev/scripts/clean-redcap.ps1 +++ b/redcap-docker-dev/scripts/clean-redcap.ps1 @@ -78,3 +78,5 @@ Write-Host "" + + diff --git a/redcap-docker-dev/scripts/create-redcap-password.php b/redcap-docker-dev/scripts/create-redcap-password.php index dd0a070e..8203a2c4 100644 --- a/redcap-docker-dev/scripts/create-redcap-password.php +++ b/redcap-docker-dev/scripts/create-redcap-password.php @@ -56,3 +56,5 @@ try { + + diff --git a/redcap-docker-dev/scripts/logs-redcap.ps1 b/redcap-docker-dev/scripts/logs-redcap.ps1 index 1c220ba1..c26c6e1a 100644 --- a/redcap-docker-dev/scripts/logs-redcap.ps1 +++ b/redcap-docker-dev/scripts/logs-redcap.ps1 @@ -69,3 +69,5 @@ Write-Host "" + + diff --git a/redcap-docker-dev/scripts/reset-admin-password.php b/redcap-docker-dev/scripts/reset-admin-password.php index 50b68c1a..152659bd 100644 --- a/redcap-docker-dev/scripts/reset-admin-password.php +++ b/redcap-docker-dev/scripts/reset-admin-password.php @@ -32,3 +32,5 @@ if ($result) { + + diff --git a/redcap-docker-dev/scripts/start-redcap.ps1 b/redcap-docker-dev/scripts/start-redcap.ps1 index 8a6292c7..35959af9 100644 --- a/redcap-docker-dev/scripts/start-redcap.ps1 +++ b/redcap-docker-dev/scripts/start-redcap.ps1 @@ -54,3 +54,5 @@ if ($LASTEXITCODE -eq 0) { + + diff --git a/redcap-docker-dev/scripts/stop-redcap.ps1 b/redcap-docker-dev/scripts/stop-redcap.ps1 index a68ac4ab..12e59d06 100644 --- a/redcap-docker-dev/scripts/stop-redcap.ps1 +++ b/redcap-docker-dev/scripts/stop-redcap.ps1 @@ -40,3 +40,5 @@ if ($LASTEXITCODE -eq 0) { + + diff --git a/run_recovery.ps1 b/run_recovery.ps1 index 2b4f8ab6..46e74e79 100644 --- a/run_recovery.ps1 +++ b/run_recovery.ps1 @@ -84,6 +84,8 @@ Write-Host "==================================================================== + + diff --git a/tests/QUICKSTART_快速开始.md b/tests/QUICKSTART_快速开始.md index 4c7f803f..3d0a2ed9 100644 --- a/tests/QUICKSTART_快速开始.md +++ b/tests/QUICKSTART_快速开始.md @@ -131,6 +131,8 @@ INFO: Uvicorn running on http://0.0.0.0:8001 + + diff --git a/tests/README_测试说明.md b/tests/README_测试说明.md index e54c8afe..a37d4604 100644 --- a/tests/README_测试说明.md +++ b/tests/README_测试说明.md @@ -287,6 +287,8 @@ df_numeric.to_excel('test_data/numeric_test.xlsx', index=False) + + diff --git a/tests/run_tests.bat b/tests/run_tests.bat index c64256c5..ebd4823e 100644 --- a/tests/run_tests.bat +++ b/tests/run_tests.bat @@ -82,6 +82,8 @@ pause + + diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 097fe797..1bdfc7f1 100644 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -78,6 +78,8 @@ echo "========================================" + + diff --git a/快速部署到SAE.md b/快速部署到SAE.md index 02d7e83e..3c507b76 100644 --- a/快速部署到SAE.md +++ b/快速部署到SAE.md @@ -343,6 +343,8 @@ OSS AccessKeySecret:_______________ + + diff --git a/部署检查清单.md b/部署检查清单.md index 1e4c920b..6ab989e7 100644 --- a/部署检查清单.md +++ b/部署检查清单.md @@ -379,6 +379,8 @@ OSS配置: + +