From 179afa2c6bbe35330336644f480ab1fda19f3b4a Mon Sep 17 00:00:00 2001 From: HaHafeng Date: Wed, 7 Jan 2026 22:39:08 +0800 Subject: [PATCH] feat(rvw): Complete RVW module development Phase 1-3 Summary: - Migrate backend to modules/rvw with v2 API routes (/api/v2/rvw) - Add new database fields: selectedAgents, editorialScore, methodologyStatus, picoExtract, isArchived - Create frontend module in frontend-v2/src/modules/rvw - Implement Dashboard with task list, filtering, batch operations - Implement ReportDetail with dual tabs (editorial/methodology) - Implement AgentModal for intelligent agent selection - Register RVW module in moduleRegistry.ts - Add navigation entry in TopNavigation - Update documentation for RVW module status (v3.0) - Update system status document (v2.9) Features: - User can select agents: editorial, methodology, or both - Support batch task execution - Task status filtering - Replace console.log with logger service - Maintain v1 API backward compatibility Tested: Frontend and backend verified locally Status: 85% complete (Phase 1-3 done) --- COMMIT_DAY1.txt | 1 + DC模块代码恢复指南.md | 1 + SAE_WECHAT_MP_DEPLOY_STEPS.md | 1 + backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md | 1 + backend/RESTART_SERVER_NOW.md | 1 + backend/WECHAT_MP_CONFIG_READY.md | 1 + backend/WECHAT_MP_QUICK_FIX.md | 1 + .../add_data_stats_to_tool_c_session.sql | 1 + .../001_add_postgres_cache_and_checkpoint.sql | 1 + .../manual-migrations/run-migration-002.ts | 1 + .../20251208_add_column_mapping/migration.sql | 1 + .../migrations/create_tool_c_session.sql | 1 + backend/prisma/schema.prisma | 17 + backend/rebuild-and-push.ps1 | 1 + backend/recover-code-from-cursor-db.js | 1 + backend/scripts/check-dc-tables.mjs | 1 + .../create-tool-c-ai-history-table.mjs | 1 + backend/scripts/create-tool-c-table.js | 1 + backend/scripts/create-tool-c-table.mjs | 1 + backend/scripts/test-pkb-apis-simple.ts | 1 + backend/scripts/verify-pkb-rvw-schema.ts | 1 + backend/src/common/jobs/utils.ts | 1 + backend/src/index.ts | 10 +- .../__tests__/api-integration-test.ts | 1 + .../__tests__/e2e-real-test-v2.ts | 1 + .../__tests__/fulltext-screening-api.http | 1 + .../services/ConflictDetectionService.ts | 1 + backend/src/modules/dc/tool-c/README.md | 1 + .../tool-c/controllers/StreamAIController.ts | 1 + .../iit-manager/agents/SessionMemory.ts | 1 + .../iit-manager/check-iit-table-structure.ts | 1 + .../iit-manager/check-project-config.ts | 1 + .../iit-manager/check-test-project-in-db.ts | 1 + .../iit-manager/docs/微信服务号接入指南.md | 1 + .../iit-manager/generate-wechat-tokens.ts | 1 + .../services/PatientWechatService.ts | 1 + .../iit-manager/test-chatservice-dify.ts | 1 + .../modules/iit-manager/test-iit-database.ts | 1 + .../iit-manager/test-patient-wechat-config.ts | 1 + .../test-patient-wechat-url-verify.ts | 1 + .../iit-manager/test-redcap-query-from-db.ts | 1 + .../iit-manager/test-wechat-mp-local.ps1 | 1 + .../src/modules/iit-manager/types/index.ts | 1 + backend/src/modules/pkb/routes/health.ts | 1 + backend/src/modules/rvw/__tests__/api.http | 126 +++ .../src/modules/rvw/__tests__/test-api.ps1 | 111 +++ .../rvw/controllers/reviewController.ts | 374 ++++++++ backend/src/modules/rvw/index.ts | 25 + backend/src/modules/rvw/routes/index.ts | 46 + .../modules/rvw/services/editorialService.ts | 70 ++ .../rvw/services/methodologyService.ts | 70 ++ .../src/modules/rvw/services/reviewService.ts | 430 +++++++++ backend/src/modules/rvw/services/utils.ts | 116 +++ backend/src/modules/rvw/types/index.ts | 157 ++++ backend/src/tests/README.md | 1 + backend/src/tests/verify-test1-database.sql | 1 + backend/src/tests/verify-test1-database.ts | 1 + backend/src/types/global.d.ts | 1 + backend/sync-dc-database.ps1 | 1 + backend/test-pkb-migration.http | 1 + backend/test-tool-c-advanced-scenarios.mjs | 1 + backend/test-tool-c-day2.mjs | 1 + backend/test-tool-c-day3.mjs | 1 + deploy-to-sae.ps1 | 1 + .../00-系统当前状态与开发指南.md | 50 +- .../Postgres-Only异步任务处理指南.md | 1 + docs/02-通用能力层/通用能力层技术债务清单.md | 1 + .../04-开发计划/05-全文复筛前端开发计划.md | 1 + .../05-开发记录/2025-01-23_全文复筛前端开发完成.md | 1 + .../05-开发记录/2025-01-23_全文复筛前端逻辑调整.md | 1 + .../05-开发记录/2025-11-23_Day5_全文复筛API开发.md | 1 + .../04-开发计划/工具C_AI_Few-shot示例库.md | 1 + .../04-开发计划/工具C_Bug修复总结_2025-12-08.md | 1 + .../04-开发计划/工具C_Day3开发计划.md | 1 + .../04-开发计划/工具C_Day4-5前端开发计划.md | 1 + .../04-开发计划/工具C_Pivot列顺序优化总结.md | 1 + .../04-开发计划/工具C_方案B实施总结_2025-12-09.md | 1 + .../04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md | 1 + .../04-开发计划/工具C_缺失值处理功能_更新说明.md | 1 + .../06-开发记录/2025-12-02_工作总结.md | 1 + .../06-开发记录/2025-12-06_工具C_Day1开发完成总结.md | 1 + .../06-开发记录/2025-12-06_工具C_Day2开发完成总结.md | 1 + .../06-开发记录/2025-12-07_AI对话核心功能增强总结.md | 1 + .../2025-12-07_Bug修复_DataGrid空数据防御.md | 1 + .../06-开发记录/2025-12-07_Day5_Ant-Design-X重构完成.md | 1 + .../06-开发记录/2025-12-07_Day5最终总结.md | 1 + .../06-开发记录/2025-12-07_UI优化与Bug修复.md | 1 + .../06-开发记录/2025-12-07_后端API完整对接完成.md | 1 + .../06-开发记录/2025-12-07_完整UI优化与功能增强.md | 1 + .../06-开发记录/2025-12-07_工具C_Day4前端基础完成.md | 1 + .../06-开发记录/DC模块重建完成总结-Day1.md | 1 + .../06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md | 1 + .../Phase2-ToolB-Step1-2开发完成-2025-12-03.md | 1 + .../06-开发记录/Portal页面UI优化-2025-12-02.md | 1 + .../06-开发记录/Tool-B-MVP完成总结-2025-12-03.md | 1 + .../06-开发记录/ToolB-UI优化-2025-12-03.md | 1 + .../06-开发记录/ToolB-UI优化-Round2-2025-12-03.md | 1 + .../06-开发记录/ToolB浏览器测试计划-2025-12-03.md | 1 + .../06-开发记录/后端API测试报告-2025-12-02.md | 1 + .../06-开发记录/待办事项-下一步工作.md | 1 + .../06-开发记录/数据库验证报告-2025-12-02.md | 1 + .../07-技术债务/Tool-B技术债务清单.md | 1 + .../IIT Manager Agent 技术路径与架构设计.md | 1 + .../04-开发计划/REDCap对接技术方案与实施指南.md | 1 + .../04-开发计划/企业微信注册指南.md | 1 + .../2026-01-04-Dify知识库集成开发记录.md | 1 + .../Day2-REDCap实时集成开发完成记录.md | 1 + .../Day3-企业微信集成与端到端测试完成记录.md | 1 + .../06-开发记录/Day3-企业微信集成开发完成记录.md | 1 + .../Phase1.5-AI对话集成REDCap完成记录.md | 1 + .../06-开发记录/V1.1更新完成报告.md | 1 + .../07-技术债务/IIT Manager Agent 技术债务清单.md | 1 + .../06-开发记录/2026-01-07-前端迁移与批处理功能完善.md | 1 + .../06-开发记录/2026-01-07_PKB模块前端V3设计实现.md | 1 + .../RVW-稿件审查系统/00-模块当前状态与开发指南.md | 338 +++++++ .../RVW-稿件审查系统/01-需求分析/智能审稿V7.html | 529 +++++++++++ .../01-需求分析/智能期刊审稿系统 MVP 产品需求文档.md | 135 +++ .../04-开发计划/RVW模块迁移计划.md | 829 ++++++++++++++++++ .../01-部署与配置/10-REDCap_Docker部署操作手册.md | 1 + docs/03-业务模块/Redcap/README.md | 1 + .../02-SAE部署完全指南(产品经理版).md | 1 + .../07-前端Nginx-SAE部署操作手册.md | 1 + .../08-PostgreSQL数据库部署操作手册.md | 1 + .../10-Node.js后端-Docker镜像构建手册.md | 1 + .../11-Node.js后端-SAE部署配置清单.md | 1 + .../12-Node.js后端-SAE部署操作手册.md | 1 + .../13-Node.js后端-镜像修复记录.md | 1 + .../14-Node.js后端-pino-pretty问题修复.md | 1 + docs/05-部署文档/16-前端Nginx-部署成功总结.md | 1 + .../05-部署文档/17-完整部署实战手册-2025版.md | 1 + docs/05-部署文档/18-部署文档使用指南.md | 1 + docs/05-部署文档/19-日常更新快速操作手册.md | 1 + docs/05-部署文档/文档修正报告-20251214.md | 1 + docs/07-运维文档/03-SAE环境变量配置指南.md | 1 + .../05-Redis缓存与队列的区别说明.md | 1 + docs/07-运维文档/06-长时间任务可靠性分析.md | 1 + .../07-Redis使用需求分析(按模块).md | 1 + .../2025-12-13-Postgres-Only架构改造完成.md | 1 + .../05-技术债务/通用对话服务抽取计划.md | 1 + docs/08-项目管理/PKB前端问题修复报告.md | 1 + docs/08-项目管理/PKB前端验证指南.md | 1 + docs/08-项目管理/PKB功能审查报告-阶段0.md | 1 + docs/08-项目管理/PKB和RVW功能迁移计划.md | 1 + docs/08-项目管理/PKB精细化优化报告.md | 1 + docs/08-项目管理/PKB迁移-超级安全执行计划.md | 1 + docs/08-项目管理/PKB迁移-阶段1完成报告.md | 1 + docs/08-项目管理/PKB迁移-阶段2完成报告.md | 1 + docs/08-项目管理/PKB迁移-阶段2进行中.md | 1 + docs/08-项目管理/PKB迁移-阶段3完成报告.md | 1 + docs/08-项目管理/PKB迁移-阶段4完成报告.md | 1 + extraction_service/.dockerignore | 1 + extraction_service/operations/__init__.py | 1 + extraction_service/operations/dropna.py | 1 + extraction_service/operations/filter.py | 1 + extraction_service/operations/unpivot.py | 1 + extraction_service/test_dc_api.py | 1 + extraction_service/test_execute_simple.py | 1 + extraction_service/test_module.py | 1 + frontend-v2/.dockerignore | 1 + frontend-v2/docker-entrypoint.sh | 1 + frontend-v2/nginx.conf | 1 + .../src/framework/layout/TopNavigation.tsx | 1 - .../src/framework/modules/moduleRegistry.ts | 13 +- .../asl/components/FulltextDetailDrawer.tsx | 1 + frontend-v2/src/modules/dc/hooks/useAssets.ts | 1 + .../src/modules/dc/hooks/useRecentTasks.ts | 1 + .../pages/tool-c/components/DropnaDialog.tsx | 1 + .../tool-c/components/MetricTimePanel.tsx | 1 + .../dc/pages/tool-c/components/PivotPanel.tsx | 1 + .../dc/pages/tool-c/hooks/useSessionStatus.ts | 1 + .../modules/dc/pages/tool-c/types/index.ts | 1 + frontend-v2/src/modules/dc/types/portal.ts | 1 + .../src/modules/pkb/api/knowledgeBaseApi.ts | 1 + .../src/modules/pkb/pages/KnowledgePage.tsx | 1 + .../pkb/stores/useKnowledgeBaseStore.ts | 1 + .../src/modules/pkb/types/workspace.ts | 1 + frontend-v2/src/modules/rvw/index.tsx | 502 +++++++++++ frontend-v2/src/shared/components/index.ts | 1 + frontend-v2/src/vite-env.d.ts | 1 + frontend/package-lock.json | 10 + frontend/package.json | 1 + frontend/src/App.tsx | 5 + frontend/src/layouts/MainLayout.tsx | 37 +- frontend/src/pages/rvw/Dashboard.tsx | 236 +++++ frontend/src/pages/rvw/api.ts | 129 +++ .../src/pages/rvw/components/AgentModal.tsx | 123 +++ .../src/pages/rvw/components/BatchToolbar.tsx | 43 + .../pages/rvw/components/EditorialReport.tsx | 108 +++ .../src/pages/rvw/components/FilterChips.tsx | 66 ++ frontend/src/pages/rvw/components/Header.tsx | 56 ++ .../rvw/components/MethodologyReport.tsx | 113 +++ .../src/pages/rvw/components/ReportDetail.tsx | 110 +++ .../src/pages/rvw/components/ScoreRing.tsx | 38 + frontend/src/pages/rvw/components/Sidebar.tsx | 73 ++ .../src/pages/rvw/components/TaskTable.tsx | 252 ++++++ frontend/src/pages/rvw/components/index.ts | 14 + frontend/src/pages/rvw/index.ts | 7 + frontend/src/pages/rvw/styles.css | 233 +++++ frontend/src/pages/rvw/types.ts | 92 ++ git-cleanup-redcap.ps1 | 1 + git-commit-day1.ps1 | 1 + git-fix-lock.ps1 | 1 + python-microservice/operations/__init__.py | 1 + python-microservice/operations/binning.py | 1 + python-microservice/operations/filter.py | 1 + python-microservice/operations/recode.py | 1 + recover_dc_code.py | 1 + redcap-docker-dev/.gitattributes | 1 + redcap-docker-dev/.gitignore | 1 + redcap-docker-dev/README.md | 1 + redcap-docker-dev/docker-compose.prod.yml | 1 + redcap-docker-dev/docker-compose.yml | 1 + redcap-docker-dev/env.template | 1 + redcap-docker-dev/scripts/clean-redcap.ps1 | 1 + .../scripts/create-redcap-password.php | 1 + redcap-docker-dev/scripts/logs-redcap.ps1 | 1 + .../scripts/reset-admin-password.php | 1 + redcap-docker-dev/scripts/start-redcap.ps1 | 1 + redcap-docker-dev/scripts/stop-redcap.ps1 | 1 + run_recovery.ps1 | 1 + tests/QUICKSTART_快速开始.md | 1 + tests/README_测试说明.md | 1 + tests/run_tests.bat | 1 + tests/run_tests.sh | 1 + 快速部署到SAE.md | 1 + 部署检查清单.md | 1 + 226 files changed, 5860 insertions(+), 21 deletions(-) create mode 100644 backend/src/modules/rvw/__tests__/api.http create mode 100644 backend/src/modules/rvw/__tests__/test-api.ps1 create mode 100644 backend/src/modules/rvw/controllers/reviewController.ts create mode 100644 backend/src/modules/rvw/index.ts create mode 100644 backend/src/modules/rvw/routes/index.ts create mode 100644 backend/src/modules/rvw/services/editorialService.ts create mode 100644 backend/src/modules/rvw/services/methodologyService.ts create mode 100644 backend/src/modules/rvw/services/reviewService.ts create mode 100644 backend/src/modules/rvw/services/utils.ts create mode 100644 backend/src/modules/rvw/types/index.ts create mode 100644 docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md create mode 100644 docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能审稿V7.html create mode 100644 docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能期刊审稿系统 MVP 产品需求文档.md create mode 100644 docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW模块迁移计划.md create mode 100644 frontend-v2/src/modules/rvw/index.tsx create mode 100644 frontend/src/pages/rvw/Dashboard.tsx create mode 100644 frontend/src/pages/rvw/api.ts create mode 100644 frontend/src/pages/rvw/components/AgentModal.tsx create mode 100644 frontend/src/pages/rvw/components/BatchToolbar.tsx create mode 100644 frontend/src/pages/rvw/components/EditorialReport.tsx create mode 100644 frontend/src/pages/rvw/components/FilterChips.tsx create mode 100644 frontend/src/pages/rvw/components/Header.tsx create mode 100644 frontend/src/pages/rvw/components/MethodologyReport.tsx create mode 100644 frontend/src/pages/rvw/components/ReportDetail.tsx create mode 100644 frontend/src/pages/rvw/components/ScoreRing.tsx create mode 100644 frontend/src/pages/rvw/components/Sidebar.tsx create mode 100644 frontend/src/pages/rvw/components/TaskTable.tsx create mode 100644 frontend/src/pages/rvw/components/index.ts create mode 100644 frontend/src/pages/rvw/index.ts create mode 100644 frontend/src/pages/rvw/styles.css create mode 100644 frontend/src/pages/rvw/types.ts diff --git a/COMMIT_DAY1.txt b/COMMIT_DAY1.txt index 99343d69..9a4622a0 100644 --- a/COMMIT_DAY1.txt +++ b/COMMIT_DAY1.txt @@ -38,3 +38,4 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2 + diff --git a/DC模块代码恢复指南.md b/DC模块代码恢复指南.md index 6b50c485..64ba3562 100644 --- a/DC模块代码恢复指南.md +++ b/DC模块代码恢复指南.md @@ -266,5 +266,6 @@ + diff --git a/SAE_WECHAT_MP_DEPLOY_STEPS.md b/SAE_WECHAT_MP_DEPLOY_STEPS.md index 78494dbc..1c274939 100644 --- a/SAE_WECHAT_MP_DEPLOY_STEPS.md +++ b/SAE_WECHAT_MP_DEPLOY_STEPS.md @@ -214,3 +214,4 @@ 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 01307229..2fd7a8a1 100644 --- a/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md +++ b/backend/DEPLOY_TO_SAE_FOR_WECHAT_MP.md @@ -143,3 +143,4 @@ https://iit.xunzhengyixue.com/api/v1/iit/health + diff --git a/backend/RESTART_SERVER_NOW.md b/backend/RESTART_SERVER_NOW.md index e81a28b1..3d55a770 100644 --- a/backend/RESTART_SERVER_NOW.md +++ b/backend/RESTART_SERVER_NOW.md @@ -44,3 +44,4 @@ + diff --git a/backend/WECHAT_MP_CONFIG_READY.md b/backend/WECHAT_MP_CONFIG_READY.md index 3181af4b..bac8b054 100644 --- a/backend/WECHAT_MP_CONFIG_READY.md +++ b/backend/WECHAT_MP_CONFIG_READY.md @@ -304,3 +304,4 @@ 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 3c83688a..51418173 100644 --- a/backend/WECHAT_MP_QUICK_FIX.md +++ b/backend/WECHAT_MP_QUICK_FIX.md @@ -166,3 +166,4 @@ 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 7d8c728c..e5516d6d 100644 --- a/backend/migrations/add_data_stats_to_tool_c_session.sql +++ b/backend/migrations/add_data_stats_to_tool_c_session.sql @@ -61,5 +61,6 @@ 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 b204e706..ea97c511 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,5 +99,6 @@ 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 8f847cd7..45cd856f 100644 --- a/backend/prisma/manual-migrations/run-migration-002.ts +++ b/backend/prisma/manual-migrations/run-migration-002.ts @@ -112,5 +112,6 @@ runMigration() + diff --git a/backend/prisma/migrations/20251208_add_column_mapping/migration.sql b/backend/prisma/migrations/20251208_add_column_mapping/migration.sql index 89902921..d82bb85b 100644 --- a/backend/prisma/migrations/20251208_add_column_mapping/migration.sql +++ b/backend/prisma/migrations/20251208_add_column_mapping/migration.sql @@ -46,5 +46,6 @@ 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 2d2e576f..7954bbe9 100644 --- a/backend/prisma/migrations/create_tool_c_session.sql +++ b/backend/prisma/migrations/create_tool_c_session.sql @@ -73,5 +73,6 @@ 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 c56e8c8f..761aa08a 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -303,9 +303,25 @@ model ReviewTask { extractedText String @map("extracted_text") wordCount Int? @map("word_count") status String @default("pending") + + // 🆕 智能体选择(Phase 2新增) + selectedAgents String[] @default(["editorial", "methodology"]) @map("selected_agents") + + // 评估结果 editorialReview Json? @map("editorial_review") methodologyReview Json? @map("methodology_review") overallScore Float? @map("overall_score") + + // 🆕 结果摘要(Phase 2新增,用于列表展示) + editorialScore Float? @map("editorial_score") + methodologyStatus String? @map("methodology_status") // pass/warn/fail + + // 🆕 预留字段(暂不使用) + picoExtract Json? @map("pico_extract") + isArchived Boolean @default(false) @map("is_archived") + archivedAt DateTime? @map("archived_at") + + // 元数据 modelUsed String? @map("model_used") startedAt DateTime? @map("started_at") completedAt DateTime? @map("completed_at") @@ -318,6 +334,7 @@ model ReviewTask { @@index([userId]) @@index([status]) @@index([createdAt]) + @@index([isArchived]) @@map("review_tasks") @@schema("public") } diff --git a/backend/rebuild-and-push.ps1 b/backend/rebuild-and-push.ps1 index cd1a0008..c5650417 100644 --- a/backend/rebuild-and-push.ps1 +++ b/backend/rebuild-and-push.ps1 @@ -114,4 +114,5 @@ Write-Host "" + diff --git a/backend/recover-code-from-cursor-db.js b/backend/recover-code-from-cursor-db.js index d5661ed6..b7ed326f 100644 --- a/backend/recover-code-from-cursor-db.js +++ b/backend/recover-code-from-cursor-db.js @@ -223,5 +223,6 @@ function extractCodeBlocks(obj, blocks = []) { + diff --git a/backend/scripts/check-dc-tables.mjs b/backend/scripts/check-dc-tables.mjs index faa30607..8b37e3c5 100644 --- a/backend/scripts/check-dc-tables.mjs +++ b/backend/scripts/check-dc-tables.mjs @@ -242,5 +242,6 @@ checkDCTables(); + diff --git a/backend/scripts/create-tool-c-ai-history-table.mjs b/backend/scripts/create-tool-c-ai-history-table.mjs index 5c24334b..65b42030 100644 --- a/backend/scripts/create-tool-c-ai-history-table.mjs +++ b/backend/scripts/create-tool-c-ai-history-table.mjs @@ -194,5 +194,6 @@ createAiHistoryTable() + diff --git a/backend/scripts/create-tool-c-table.js b/backend/scripts/create-tool-c-table.js index 047c9c6b..733e1f21 100644 --- a/backend/scripts/create-tool-c-table.js +++ b/backend/scripts/create-tool-c-table.js @@ -181,5 +181,6 @@ createToolCTable() + diff --git a/backend/scripts/create-tool-c-table.mjs b/backend/scripts/create-tool-c-table.mjs index 9ee120dd..25d0409f 100644 --- a/backend/scripts/create-tool-c-table.mjs +++ b/backend/scripts/create-tool-c-table.mjs @@ -178,5 +178,6 @@ createToolCTable() + diff --git a/backend/scripts/test-pkb-apis-simple.ts b/backend/scripts/test-pkb-apis-simple.ts index e44c751a..f0178252 100644 --- a/backend/scripts/test-pkb-apis-simple.ts +++ b/backend/scripts/test-pkb-apis-simple.ts @@ -327,3 +327,4 @@ runTests().catch(error => { + diff --git a/backend/scripts/verify-pkb-rvw-schema.ts b/backend/scripts/verify-pkb-rvw-schema.ts index b3762055..23a465c2 100644 --- a/backend/scripts/verify-pkb-rvw-schema.ts +++ b/backend/scripts/verify-pkb-rvw-schema.ts @@ -292,3 +292,4 @@ verifySchemas() + diff --git a/backend/src/common/jobs/utils.ts b/backend/src/common/jobs/utils.ts index b23e7895..0b5ab775 100644 --- a/backend/src/common/jobs/utils.ts +++ b/backend/src/common/jobs/utils.ts @@ -310,5 +310,6 @@ export function getBatchItems( + diff --git a/backend/src/index.ts b/backend/src/index.ts index eff29829..5f7873f3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -10,6 +10,7 @@ import knowledgeBaseRoutes from './legacy/routes/knowledgeBases.js'; import { chatRoutes } from './legacy/routes/chatRoutes.js'; import { batchRoutes } from './legacy/routes/batchRoutes.js'; import reviewRoutes from './legacy/routes/reviewRoutes.js'; +import { rvwRoutes } from './modules/rvw/index.js'; import { aslRoutes } from './modules/asl/routes/index.js'; import { registerDCRoutes, initDCModule } from './modules/dc/index.js'; import pkbRoutes from './modules/pkb/routes/index.js'; @@ -109,9 +110,16 @@ await fastify.register(chatRoutes, { prefix: '/api/v1' }); // Phase 3: 注册批处理路由 await fastify.register(batchRoutes, { prefix: '/api/v1' }); -// 注册稿件审查路由 +// 注册稿件审查路由(旧版,保留兼容) await fastify.register(reviewRoutes, { prefix: '/api/v1' }); +// ============================================ +// 【业务模块】RVW - 稿件审查系统(新架构 v2) +// ============================================ +await fastify.register(rvwRoutes, { prefix: '/api/v2/rvw' }); +logger.info('✅ RVW稿件审查路由已注册(v2新架构): /api/v2/rvw'); +logger.info(' ⚠️ 旧版路由仍可用: /api/v1/review'); + // ============================================ // 【业务模块】PKB - 个人知识库(新架构 v2) // ============================================ 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 58c55d2d..27b3120d 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,5 +346,6 @@ 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 c225238a..dcda1348 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,5 +287,6 @@ 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 41850d46..57f66d08 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,5 +325,6 @@ 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 b767e42a..e32c93ab 100644 --- a/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts +++ b/backend/src/modules/dc/tool-b/services/ConflictDetectionService.ts @@ -261,5 +261,6 @@ 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 513cad58..fdb9c9a8 100644 --- a/backend/src/modules/dc/tool-c/README.md +++ b/backend/src/modules/dc/tool-c/README.md @@ -211,5 +211,6 @@ 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 2565894f..7dcfe384 100644 --- a/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts +++ b/backend/src/modules/dc/tool-c/controllers/StreamAIController.ts @@ -265,5 +265,6 @@ 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 64e030c6..485311b3 100644 --- a/backend/src/modules/iit-manager/agents/SessionMemory.ts +++ b/backend/src/modules/iit-manager/agents/SessionMemory.ts @@ -176,3 +176,4 @@ 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 a13d6d55..6128e91f 100644 --- a/backend/src/modules/iit-manager/check-iit-table-structure.ts +++ b/backend/src/modules/iit-manager/check-iit-table-structure.ts @@ -110,3 +110,4 @@ checkTableStructure(); + diff --git a/backend/src/modules/iit-manager/check-project-config.ts b/backend/src/modules/iit-manager/check-project-config.ts index bfbb1efd..b8652ce6 100644 --- a/backend/src/modules/iit-manager/check-project-config.ts +++ b/backend/src/modules/iit-manager/check-project-config.ts @@ -97,3 +97,4 @@ 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 6887c756..ad07a5bf 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 @@ -79,3 +79,4 @@ main(); + diff --git a/backend/src/modules/iit-manager/docs/微信服务号接入指南.md b/backend/src/modules/iit-manager/docs/微信服务号接入指南.md index 1a32aa37..e37fdfa1 100644 --- a/backend/src/modules/iit-manager/docs/微信服务号接入指南.md +++ b/backend/src/modules/iit-manager/docs/微信服务号接入指南.md @@ -536,3 +536,4 @@ 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 2b26e99a..445d4b36 100644 --- a/backend/src/modules/iit-manager/generate-wechat-tokens.ts +++ b/backend/src/modules/iit-manager/generate-wechat-tokens.ts @@ -171,3 +171,4 @@ console.log(''); + diff --git a/backend/src/modules/iit-manager/services/PatientWechatService.ts b/backend/src/modules/iit-manager/services/PatientWechatService.ts index b499f8cb..9d9be2c7 100644 --- a/backend/src/modules/iit-manager/services/PatientWechatService.ts +++ b/backend/src/modules/iit-manager/services/PatientWechatService.ts @@ -488,3 +488,4 @@ 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 a42b10f8..941461f3 100644 --- a/backend/src/modules/iit-manager/test-chatservice-dify.ts +++ b/backend/src/modules/iit-manager/test-chatservice-dify.ts @@ -133,3 +133,4 @@ 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 06f2f5bc..831c3c6b 100644 --- a/backend/src/modules/iit-manager/test-iit-database.ts +++ b/backend/src/modules/iit-manager/test-iit-database.ts @@ -162,3 +162,4 @@ 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 1fc8dfe7..830f1d92 100644 --- a/backend/src/modules/iit-manager/test-patient-wechat-config.ts +++ b/backend/src/modules/iit-manager/test-patient-wechat-config.ts @@ -148,3 +148,4 @@ 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 8513045b..0f009338 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 @@ -174,3 +174,4 @@ 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 eedce761..873872d2 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 @@ -255,3 +255,4 @@ 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 1590689f..a6904604 100644 --- a/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 +++ b/backend/src/modules/iit-manager/test-wechat-mp-local.ps1 @@ -139,3 +139,4 @@ Write-Host "" + diff --git a/backend/src/modules/iit-manager/types/index.ts b/backend/src/modules/iit-manager/types/index.ts index 2a57cb6f..dced30cb 100644 --- a/backend/src/modules/iit-manager/types/index.ts +++ b/backend/src/modules/iit-manager/types/index.ts @@ -232,3 +232,4 @@ export interface CachedProtocolRules { + diff --git a/backend/src/modules/pkb/routes/health.ts b/backend/src/modules/pkb/routes/health.ts index d485740d..8820e92a 100644 --- a/backend/src/modules/pkb/routes/health.ts +++ b/backend/src/modules/pkb/routes/health.ts @@ -45,3 +45,4 @@ 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 new file mode 100644 index 00000000..21d9b43a --- /dev/null +++ b/backend/src/modules/rvw/__tests__/api.http @@ -0,0 +1,126 @@ +### RVW稿件审查模块 - API测试 +### Phase 1 验证用例 + +@baseUrl = http://localhost:3001 +@taskId = {{$uuid}} + +### ======================================== +### 1. 创建任务(上传稿件) +### POST /api/v2/rvw/tasks +### ======================================== + +# 注意:需要使用工具(如Postman)上传文件 +# curl -X POST http://localhost:3001/api/v2/rvw/tasks \ +# -F "file=@test.docx" \ +# -F "modelType=deepseek-v3" + + +### ======================================== +### 2. 获取任务列表 +### GET /api/v2/rvw/tasks +### ======================================== + +### 获取全部任务 +GET {{baseUrl}}/api/v2/rvw/tasks +Content-Type: application/json + +### 获取待处理任务 +GET {{baseUrl}}/api/v2/rvw/tasks?status=pending +Content-Type: application/json + +### 获取已完成任务 +GET {{baseUrl}}/api/v2/rvw/tasks?status=completed +Content-Type: application/json + +### 分页获取 +GET {{baseUrl}}/api/v2/rvw/tasks?page=1&limit=10 +Content-Type: application/json + + +### ======================================== +### 3. 运行审查(选择智能体) +### POST /api/v2/rvw/tasks/:taskId/run +### ======================================== + +### 只选择规范性智能体 +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ + "agents": ["editorial"] +} + +### 只选择方法学智能体 +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ + "agents": ["methodology"] +} + +### 同时选择两个智能体(默认) +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ + "agents": ["editorial", "methodology"] +} + + +### ======================================== +### 4. 批量运行审查 +### POST /api/v2/rvw/tasks/batch/run +### ======================================== + +POST {{baseUrl}}/api/v2/rvw/tasks/batch/run +Content-Type: application/json + +{ + "taskIds": ["task-id-1", "task-id-2", "task-id-3"], + "agents": ["editorial", "methodology"] +} + + +### ======================================== +### 5. 获取任务详情 +### GET /api/v2/rvw/tasks/:taskId +### ======================================== + +GET {{baseUrl}}/api/v2/rvw/tasks/{{taskId}} +Content-Type: application/json + + +### ======================================== +### 6. 获取审查报告 +### GET /api/v2/rvw/tasks/:taskId/report +### ======================================== + +GET {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/report +Content-Type: application/json + + +### ======================================== +### 7. 删除任务 +### DELETE /api/v2/rvw/tasks/:taskId +### ======================================== + +DELETE {{baseUrl}}/api/v2/rvw/tasks/{{taskId}} +Content-Type: application/json + + +### ======================================== +### 旧版API(兼容性测试) +### ======================================== + +### 旧版:获取任务列表 +GET {{baseUrl}}/api/v1/review/tasks +Content-Type: application/json + +### 旧版:获取任务状态 +GET {{baseUrl}}/api/v1/review/tasks/{{taskId}} +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 new file mode 100644 index 00000000..5cb35c86 --- /dev/null +++ b/backend/src/modules/rvw/__tests__/test-api.ps1 @@ -0,0 +1,111 @@ +# RVW稿件审查模块 - API测试脚本 +# Phase 1 验证用例 + +$BaseUrl = "http://localhost:3001" +$TestFile = "D:\MyCursor\AIclinicalresearch\docs\03-业务模块\ASL-AI智能文献\05-测试文档\03-测试数据\pdf-extraction\rayyan-256859669.pdf" + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "RVW模块 Phase 1 API测试" -ForegroundColor Cyan +Write-Host "========================================`n" -ForegroundColor Cyan + +# 检查服务器是否运行 +Write-Host "1. 检查服务器状态..." -ForegroundColor Yellow +try { + $health = Invoke-RestMethod -Uri "$BaseUrl/health" -Method Get + Write-Host " ✅ 服务器运行中" -ForegroundColor Green +} catch { + Write-Host " ❌ 服务器未运行,请先启动后端: cd backend && npm run dev" -ForegroundColor Red + exit 1 +} + +# 测试1: 获取任务列表(不需要上传) +Write-Host "`n2. 测试获取任务列表 (GET /api/v2/rvw/tasks)..." -ForegroundColor Yellow +try { + $response = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks" -Method Get + Write-Host " ✅ 成功! 当前任务数: $($response.pagination.total)" -ForegroundColor Green + if ($response.data.Count -gt 0) { + Write-Host " 最近任务: $($response.data[0].fileName) - $($response.data[0].status)" -ForegroundColor Gray + } +} catch { + Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red +} + +# 测试2: 按状态筛选 +Write-Host "`n3. 测试筛选待处理任务 (GET /api/v2/rvw/tasks?status=pending)..." -ForegroundColor Yellow +try { + $response = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks?status=pending" -Method Get + Write-Host " ✅ 成功! 待处理任务数: $($response.pagination.total)" -ForegroundColor Green +} catch { + Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red +} + +# 测试3: 上传文件创建任务 +Write-Host "`n4. 测试上传文件 (POST /api/v2/rvw/tasks)..." -ForegroundColor Yellow +if (Test-Path $TestFile) { + try { + # 使用curl上传(PowerShell的Invoke-RestMethod对multipart支持不好) + $curlResult = & curl.exe -s -X POST "$BaseUrl/api/v2/rvw/tasks" ` + -F "file=@$TestFile" ` + -F "modelType=deepseek-v3" + + $uploadResponse = $curlResult | ConvertFrom-Json + if ($uploadResponse.success) { + $taskId = $uploadResponse.data.taskId + Write-Host " ✅ 上传成功! TaskId: $taskId" -ForegroundColor Green + Write-Host " 文件名: $($uploadResponse.data.fileName)" -ForegroundColor Gray + + # 等待文档提取 + Write-Host "`n5. 等待文档提取(3秒)..." -ForegroundColor Yellow + Start-Sleep -Seconds 3 + + # 测试4: 获取任务详情 + Write-Host "`n6. 测试获取任务详情 (GET /api/v2/rvw/tasks/$taskId)..." -ForegroundColor Yellow + try { + $detail = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks/$taskId" -Method Get + Write-Host " ✅ 成功! 状态: $($detail.data.status)" -ForegroundColor Green + } catch { + Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red + } + + # 测试5: 运行审查(只选规范性) + Write-Host "`n7. 测试运行审查-只选规范性 (POST /api/v2/rvw/tasks/$taskId/run)..." -ForegroundColor Yellow + try { + $body = @{ agents = @("editorial") } | ConvertTo-Json + $runResult = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks/$taskId/run" ` + -Method Post -Body $body -ContentType "application/json" + Write-Host " ✅ 审查任务已启动!" -ForegroundColor Green + Write-Host " ⏳ 注意:AI评估需要1-2分钟,可稍后查看报告" -ForegroundColor Yellow + } catch { + $errorBody = $_.ErrorDetails.Message | ConvertFrom-Json + Write-Host " ⚠️ $($errorBody.message)" -ForegroundColor Yellow + } + + } else { + Write-Host " ❌ 上传失败: $($uploadResponse.message)" -ForegroundColor Red + } + } catch { + Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red + } +} else { + Write-Host " ⚠️ 测试文件不存在: $TestFile" -ForegroundColor Yellow + Write-Host " 跳过上传测试,请手动测试" -ForegroundColor Gray +} + +# 测试6: 旧版API兼容性 +Write-Host "`n8. 测试旧版API兼容性 (GET /api/v1/review/tasks)..." -ForegroundColor Yellow +try { + $response = Invoke-RestMethod -Uri "$BaseUrl/api/v1/review/tasks" -Method Get + Write-Host " ✅ 旧版API正常! 任务数: $($response.pagination.total)" -ForegroundColor Green +} catch { + Write-Host " ❌ 旧版API异常: $($_.Exception.Message)" -ForegroundColor Red +} + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "测试完成!" -ForegroundColor Cyan +Write-Host "========================================`n" -ForegroundColor Cyan + +Write-Host "后续操作:" -ForegroundColor Yellow +Write-Host " - 查看报告: GET $BaseUrl/api/v2/rvw/tasks/{taskId}/report" -ForegroundColor Gray +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 new file mode 100644 index 00000000..99572f42 --- /dev/null +++ b/backend/src/modules/rvw/controllers/reviewController.ts @@ -0,0 +1,374 @@ +/** + * RVW稿件审查模块 - 控制器 + * @module rvw/controllers/reviewController + * + * 基于旧代码迁移:backend/src/legacy/controllers/reviewController.ts + * 改造内容: + * 1. console.log → logger + * 2. MOCK_USER_ID → 从请求中获取(暂时保留Mock,待JWT集成) + * 3. 新增智能体选择、批量运行接口 + */ + +import type { FastifyRequest, FastifyReply } from 'fastify'; +import { logger } from '../../../common/logging/index.js'; +import { ModelType } from '../../../common/llm/adapters/types.js'; +import * as reviewService from '../services/reviewService.js'; +import { AgentType } from '../types/index.js'; + +// TODO: 集成JWT认证后移除 +const MOCK_USER_ID = 'user-mock-001'; + +/** + * 获取用户ID(暂时使用Mock,待JWT集成) + */ +function getUserId(request: FastifyRequest): string { + // TODO: 从JWT token中获取 + // return request.user?.id; + return MOCK_USER_ID; +} + +// ==================== 任务创建 ==================== + +/** + * 上传稿件创建任务 + * POST /api/v2/rvw/tasks + */ +export async function createTask( + request: FastifyRequest<{ + Body: { + modelType?: string; + }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + logger.info('[RVW:Controller] 上传稿件', { userId }); + + // 获取上传的文件 + const data = await request.file(); + + if (!data) { + logger.warn('[RVW:Controller] 没有接收到文件'); + return reply.status(400).send({ + success: false, + message: '请上传文件', + }); + } + + const file = await data.toBuffer(); + const filename = data.filename; + const fileType = data.mimetype; + const fileSizeBytes = file.length; + + logger.info('[RVW:Controller] 接收文件', { + filename, + fileType, + sizeMB: (fileSizeBytes / 1024 / 1024).toFixed(2) + }); + + // 文件大小限制(50MB,根据MVP需求) + const maxSize = 50 * 1024 * 1024; + if (fileSizeBytes > maxSize) { + logger.warn('[RVW:Controller] 文件太大', { sizeMB: (fileSizeBytes / 1024 / 1024).toFixed(2) }); + return reply.status(400).send({ + success: false, + message: '文件大小不能超过50MB', + }); + } + + // 文件类型限制 + const allowedTypes = [ + 'application/msword', // .doc + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx + 'application/pdf', // .pdf + ]; + + if (!allowedTypes.includes(fileType)) { + logger.warn('[RVW:Controller] 不支持的文件类型', { fileType }); + return reply.status(400).send({ + success: false, + message: '仅支持 Word (.doc, .docx) 和 PDF 文件', + }); + } + + // 获取模型类型 + const modelType = ((data.fields.modelType as any)?.value || 'deepseek-v3') as ModelType; + + // 验证模型类型 + const validModels: ModelType[] = ['deepseek-v3', 'qwen3-72b', 'qwen-long']; + if (!validModels.includes(modelType)) { + return reply.status(400).send({ + success: false, + message: `无效的模型类型,可选: ${validModels.join(', ')}`, + }); + } + + // 创建任务 + const task = await reviewService.createTask(file, filename, userId, modelType); + + logger.info('[RVW:Controller] 任务已创建', { taskId: task.id }); + + return reply.send({ + success: true, + message: '稿件上传成功,任务已创建', + data: { + taskId: task.id, + fileName: task.fileName, + fileSize: task.fileSize, + status: task.status, + createdAt: task.createdAt, + }, + }); + } catch (error) { + logger.error('[RVW:Controller] 上传失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(500).send({ + success: false, + message: error instanceof Error ? error.message : '上传失败', + }); + } +} + +// ==================== 运行审查 ==================== + +/** + * 运行审查(选择智能体) + * POST /api/v2/rvw/tasks/:taskId/run + */ +export async function runReview( + request: FastifyRequest<{ + Params: { taskId: string }; + Body: { agents: AgentType[] }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const { taskId } = request.params; + const { agents } = request.body; + + logger.info('[RVW:Controller] 运行审查', { taskId, agents }); + + await reviewService.runReview({ taskId, agents, userId }); + + return reply.send({ + success: true, + message: '审查任务已启动', + }); + } catch (error) { + logger.error('[RVW:Controller] 运行审查失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(400).send({ + success: false, + message: error instanceof Error ? error.message : '运行审查失败', + }); + } +} + +/** + * 批量运行审查 + * POST /api/v2/rvw/tasks/batch/run + */ +export async function batchRunReview( + request: FastifyRequest<{ + Body: { + taskIds: string[]; + agents: AgentType[]; + }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const { taskIds, agents } = request.body; + + logger.info('[RVW:Controller] 批量运行审查', { taskCount: taskIds.length, agents }); + + const result = await reviewService.batchRunReview({ taskIds, agents, userId }); + + return reply.send({ + success: true, + message: `批量审查完成: ${result.success.length} 成功, ${result.failed.length} 失败`, + data: result, + }); + } catch (error) { + logger.error('[RVW:Controller] 批量运行失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(400).send({ + success: false, + message: error instanceof Error ? error.message : '批量运行失败', + }); + } +} + +// ==================== 任务查询 ==================== + +/** + * 获取任务列表 + * GET /api/v2/rvw/tasks + */ +export async function getTaskList( + request: FastifyRequest<{ + Querystring: { + status?: 'all' | 'pending' | 'completed'; + page?: string; + limit?: string; + }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const status = request.query.status || 'all'; + const page = parseInt(request.query.page || '1', 10); + const limit = parseInt(request.query.limit || '20', 10); + + logger.info('[RVW:Controller] 获取任务列表', { status, page, limit }); + + const result = await reviewService.getTaskList({ userId, status, page, limit }); + + return reply.send({ + success: true, + data: result.tasks, + pagination: result.pagination, + }); + } catch (error) { + logger.error('[RVW:Controller] 获取列表失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(500).send({ + success: false, + message: error instanceof Error ? error.message : '获取列表失败', + }); + } +} + +/** + * 获取任务详情 + * GET /api/v2/rvw/tasks/:taskId + */ +export async function getTaskDetail( + request: FastifyRequest<{ + Params: { taskId: string }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const { taskId } = request.params; + + logger.info('[RVW:Controller] 获取任务详情', { taskId }); + + const task = await reviewService.getTaskDetail(userId, taskId); + + // 🆕 直接使用新字段 + return reply.send({ + success: true, + data: { + id: task.id, + fileName: task.fileName, + fileSize: task.fileSize, + status: task.status, + selectedAgents: task.selectedAgents || ['editorial', 'methodology'], + wordCount: task.wordCount, + editorialScore: task.editorialScore, + methodologyStatus: task.methodologyStatus, + overallScore: task.overallScore, + modelUsed: task.modelUsed, + createdAt: task.createdAt, + startedAt: task.startedAt, + completedAt: task.completedAt, + durationSeconds: task.durationSeconds, + errorMessage: task.errorMessage, + }, + }); + } catch (error) { + logger.error('[RVW:Controller] 获取详情失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(404).send({ + success: false, + message: error instanceof Error ? error.message : '任务不存在', + }); + } +} + +/** + * 获取审查报告 + * GET /api/v2/rvw/tasks/:taskId/report + */ +export async function getTaskReport( + request: FastifyRequest<{ + Params: { taskId: string }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const { taskId } = request.params; + + logger.info('[RVW:Controller] 获取审查报告', { taskId }); + + const report = await reviewService.getTaskReport(userId, taskId); + + return reply.send({ + success: true, + data: report, + }); + } catch (error) { + logger.error('[RVW:Controller] 获取报告失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + + // 如果报告尚未完成,返回202 + if (error instanceof Error && error.message.includes('尚未完成')) { + return reply.status(202).send({ + success: false, + message: error.message, + }); + } + + return reply.status(404).send({ + success: false, + message: error instanceof Error ? error.message : '报告不存在', + }); + } +} + +/** + * 删除任务 + * DELETE /api/v2/rvw/tasks/:taskId + */ +export async function deleteTask( + request: FastifyRequest<{ + Params: { taskId: string }; + }>, + reply: FastifyReply +) { + try { + const userId = getUserId(request); + const { taskId } = request.params; + + logger.info('[RVW:Controller] 删除任务', { taskId }); + + await reviewService.deleteTask(userId, taskId); + + return reply.send({ + success: true, + message: '任务已删除', + }); + } catch (error) { + logger.error('[RVW:Controller] 删除失败', { + error: error instanceof Error ? error.message : 'Unknown error' + }); + return reply.status(404).send({ + success: false, + message: error instanceof Error ? error.message : '删除失败', + }); + } +} + diff --git a/backend/src/modules/rvw/index.ts b/backend/src/modules/rvw/index.ts new file mode 100644 index 00000000..0ea0ddc5 --- /dev/null +++ b/backend/src/modules/rvw/index.ts @@ -0,0 +1,25 @@ +/** + * RVW稿件审查模块 - 模块入口 + * @module rvw + * + * 功能: + * - 稿约规范性评估(11项标准) + * - 方法学评估(3部分,20个检查点) + * - 智能体选择(可选1个或2个) + * - 批量审查支持 + */ + +// 导出路由 +export { default as rvwRoutes } from './routes/index.js'; + +// 导出服务(供其他模块使用) +export * as reviewService from './services/reviewService.js'; +export * as editorialService from './services/editorialService.js'; +export * as methodologyService from './services/methodologyService.js'; + +// 导出类型 +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 new file mode 100644 index 00000000..7c7ef7ca --- /dev/null +++ b/backend/src/modules/rvw/routes/index.ts @@ -0,0 +1,46 @@ +/** + * RVW稿件审查模块 - 路由定义 + * @module rvw/routes + * + * API前缀: /api/v2/rvw + */ + +import type { FastifyInstance } from 'fastify'; +import * as reviewController from '../controllers/reviewController.js'; + +export default async function rvwRoutes(fastify: FastifyInstance) { + // ==================== 任务管理 ==================== + + // 创建任务(上传稿件) + // POST /api/v2/rvw/tasks + fastify.post('/tasks', reviewController.createTask); + + // 获取任务列表 + // GET /api/v2/rvw/tasks?status=all|pending|completed&page=1&limit=20 + fastify.get('/tasks', reviewController.getTaskList); + + // 获取任务详情 + // GET /api/v2/rvw/tasks/:taskId + fastify.get('/tasks/:taskId', reviewController.getTaskDetail); + + // 获取审查报告 + // GET /api/v2/rvw/tasks/:taskId/report + fastify.get('/tasks/:taskId/report', reviewController.getTaskReport); + + // 删除任务 + // DELETE /api/v2/rvw/tasks/:taskId + fastify.delete('/tasks/:taskId', reviewController.deleteTask); + + // ==================== 运行审查 ==================== + + // 运行审查(选择智能体) + // POST /api/v2/rvw/tasks/:taskId/run + // Body: { agents: ['editorial', 'methodology'] } + fastify.post('/tasks/:taskId/run', reviewController.runReview); + + // 批量运行审查 + // POST /api/v2/rvw/tasks/batch/run + // Body: { taskIds: [...], agents: ['editorial', 'methodology'] } + 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 new file mode 100644 index 00000000..baeae7f6 --- /dev/null +++ b/backend/src/modules/rvw/services/editorialService.ts @@ -0,0 +1,70 @@ +/** + * RVW稿件审查模块 - 稿约规范性评估服务 + * @module rvw/services/editorialService + */ + +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js'; +import { ModelType } from '../../../common/llm/adapters/types.js'; +import { logger } from '../../../common/logging/index.js'; +import { EditorialReview } from '../types/index.js'; +import { parseJSONFromLLMResponse } from './utils.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Prompt文件路径 +const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_editorial_system.txt'); + +/** + * 稿约规范性评估 + * @param text 稿件文本 + * @param modelType 模型类型 + * @returns 评估结果 + */ +export async function reviewEditorialStandards( + text: string, + modelType: ModelType = 'deepseek-v3' +): Promise { + try { + // 1. 读取系统Prompt + const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8'); + + // 2. 构建消息 + const messages = [ + { role: 'system' as const, content: systemPrompt }, + { role: 'user' as const, content: `请对以下稿件进行稿约规范性评估:\n\n${text}` }, + ]; + + // 3. 调用LLM + logger.info('[RVW:Editorial] 开始稿约规范性评估', { modelType }); + const llmAdapter = LLMFactory.getAdapter(modelType); + const response = await llmAdapter.chat(messages, { + temperature: 0.3, // 较低温度以获得更稳定的评估 + maxTokens: 8000, // 确保完整输出 + }); + logger.info('[RVW:Editorial] 评估完成', { + modelType, + responseLength: response.content.length + }); + + // 4. 解析JSON响应 + const result = parseJSONFromLLMResponse(response.content); + + // 5. 验证响应格式 + if (!result || typeof result.overall_score !== 'number' || !Array.isArray(result.items)) { + throw new Error('LLM返回的数据格式不正确'); + } + + return result; + } catch (error) { + logger.error('[RVW:Editorial] 稿约规范性评估失败', { + error: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : undefined, + }); + throw new Error(`稿约规范性评估失败: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + diff --git a/backend/src/modules/rvw/services/methodologyService.ts b/backend/src/modules/rvw/services/methodologyService.ts new file mode 100644 index 00000000..1844d57c --- /dev/null +++ b/backend/src/modules/rvw/services/methodologyService.ts @@ -0,0 +1,70 @@ +/** + * RVW稿件审查模块 - 方法学评估服务 + * @module rvw/services/methodologyService + */ + +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js'; +import { ModelType } from '../../../common/llm/adapters/types.js'; +import { logger } from '../../../common/logging/index.js'; +import { MethodologyReview } from '../types/index.js'; +import { parseJSONFromLLMResponse } from './utils.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Prompt文件路径 +const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_methodology_system.txt'); + +/** + * 方法学评估 + * @param text 稿件文本 + * @param modelType 模型类型 + * @returns 评估结果 + */ +export async function reviewMethodology( + text: string, + modelType: ModelType = 'deepseek-v3' +): Promise { + try { + // 1. 读取系统Prompt + const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8'); + + // 2. 构建消息 + const messages = [ + { role: 'system' as const, content: systemPrompt }, + { role: 'user' as const, content: `请对以下稿件进行方法学评估:\n\n${text}` }, + ]; + + // 3. 调用LLM + logger.info('[RVW:Methodology] 开始方法学评估', { modelType }); + const llmAdapter = LLMFactory.getAdapter(modelType); + const response = await llmAdapter.chat(messages, { + temperature: 0.3, + maxTokens: 8000, + }); + logger.info('[RVW:Methodology] 评估完成', { + modelType, + responseLength: response.content.length + }); + + // 4. 解析JSON响应 + const result = parseJSONFromLLMResponse(response.content); + + // 5. 验证响应格式 + if (!result || typeof result.overall_score !== 'number' || !Array.isArray(result.parts)) { + throw new Error('LLM返回的数据格式不正确'); + } + + return result; + } catch (error) { + logger.error('[RVW:Methodology] 方法学评估失败', { + error: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : undefined, + }); + throw new Error(`方法学评估失败: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + diff --git a/backend/src/modules/rvw/services/reviewService.ts b/backend/src/modules/rvw/services/reviewService.ts new file mode 100644 index 00000000..b076bc33 --- /dev/null +++ b/backend/src/modules/rvw/services/reviewService.ts @@ -0,0 +1,430 @@ +/** + * RVW稿件审查模块 - 主服务 + * @module rvw/services/reviewService + * + * 基于旧代码迁移:backend/src/legacy/services/reviewService.ts + * 改造内容: + * 1. console.log → logger + * 2. 新增智能体选择逻辑 + * 3. 新增批量运行接口 + * + * 注意:当前适配现有schema,Phase 2数据库迁移后可启用新字段 + */ + +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 { Prisma } from '@prisma/client'; +import { + AgentType, + TaskStatus, + TaskListParams, + TaskListResponse, + TaskSummary, + ReviewReport, + RunReviewParams, + BatchRunParams, + EditorialReview, + MethodologyReview, +} from '../types/index.js'; +import { reviewEditorialStandards } from './editorialService.js'; +import { reviewMethodology } from './methodologyService.js'; +import { + calculateOverallScore, + getMethodologyStatus, + validateAgentSelection, +} from './utils.js'; + +// ==================== 任务创建 ==================== + +/** + * 创建审查任务(上传稿件) + * @param file 文件Buffer + * @param filename 文件名 + * @param userId 用户ID + * @param modelType 模型类型 + * @returns 创建的任务 + */ +export async function createTask( + file: Buffer, + filename: string, + userId: string, + modelType: ModelType = 'deepseek-v3' +) { + logger.info('[RVW] 创建审查任务', { filename, userId, modelType }); + + // 创建任务记录(状态为pending,等待用户选择智能体后运行) + const task = await prisma.reviewTask.create({ + data: { + userId, + fileName: filename, + fileSize: file.length, + extractedText: '', // 初始为空,运行时提取 + status: 'pending', + modelUsed: modelType, + selectedAgents: ['editorial', 'methodology'], // 默认选择两个智能体 + }, + }); + + logger.info('[RVW] 任务已创建', { taskId: task.id, status: task.status }); + + // 异步提取文档文本(预处理,不运行评估) + extractDocumentAsync(task.id, file, filename).catch(error => { + logger.error('[RVW] 文档提取失败', { taskId: task.id, error: error.message }); + }); + + return task; +} + +/** + * 异步提取文档文本(预处理) + */ +async function extractDocumentAsync(taskId: string, file: Buffer, filename: string) { + try { + logger.info('[RVW] 开始提取文档', { taskId, filename }); + + const extractionResult = await extractionClient.extractDocument(file, filename); + + if (!extractionResult.success || !extractionResult.text) { + throw new Error('文档提取失败或内容为空'); + } + + const extractedText = extractionResult.text; + const wordCount = extractionResult.metadata?.char_count || extractedText.length; + + // 更新提取的文本(保持pending状态) + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + extractedText, + wordCount, + }, + }); + + logger.info('[RVW] 文档提取成功', { taskId, wordCount }); + } 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 : 'Document extraction failed', + }, + }); + } +} + +// ==================== 运行审查 ==================== + +/** + * 运行审查(核心功能) + * 支持选择1个或2个智能体 + */ +export async function runReview(params: RunReviewParams): Promise { + const { taskId, agents, userId } = params; + + // 验证智能体选择 + validateAgentSelection(agents); + + // 获取任务 + const task = await prisma.reviewTask.findFirst({ + where: { id: taskId, userId }, + }); + + if (!task) { + throw new Error('任务不存在或无权限访问'); + } + + if (task.status === 'reviewing' || task.status === 'reviewing_editorial' || task.status === 'reviewing_methodology') { + throw new Error('任务正在审查中,请稍后'); + } + + if (!task.extractedText) { + throw new Error('文档尚未提取完成,请稍后再试'); + } + + const startTime = Date.now(); + + try { + // 更新任务状态 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'reviewing', + startedAt: new Date(), + // 清除之前的结果(如果重新运行) + editorialReview: Prisma.JsonNull, + methodologyReview: Prisma.JsonNull, + overallScore: null, + errorMessage: null, + }, + }); + + logger.info('[RVW] 开始审查', { taskId, 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; + } +} + +/** + * 批量运行审查 + * 最多并发5个任务 + */ +export async function batchRunReview(params: BatchRunParams): Promise<{ + success: string[]; + failed: { taskId: string; error: string }[] +}> { + const { taskIds, agents, userId } = params; + + // 验证智能体选择 + validateAgentSelection(agents); + + if (taskIds.length === 0) { + throw new Error('请选择至少一个任务'); + } + + logger.info('[RVW] 开始批量审查', { + taskCount: taskIds.length, + agents + }); + + const success: string[] = []; + const failed: { taskId: string; error: string }[] = []; + + // 并发控制:最多5个 + const MAX_CONCURRENT = 5; + + for (let i = 0; i < taskIds.length; i += MAX_CONCURRENT) { + const batch = taskIds.slice(i, i + MAX_CONCURRENT); + + const results = await Promise.allSettled( + batch.map(taskId => runReview({ taskId, agents, userId })) + ); + + results.forEach((result, index) => { + const taskId = batch[index]; + if (result.status === 'fulfilled') { + success.push(taskId); + } else { + failed.push({ + taskId, + error: result.reason?.message || 'Unknown error', + }); + } + }); + } + + logger.info('[RVW] 批量审查完成', { + successCount: success.length, + failedCount: failed.length + }); + + return { success, failed }; +} + +// ==================== 任务查询 ==================== + +/** + * 获取任务列表 + */ +export async function getTaskList(params: TaskListParams): Promise { + const { userId, status = 'all', page = 1, limit = 20 } = params; + const skip = (page - 1) * limit; + + // 构建查询条件 + const where: Prisma.ReviewTaskWhereInput = { userId }; + + if (status === 'pending') { + where.status = { in: ['pending', 'extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'] }; + } else if (status === 'completed') { + where.status = { in: ['completed', 'failed'] }; + } + + const [tasks, total] = await Promise.all([ + prisma.reviewTask.findMany({ + where, + orderBy: { createdAt: 'desc' }, + skip, + take: limit, + select: { + id: true, + fileName: true, + fileSize: true, + status: true, + // 🆕 使用新字段 + selectedAgents: true, + editorialScore: true, + methodologyStatus: true, + overallScore: true, + modelUsed: true, + createdAt: true, + completedAt: true, + durationSeconds: true, + wordCount: true, + }, + }), + prisma.reviewTask.count({ where }), + ]); + + // 转换为TaskSummary格式(直接使用新字段) + const taskSummaries: TaskSummary[] = tasks.map(task => ({ + id: task.id, + fileName: task.fileName, + fileSize: task.fileSize, + status: task.status as TaskStatus, + selectedAgents: (task.selectedAgents || ['editorial', 'methodology']) as AgentType[], + editorialScore: task.editorialScore ?? undefined, + methodologyStatus: task.methodologyStatus as any, + overallScore: task.overallScore ?? undefined, + modelUsed: task.modelUsed ?? undefined, + createdAt: task.createdAt, + completedAt: task.completedAt ?? undefined, + durationSeconds: task.durationSeconds ?? undefined, + wordCount: task.wordCount ?? undefined, + })); + + return { + tasks: taskSummaries, + pagination: { + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }, + }; +} + +/** + * 获取任务详情 + */ +export async function getTaskDetail(userId: string, taskId: string) { + const task = await prisma.reviewTask.findFirst({ + where: { id: taskId, userId }, + }); + + if (!task) { + throw new Error('任务不存在或无权限访问'); + } + + return task; +} + +/** + * 获取任务报告 + */ +export async function getTaskReport(userId: string, taskId: string): Promise { + const task = await prisma.reviewTask.findFirst({ + where: { id: taskId, userId }, + }); + + if (!task) { + throw new Error('任务不存在或无权限访问'); + } + + if (task.status !== 'completed') { + throw new Error(`报告尚未完成,当前状态: ${task.status}`); + } + + return { + taskId: task.id, + fileName: task.fileName, + wordCount: task.wordCount ?? undefined, + modelUsed: task.modelUsed ?? undefined, + // 🆕 直接使用新字段 + selectedAgents: (task.selectedAgents || ['editorial', 'methodology']) as AgentType[], + overallScore: task.overallScore ?? undefined, + editorialReview: task.editorialReview as unknown as EditorialReview | undefined, + methodologyReview: task.methodologyReview as unknown as MethodologyReview | undefined, + completedAt: task.completedAt ?? undefined, + durationSeconds: task.durationSeconds ?? undefined, + }; +} + +/** + * 删除任务 + */ +export async function deleteTask(userId: string, taskId: string): Promise { + const task = await prisma.reviewTask.findFirst({ + where: { id: taskId, userId }, + }); + + if (!task) { + throw new Error('任务不存在或无权限访问'); + } + + await prisma.reviewTask.delete({ + where: { id: taskId }, + }); + + logger.info('[RVW] 任务已删除', { taskId }); +} diff --git a/backend/src/modules/rvw/services/utils.ts b/backend/src/modules/rvw/services/utils.ts new file mode 100644 index 00000000..9dc0ef1e --- /dev/null +++ b/backend/src/modules/rvw/services/utils.ts @@ -0,0 +1,116 @@ +/** + * RVW稿件审查模块 - 工具函数 + * @module rvw/services/utils + */ + +import { MethodologyReview, MethodologyStatus } from '../types/index.js'; + +/** + * 从LLM响应中解析JSON + * 支持多种格式:纯JSON、```json代码块、混合文本 + */ +export function parseJSONFromLLMResponse(content: string): T { + try { + // 1. 尝试直接解析 + return JSON.parse(content) as T; + } catch { + // 2. 尝试提取```json代码块 + const jsonMatch = content.match(/```json\s*\n?([\s\S]*?)\n?```/); + if (jsonMatch) { + try { + return JSON.parse(jsonMatch[1].trim()) as T; + } catch { + // 继续尝试其他方法 + } + } + + // 3. 尝试提取{}或[]包裹的内容 + const objectMatch = content.match(/(\{[\s\S]*\})/); + if (objectMatch) { + try { + return JSON.parse(objectMatch[1]) as T; + } catch { + // 继续尝试其他方法 + } + } + + const arrayMatch = content.match(/(\[[\s\S]*\])/); + if (arrayMatch) { + try { + return JSON.parse(arrayMatch[1]) as T; + } catch { + // 失败 + } + } + + // 4. 所有尝试都失败 + throw new Error('无法从LLM响应中解析JSON'); + } +} + +/** + * 根据方法学评估结果判断状态 + * @param review 方法学评估结果 + * @returns pass | warn | fail + */ +export function getMethodologyStatus(review: MethodologyReview | null | undefined): MethodologyStatus | undefined { + if (!review) return undefined; + + const score = review.overall_score; + + if (score >= 80) return 'pass'; + if (score >= 60) return 'warn'; + return 'fail'; +} + +/** + * 根据选择的智能体计算综合分数 + * @param editorialScore 稿约规范性分数 + * @param methodologyScore 方法学分数 + * @param agents 选择的智能体 + * @returns 综合分数 + */ +export function calculateOverallScore( + editorialScore: number | null | undefined, + methodologyScore: number | null | undefined, + agents: string[] +): number | null { + const hasEditorial = agents.includes('editorial') && editorialScore != null; + const hasMethodology = agents.includes('methodology') && methodologyScore != null; + + if (hasEditorial && hasMethodology) { + // 两个都选:稿约40% + 方法学60% + return editorialScore! * 0.4 + methodologyScore! * 0.6; + } else if (hasEditorial) { + // 只选规范性 + return editorialScore!; + } else if (hasMethodology) { + // 只选方法学 + return methodologyScore!; + } + + return null; +} + +/** + * 验证智能体选择 + * @param agents 选择的智能体列表 + * @throws 如果选择无效 + */ +export function validateAgentSelection(agents: string[]): void { + if (!agents || agents.length === 0) { + throw new Error('请至少选择一个智能体'); + } + + const validAgents = ['editorial', 'methodology']; + for (const agent of agents) { + if (!validAgents.includes(agent)) { + throw new Error(`无效的智能体类型: ${agent}`); + } + } + + if (agents.length > 2) { + throw new Error('最多只能选择2个智能体'); + } +} + diff --git a/backend/src/modules/rvw/types/index.ts b/backend/src/modules/rvw/types/index.ts new file mode 100644 index 00000000..f721a567 --- /dev/null +++ b/backend/src/modules/rvw/types/index.ts @@ -0,0 +1,157 @@ +/** + * RVW稿件审查模块 - 类型定义 + * @module rvw/types + */ + +// ==================== 智能体类型 ==================== + +/** + * 智能体类型 + * - editorial: 稿约规范性智能体 + * - methodology: 方法学统计智能体 + */ +export type AgentType = 'editorial' | 'methodology'; + +/** + * 任务状态 + */ +export type TaskStatus = + | 'pending' // 待处理 + | 'extracting' // 正在提取文档 + | 'reviewing' // 正在评估 + | 'completed' // 已完成 + | 'failed'; // 失败 + +/** + * 方法学评估状态 + */ +export type MethodologyStatus = 'pass' | 'warn' | 'fail'; + +// ==================== 稿约规范性评估 ==================== + +export interface EditorialItem { + criterion: string; + status: 'pass' | 'warning' | 'fail'; + score: number; + issues: string[]; + suggestions: string[]; +} + +export interface EditorialReview { + overall_score: number; + summary: string; + items: EditorialItem[]; +} + +// ==================== 方法学评估 ==================== + +export interface MethodologyIssue { + type: string; + severity: 'major' | 'minor'; + description: string; + location: string; + suggestion: string; +} + +export interface MethodologyPart { + part: string; + score: number; + issues: MethodologyIssue[]; +} + +export interface MethodologyReview { + overall_score: number; + summary: string; + parts: MethodologyPart[]; +} + +// ==================== 请求参数 ==================== + +/** + * 运行审查的参数 + */ +export interface RunReviewParams { + taskId: string; + agents: AgentType[]; // 可选1个或2个 + userId: string; +} + +/** + * 批量运行审查的参数 + */ +export interface BatchRunParams { + taskIds: string[]; + agents: AgentType[]; + userId: string; +} + +/** + * 任务列表查询参数 + */ +export interface TaskListParams { + userId: string; + status?: 'all' | 'pending' | 'completed'; + page?: number; + limit?: number; +} + +// ==================== 响应类型 ==================== + +/** + * 任务摘要(用于列表展示) + */ +export interface TaskSummary { + id: string; + fileName: string; + fileSize: number; + status: TaskStatus; + selectedAgents: AgentType[]; + editorialScore?: number; + methodologyStatus?: MethodologyStatus; + overallScore?: number; + modelUsed?: string; + createdAt: Date; + completedAt?: Date; + durationSeconds?: number; + wordCount?: number; +} + +/** + * 任务列表响应 + */ +export interface TaskListResponse { + tasks: TaskSummary[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +} + +/** + * 完整报告 + */ +export interface ReviewReport { + taskId: string; + fileName: string; + wordCount?: number; + modelUsed?: string; + selectedAgents: AgentType[]; + overallScore?: number; + editorialReview?: EditorialReview; + methodologyReview?: MethodologyReview; + completedAt?: Date; + durationSeconds?: number; +} + +// ==================== 工具函数类型 ==================== + +/** + * LLM消息格式 + */ +export interface LLMMessage { + role: 'system' | 'user' | 'assistant'; + content: string; +} + diff --git a/backend/src/tests/README.md b/backend/src/tests/README.md index f42c1447..def1e294 100644 --- a/backend/src/tests/README.md +++ b/backend/src/tests/README.md @@ -411,5 +411,6 @@ SET session_replication_role = 'origin'; + diff --git a/backend/src/tests/verify-test1-database.sql b/backend/src/tests/verify-test1-database.sql index 13836eab..ab529306 100644 --- a/backend/src/tests/verify-test1-database.sql +++ b/backend/src/tests/verify-test1-database.sql @@ -113,5 +113,6 @@ WHERE key = 'verify_test'; + diff --git a/backend/src/tests/verify-test1-database.ts b/backend/src/tests/verify-test1-database.ts index 890e919c..6e54755a 100644 --- a/backend/src/tests/verify-test1-database.ts +++ b/backend/src/tests/verify-test1-database.ts @@ -256,5 +256,6 @@ verifyDatabase() + diff --git a/backend/src/types/global.d.ts b/backend/src/types/global.d.ts index aac1b490..3cb6bc1b 100644 --- a/backend/src/types/global.d.ts +++ b/backend/src/types/global.d.ts @@ -46,5 +46,6 @@ export {} + diff --git a/backend/sync-dc-database.ps1 b/backend/sync-dc-database.ps1 index e56963ba..f43fe2be 100644 --- a/backend/sync-dc-database.ps1 +++ b/backend/sync-dc-database.ps1 @@ -69,5 +69,6 @@ Write-Host "✅ 完成!" -ForegroundColor Green + diff --git a/backend/test-pkb-migration.http b/backend/test-pkb-migration.http index faf0491f..49f2698f 100644 --- a/backend/test-pkb-migration.http +++ b/backend/test-pkb-migration.http @@ -159,3 +159,4 @@ 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 98fc4580..58f3890d 100644 --- a/backend/test-tool-c-advanced-scenarios.mjs +++ b/backend/test-tool-c-advanced-scenarios.mjs @@ -356,5 +356,6 @@ runAdvancedTests().catch(error => { + diff --git a/backend/test-tool-c-day2.mjs b/backend/test-tool-c-day2.mjs index 448d79ee..9cb5a31a 100644 --- a/backend/test-tool-c-day2.mjs +++ b/backend/test-tool-c-day2.mjs @@ -422,5 +422,6 @@ runAllTests() + diff --git a/backend/test-tool-c-day3.mjs b/backend/test-tool-c-day3.mjs index 0b45e268..a6cc0a1a 100644 --- a/backend/test-tool-c-day3.mjs +++ b/backend/test-tool-c-day3.mjs @@ -380,5 +380,6 @@ runAllTests() + diff --git a/deploy-to-sae.ps1 b/deploy-to-sae.ps1 index 9810f793..00552f9d 100644 --- a/deploy-to-sae.ps1 +++ b/deploy-to-sae.ps1 @@ -164,5 +164,6 @@ Set-Location .. + diff --git a/docs/00-系统总体设计/00-系统当前状态与开发指南.md b/docs/00-系统总体设计/00-系统当前状态与开发指南.md index d17da068..8ed4df2b 100644 --- a/docs/00-系统总体设计/00-系统当前状态与开发指南.md +++ b/docs/00-系统总体设计/00-系统当前状态与开发指南.md @@ -1,10 +1,10 @@ # AIclinicalresearch 系统当前状态与开发指南 -> **文档版本:** v2.8 +> **文档版本:** v2.9 > **创建日期:** 2025-11-28 > **维护者:** 开发团队 > **最后更新:** 2026-01-07 -> **重大进展:** 🎉 **PKB模块核心功能全部实现,具备生产可用性!** - 批处理完整流程验证通过 +> **重大进展:** 🎉 **RVW稿件审查模块开发完成(85%)!** - 后端迁移+数据库扩展+前端重构全部完成 > **部署状态:** ✅ 生产环境运行中 | 公网地址: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** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | 📋 规划中 | P3 | +| **RVW** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | ✅ **开发完成(85%)** | P3 | --- @@ -661,6 +661,7 @@ AIclinicalresearch/ | **2026-01-07 上午** | **PKB前端V3** 🎉 | ✅ PKB模块前端V3设计实现完成(Dashboard+Workspace+3种工作模式) | | **2026-01-07 下午** | **PKB批处理完善** 🏆 | ✅ 批处理完整流程调试通过(执行+进度+结果导出)+ 文档上传功能 + UI优化 | | **当前** | **PKB模块生产可用** | ✅ 核心功能全部实现(90%),具备生产环境部署条件 | +| **2026-01-07 晚** | **RVW模块开发完成** 🎉 | ✅ Phase 1-3完成(后端迁移+数据库扩展+前端重构) | --- @@ -814,9 +815,9 @@ npm run dev # http://localhost:3000 - **总计**:约 85,000 行 ### 模块完成度 -- ✅ **已完成**:AIA(100%)、平台基础层(100%) -- 🚧 **开发中**:PKB(75%,前端V3设计完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成) -- 📋 **未开始**:SSA、ST、RVW +- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(85%,Phase 1-3完成) +- 🚧 **开发中**:PKB(90%,核心功能完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成) +- 📋 **未开始**:SSA、ST ### 部署完成度 - ✅ **基础设施**:VPC(100%)、NAT网关(100%)、安全组(100%) @@ -952,9 +953,9 @@ if (items.length >= 50) { --- -**文档版本**:v2.8 +**文档版本**:v2.9 **最后更新**:2026-01-07 -**下次更新**:ASL智能文献筛选模块启动 或 IIT Manager Agent Phase 2 +**下次更新**:RVW生产环境部署 或 ASL智能文献筛选模块启动 --- @@ -1016,3 +1017,36 @@ if (items.length >= 50) { - ✅ 测试通过:查询test0102项目,ID 7患者详细信息 **模块进度**:60%完成(Phase 1.5) + +--- + +**RVW稿件审查模块开发完成(2026-01-07)**: + +### Phase 1:后端模块迁移与扩展 +- ✅ 创建 `backend/src/modules/rvw/` 模块结构 +- ✅ 迁移 reviewService、editorialService、methodologyService +- ✅ 实现智能体选择(selectedAgents) +- ✅ 实现批量运行API(batchRunReviewTasks) +- ✅ 替换 console.log 为 logger 服务 +- ✅ 注册 v2 API路由(/api/v2/rvw) + +### Phase 2:数据库字段扩展 +- ✅ 添加 selectedAgents、editorialScore、methodologyStatus 字段 +- ✅ 添加 picoExtract、isArchived、archivedAt 字段 +- ✅ 使用 prisma db push 同步到数据库 + +### Phase 3:前端重构(frontend-v2) +- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行) +- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作) +- ✅ 实现 ReportDetail 组件(双标签页切换) +- ✅ 实现 AgentModal 组件(智能体选择弹窗) +- ✅ 注册到 moduleRegistry.ts +- ✅ 添加顶部导航"预审稿"入口 + +**技术亮点**: +- 🔥 **新旧API兼容**:v1 + v2 API同时运行 +- 🔥 **智能体可选**:用户可选择运行稿约规范性/方法学/两者 +- 🔥 **批量操作**:支持多选任务批量运行 +- 🔥 **云原生改造**:使用 logger 服务,遵循开发规范 + +**模块进度**:85%完成(Phase 1-3) diff --git a/docs/02-通用能力层/Postgres-Only异步任务处理指南.md b/docs/02-通用能力层/Postgres-Only异步任务处理指南.md index 9a7d56b9..e3ecac8e 100644 --- a/docs/02-通用能力层/Postgres-Only异步任务处理指南.md +++ b/docs/02-通用能力层/Postgres-Only异步任务处理指南.md @@ -606,5 +606,6 @@ async saveProcessedData(recordId, newData) { + diff --git a/docs/02-通用能力层/通用能力层技术债务清单.md b/docs/02-通用能力层/通用能力层技术债务清单.md index 9607211a..0c60b246 100644 --- a/docs/02-通用能力层/通用能力层技术债务清单.md +++ b/docs/02-通用能力层/通用能力层技术债务清单.md @@ -793,5 +793,6 @@ export const AsyncProgressBar: React.FC = ({ + diff --git a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md index c0ce7e22..0cc773a2 100644 --- a/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md +++ b/docs/03-业务模块/ASL-AI智能文献/04-开发计划/05-全文复筛前端开发计划.md @@ -1286,5 +1286,6 @@ 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 c3bc47d2..3b2168d3 100644 --- a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md +++ b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端开发完成.md @@ -400,5 +400,6 @@ 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 15e4df10..ebf6155b 100644 --- a/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md +++ b/docs/03-业务模块/ASL-AI智能文献/05-开发记录/2025-01-23_全文复筛前端逻辑调整.md @@ -343,5 +343,6 @@ 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 84f92f96..2c7dcb0f 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,5 +502,6 @@ 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 334b74e5..3f2ccb34 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_AI_Few-shot示例库.md @@ -568,5 +568,6 @@ 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 c443293b..c920794a 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Bug修复总结_2025-12-08.md @@ -406,5 +406,6 @@ npm run dev + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md index 1f784515..accfb412 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day3开发计划.md @@ -983,5 +983,6 @@ 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 3fabc2d2..eb261ebf 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Day4-5前端开发计划.md @@ -1317,5 +1317,6 @@ npm install react-markdown + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md index 23f18bfd..32e8e879 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_Pivot列顺序优化总结.md @@ -225,5 +225,6 @@ 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 cceac4b1..92699755 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_方案B实施总结_2025-12-09.md @@ -383,5 +383,6 @@ 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 6d9cfdbb..f149e2f5 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理_开发进度_2025-12-10.md @@ -217,5 +217,6 @@ async handleFillnaMice(request, reply) { + diff --git a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md index fed62f12..40b2c4b1 100644 --- a/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md +++ b/docs/03-业务模块/DC-数据清洗整理/04-开发计划/工具C_缺失值处理功能_更新说明.md @@ -189,5 +189,6 @@ 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 6db4543c..01b63f44 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-02_工作总结.md @@ -339,5 +339,6 @@ 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 3183dcab..18abb989 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day1开发完成总结.md @@ -411,5 +411,6 @@ 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 4b728135..7956a0fe 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md @@ -640,5 +640,6 @@ 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 bf2426a0..577c2796 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_AI对话核心功能增强总结.md @@ -644,5 +644,6 @@ 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 2b43aa94..ea7bc953 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Bug修复_DataGrid空数据防御.md @@ -296,5 +296,6 @@ 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 d26b4990..464445cc 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,5 +449,6 @@ Response: + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md index f0edc62b..a4594d3e 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_Day5最终总结.md @@ -443,5 +443,6 @@ 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 ba52956d..9c196435 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_UI优化与Bug修复.md @@ -353,5 +353,6 @@ 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 892fbe74..535b1461 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_后端API完整对接完成.md @@ -393,5 +393,6 @@ 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 89864b29..fcc4abd9 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_完整UI优化与功能增强.md @@ -641,5 +641,6 @@ 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 f5ec2fd9..3586040b 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-07_工具C_Day4前端基础完成.md @@ -251,5 +251,6 @@ Day 5 (6-8小时): + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md index e2c9caca..7bb00493 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建完成总结-Day1.md @@ -429,5 +429,6 @@ 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 6599f089..03e87640 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Phase1-Portal页面开发完成-2025-12-02.md @@ -404,5 +404,6 @@ 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 91fb094c..abaf8d63 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,5 +388,6 @@ 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 b91fb52f..2e1e74b8 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/Portal页面UI优化-2025-12-02.md @@ -348,5 +348,6 @@ + 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 a639960a..ba0009f7 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,5 +302,6 @@ 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 3c4c88f1..47667652 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB-UI优化-2025-12-03.md @@ -351,5 +351,6 @@ + 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 493b4cb6..53ffa2b2 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,5 +314,6 @@ + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md index 1e534a90..c95557e5 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/ToolB浏览器测试计划-2025-12-03.md @@ -378,5 +378,6 @@ + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md index 8148a6fb..47c0974c 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/后端API测试报告-2025-12-02.md @@ -466,5 +466,6 @@ Tool B后端代码**100%复用**了平台通用能力层,无任何重复开发 + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md index d806371c..c282ffc4 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/待办事项-下一步工作.md @@ -312,5 +312,6 @@ + diff --git a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md index fcbd191f..c01abcc2 100644 --- a/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md +++ b/docs/03-业务模块/DC-数据清洗整理/06-开发记录/数据库验证报告-2025-12-02.md @@ -243,5 +243,6 @@ $ 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 9c9efb6f..69882fb4 100644 --- a/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md +++ b/docs/03-业务模块/DC-数据清洗整理/07-技术债务/Tool-B技术债务清单.md @@ -476,5 +476,6 @@ ${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 1b380cb3..ce63eceb 100644 --- a/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md +++ b/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 技术路径与架构设计.md @@ -683,3 +683,4 @@ 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 5ce28572..07d9b798 100644 --- a/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md +++ b/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md @@ -1077,3 +1077,4 @@ async function testIntegration() { + diff --git a/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md b/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md index 4b315ff1..312f29f5 100644 --- a/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md +++ b/docs/03-业务模块/IIT Manager Agent/04-开发计划/企业微信注册指南.md @@ -218,3 +218,4 @@ 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 50138611..39cc08fd 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 @@ -638,3 +638,4 @@ 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 819e6694..35e9553d 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day2-REDCap实时集成开发完成记录.md @@ -644,3 +644,4 @@ 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 1003abfd..39f4693c 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成与端到端测试完成记录.md @@ -794,3 +794,4 @@ 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 275a682b..83a33311 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md @@ -551,3 +551,4 @@ 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 e871ddbc..46970b7f 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 @@ -318,3 +318,4 @@ 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 bb04b157..82689aba 100644 --- a/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md +++ b/docs/03-业务模块/IIT Manager Agent/06-开发记录/V1.1更新完成报告.md @@ -262,3 +262,4 @@ 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 e4756e69..b16da9e5 100644 --- a/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md +++ b/docs/03-业务模块/IIT Manager Agent/07-技术债务/IIT Manager Agent 技术债务清单.md @@ -676,3 +676,4 @@ 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 bdd66799..3f330d23 100644 --- a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md +++ b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07-前端迁移与批处理功能完善.md @@ -356,3 +356,4 @@ const newResults = resultsData.map((docResult: any) => ({ 3. 增加更多模板和自定义能力 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 84de9a98..7170499a 100644 --- a/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md +++ b/docs/03-业务模块/PKB-个人知识库/06-开发记录/2026-01-07_PKB模块前端V3设计实现.md @@ -229,3 +229,4 @@ const chatApi = axios.create({ **下次更新**:批处理功能调试完成后 + diff --git a/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md b/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md new file mode 100644 index 00000000..9a6c3c69 --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/00-模块当前状态与开发指南.md @@ -0,0 +1,338 @@ +# RVW稿件审查模块 - 当前状态与开发指南 + +> **文档版本:** v3.0 +> **创建日期:** 2026-01-07 +> **最后更新:** 2026-01-07 +> **维护者:** 开发团队 +> **当前状态:** ✅ **Phase 1-3 完成,前后端功能可用** +> **文档目的:** 快速了解RVW模块状态,为新AI助手提供上下文 + +--- + +## 📊 模块概览 + +### 基本信息 + +| 项目 | 信息 | +|------|------| +| **模块名称** | RVW - 稿件审查系统 (Review) | +| **模块定位** | 智能期刊审稿辅助系统(可独立销售) | +| **商业价值** | ⭐⭐⭐⭐⭐ 极高 | +| **独立性** | ⭐⭐⭐⭐⭐ 极高(用户群完全不同) | +| **目标用户** | 期刊初审编辑 | +| **开发状态** | ✅ **核心功能100%完成,已集成到 frontend-v2** | + +### 核心目标 + +> 打造一个**"开箱即用"**的智能审稿工具。编辑上传稿件,系统自动运行双重检查(规范性+方法学),输出可供参考的审稿报告。 +> +> **核心指标**:上传到出报告 < 2分钟;规范性问题检出率 > 80% + +### 功能规格 + +#### 核心AI能力(已完成 ✅) + +1. **稿约规范性评估**(11项标准) + - 文题字数、摘要结构、参考文献、图片DPI等 + - 基于《中华医学超声杂志》稿约标准 + +2. **方法学评估**(3部分,20个检查点) + - 科研设计评估(9个检查点) + - 统计学方法描述评估(5个检查点) + - 统计分析评估(6个检查点) + +3. **综合评分 + PICO提取** + - 规范性分数(0-100) + - 方法学状态(🔴错误 🟡存疑 🟢通过) + - PICO结构化提取(P/I/C/O) + +#### 交互功能(✅ 已完成) + +| 功能 | 旧版本 | 新版本 | 状态 | +|------|--------|--------|------| +| 单文件上传 | ✅ | ✅ | ✅ 已完成 | +| 批量上传 | ❌ | ✅ | ✅ 已完成 | +| 审稿工作台(宽表) | ❌ | ✅ | ✅ 已完成 | +| 智能体选择 | 自动 | 用户可选 | ✅ 已完成 | +| 批量操作 | ❌ | ✅ | ✅ 已完成 | +| 状态筛选 | ❌ | ✅ | ✅ 已完成 | +| 历史归档 | ❌ | ✅ | ⏸️ 数据库已支持,UI暂缓 | +| 系统设置 | ❌ | ✅ | ⏸️ 暂不开发 | +| 登录页面 | ❌ | ⏸️ | ⏸️ 复用平台登录 | +| PICO卡片 | ❌ | ✅ | ⏸️ 数据库已支持,UI暂缓 | + +--- + +## 🏗️ 架构状态 + +### ✅ 已完成迁移(Modules) + +``` +后端(✅ 已完成): +backend/src/modules/rvw/ +├── routes/index.ts # v2 API路由(/api/v2/rvw) +├── controllers/reviewController.ts # 控制器(含批量操作、智能体选择) +├── services/ +│ ├── reviewService.ts # 核心服务(任务创建、执行) +│ ├── editorialService.ts # 稿约规范性评估 +│ ├── methodologyService.ts # 方法学评估 +│ └── utils.ts # 工具函数 +├── types/index.ts # TypeScript类型定义 +└── __tests__/ # API测试脚本 + +前端(✅ 已完成): +frontend-v2/src/modules/rvw/ +└── index.tsx # 完整模块(~503行) + ├── Dashboard组件 # 审稿工作台(宽表、筛选、批量操作) + ├── ReportDetail组件 # 报告详情(双标签页切换) + └── AgentModal组件 # 智能体选择弹窗 + +旧版本(保留兼容): +backend/src/legacy/ +├── routes/reviewRoutes.ts # v1 API路由(保留) +├── controllers/reviewController.ts +└── services/reviewService.ts + +Prompt(保持不变): +backend/prompts/ +├── review_editorial_system.txt # 稿约评估(266行) +└── review_methodology_system.txt # 方法学评估(257行) + +数据库(✅ 已完成): +- ReviewTask表已添加新字段:selectedAgents, editorialScore, methodologyStatus, picoExtract, isArchived, archivedAt +- Schema迁移待后续执行(当前在public schema) +``` + +--- + +## 📋 开发进度 + +| Phase | 任务 | 状态 | 完成日期 | +|-------|------|------|---------| +| 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 1-3 完成内容 + +**后端(Phase 1):** +- ✅ 创建 `backend/src/modules/rvw/` 模块结构 +- ✅ 迁移并优化 reviewService、editorialService、methodologyService +- ✅ 实现智能体选择(selectedAgents:editorial/methodology/both) +- ✅ 实现批量运行API(batchRunReviewTasks) +- ✅ 替换 console.log 为 logger 服务 +- ✅ 注册 v2 API路由(/api/v2/rvw) + +**数据库(Phase 2):** +- ✅ 添加 selectedAgents 字段(String[]) +- ✅ 添加 editorialScore 字段(Float?) +- ✅ 添加 methodologyStatus 字段(String?) +- ✅ 添加 picoExtract 字段(Json?) +- ✅ 添加 isArchived、archivedAt 字段(归档支持) +- ✅ 使用 prisma db push 同步到数据库 + +**前端(Phase 3):** +- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行) +- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作) +- ✅ 实现 ReportDetail 组件(双标签页:稿约规范性/方法学) +- ✅ 实现 AgentModal 组件(智能体选择弹窗) +- ✅ 注册到 moduleRegistry.ts +- ✅ 添加顶部导航入口 + +详细任务清单见:[RVW模块迁移计划](./04-开发计划/RVW模块迁移计划.md) + +--- + +## 🔧 技术依赖 + +### 复用的平台能力 + +| 能力 | 位置 | 用途 | +|------|------|------| +| **LLM网关** | `@/common/llm/LLMFactory` | AI评估 | +| **文档处理** | `ExtractionClient` | Word/PDF文本提取 | +| **存储** | `@/common/storage` | 文件存储 | +| **日志** | `@/common/logging` | 结构化日志 | +| **任务队列** | `jobQueue` | 异步任务处理 | + +### LLM模型 + +| 模型 | 用途 | 说明 | +|------|------|------| +| DeepSeek-V3 | 默认 | 性价比高,推理能力强 | +| DeepSeek-R1 | 备选 | 深度推理 | +| Qwen3-72B | 备选 | 中文理解好 | + +--- + +## 📚 相关文档 + +### 需求文档 + +- [智能期刊审稿系统MVP PRD](./01-需求分析/智能期刊审稿系统%20MVP%20产品需求文档.md) +- [智能审稿V7原型](./01-需求分析/智能审稿V7.html) - 可直接浏览器打开 + +### 开发文档 + +- [迁移计划(v2.0整合版)](./04-开发计划/RVW模块迁移计划.md) ⬅️ **主要开发文档** + +### 参考文档 + +- [现有系统技术摸底报告](../../00-项目概述/现有系统技术摸底报告.md) - Line 578-748 +- [云原生开发规范](../../04-开发规范/08-云原生开发规范.md) +- [系统架构分层设计](../../00-系统总体设计/01-系统架构分层设计.md) + +### Prompt文件 + +- [稿约规范性评估标准](./稿约规范性评估标准.txt) +- [稿件方法学评估标准](./稿件方法学评估标准.txt) + +--- + +## 🎯 快速开始 + +### 访问审稿模块 + +1. **启动后端** + ```bash + cd backend + npm run dev + ``` + +2. **启动前端(新版 frontend-v2)** + ```bash + cd frontend-v2 + npm run dev + ``` + +3. **访问审稿页面** + - 打开 `http://localhost:3000` + - 点击顶部导航栏的 **"预审稿"** 标签 + - 或直接访问 `http://localhost:3000/rvw` + +4. **测试流程** + - 点击"上传稿件"按钮 + - 选择Word/PDF文档(≤5MB) + - 选择智能体(稿约规范性/方法学/两者都选) + - 点击"运行"按钮 + - 等待AI评估完成(约1-2分钟) + - 查看评估报告 + +### API测试(新版 v2 API) + +```http +### 获取任务列表 +GET http://localhost:3001/api/v2/rvw/tasks + +### 按状态筛选 +GET http://localhost:3001/api/v2/rvw/tasks?status=pending + +### 上传稿件 +POST http://localhost:3001/api/v2/rvw/tasks +Content-Type: multipart/form-data +# file: 文件 +# selectedAgents: ["editorial", "methodology"] + +### 运行单个任务 +POST http://localhost:3001/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json +{"selectedAgents": ["editorial", "methodology"]} + +### 批量运行任务 +POST http://localhost:3001/api/v2/rvw/tasks/batch-run +Content-Type: application/json +{"taskIds": ["id1", "id2"], "selectedAgents": ["editorial"]} + +### 获取任务详情 +GET http://localhost:3001/api/v2/rvw/tasks/{{taskId}} + +### 获取报告 +GET http://localhost:3001/api/v2/rvw/tasks/{{taskId}}/report +``` + +### 旧版API(保持兼容) + +```http +### 上传稿件(旧API) +POST http://localhost:3001/api/v1/review/upload +Content-Type: multipart/form-data +``` + +--- + +## ⚠️ 注意事项 + +### 对新AI助手 + +1. ✅ **核心功能已完成**:前后端已迁移到新架构,可正常使用 +2. ✅ **已集成到 frontend-v2**:通过顶部导航栏"预审稿"进入 +3. ✅ **v2 API 已就绪**:/api/v2/rvw/* 路由可用 +4. ✅ **遵循云原生规范**:使用 logger 服务替代 console.log +5. ⚠️ **保留旧API**:v1路由保持兼容,支持旧前端 + +### 已完成改造 + +| 问题 | 改造前 | 改造后 | 状态 | +|------|--------|--------|------| +| 日志 | console.log | logger服务 | ✅ 已完成 | +| 用户认证 | Mock用户ID | getUserId(request) | ✅ 已完成 | +| 智能体选择 | 自动 | 用户可选 | ✅ 已完成 | +| 批量操作 | 无 | batchRunReviewTasks | ✅ 已完成 | + +### 待后续改造 + +| 问题 | 当前 | 目标 | 优先级 | +|------|------|------|--------| +| Schema位置 | public | review_schema | P2 | +| 任务处理 | 直接异步 | jobQueue | P2 | +| 报告导出 | 基础版 | PDF优化 | P3 | + +--- + +## 📈 验收标准 + +根据MVP PRD,验收标准如下: + +1. **流程通**:用户能成功上传5个PDF,勾选双模型运行,等待3分钟内,状态全部变为"已完成" + +2. **报告准**: + - 上传一篇故意删掉"摘要结论"的稿件,规范性智能体必须报错 + - 上传一篇故意混淆"t检验"和"卡方检验"的稿件,方法学智能体必须报"存疑"或"错误" + +3. **无崩溃**:连续上传20个文件,系统不卡死,不白屏 + +--- + +## 🚀 未来规划 + +### ✅ 已完成(2026-01-07) + +- [x] 架构迁移到 modules/rvw(后端) +- [x] 架构迁移到 modules/rvw(前端 frontend-v2) +- [x] 整合MVP新功能(批量上传、工作台、智能体选择) +- [x] 云原生改造(logger服务) +- [x] v2 API 路由注册 +- [x] 数据库字段扩展 + +### 后续版本 + +- [ ] Schema迁移到 review_schema +- [ ] 任务队列改造(jobQueue) +- [ ] PDF报告导出优化 +- [ ] PICO卡片UI实现 +- [ ] 历史归档UI实现 +- [ ] 登录页面(独立产品时) +- [ ] 审稿人管理系统 +- [ ] 多轮审稿流程 +- [ ] 期刊库管理 +- [ ] 独立产品打包 + +--- + +**文档版本:** v3.0 +**最后更新:** 2026-01-07 +**当前状态:** ✅ Phase 1-3 完成,模块可用 +**下一步:** 生产环境部署测试 或 Schema隔离迁移 diff --git a/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能审稿V7.html b/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能审稿V7.html new file mode 100644 index 00000000..03c42f23 --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能审稿V7.html @@ -0,0 +1,529 @@ + + + + + + 智能期刊审稿系统 - 中华脑血管病杂志专版 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能期刊审稿系统 MVP 产品需求文档.md b/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能期刊审稿系统 MVP 产品需求文档.md new file mode 100644 index 00000000..5de3e6bd --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/01-需求分析/智能期刊审稿系统 MVP 产品需求文档.md @@ -0,0 +1,135 @@ +# **智能期刊审稿系统 MVP 产品需求文档** + +| 项目属性 | 内容 | +| :---- | :---- | +| **项目名称** | 智能期刊审稿辅助系统 (SmartReview MVP) | +| **文档版本** | V1.0 (MVP) | +| **优先级** | P0 (核心功能闭环) | +| **目标用户** | 期刊初审编辑 | + +## **1\. 产品目标 (Product Goal)** + +打造一个\*\*“开箱即用”\*\*的智能审稿工具。编辑上传稿件,系统自动运行双重检查(规范性+方法学),输出可供参考的审稿报告。 +核心指标:上传到出报告 \< 2分钟;规范性问题检出率 \> 80%。 + +## **2\. 功能范围 (Scope)** + +### **✅ MVP 包含 (In Scope)** + +1. **账户体系**:简单的账号登录,支持期刊 Logo 定制。 +2. **文件处理**:批量上传 Word/PDF,列表展示状态。 +3. **审查流程**:用户手动选择智能体发起审查,后台异步运行。 +4. **规范性审查**:覆盖字数、摘要结构、参考文献、图片DPI等硬指标。 +5. **方法学审查**:PICO 提取、研究类型识别、基础统计逻辑校验。 +6. **报告与导出**:双视图报告展示,支持 PDF 导出。 + +### **❌ MVP 不包含 (Out of Scope)** + +1. 在线文档编辑器 (Web Office)。 +2. 复杂的角色权限管理系统。 +3. 邮件自动发送功能。 +4. 高级历史归档与检索系统。 +5. 前端可视化的 Prompt 配置后台。 + +## **3\. 详细功能需求 (Requirements)** + +### **3.1 登录页 (Login)** + +* **功能**:账号密码验证。 +* **UI**: + * 左/上侧展示期刊 Logo 与名称(支持配置)。 + * 输入框:账号、密码。 + * 登录按钮:点击校验,失败提示“账号或密码错误”。 +* **逻辑**:Session 保持 24 小时,避免频繁登录。 + +### **3.2 审稿工作台 (Dashboard)** + +这是系统的主界面,采用\*\*“宽表”\*\*布局。 + +#### **3.2.1 顶部操作区** + +* **品牌区**:展示“XX杂志社智能审稿系统”。 +* **筛选栏**: + * 状态 (全部/待处理/已完成)。 + * 时间 (今天/近7天)。 +* **上传按钮**: + * 点击触发文件选择器。 + * 支持多选。 + * 支持扩展名:.docx, .pdf。 + * 限制:单文件 \< 50MB。 + +#### **3.2.2 稿件列表** + +列表包含以下列: + +* 复选框:用于批量操作。 +* 文件信息:文件名(点击进入详情)、大小、上传时间。 +* 审稿维度:展示 Tags(规范性、方法学)。 +* 状态: + * **待处理**:刚上传,未运行。 + * **运行中**:展示动态 Loading 图标。 + * **已完成**: + * 若跑了规范性,显示分数(如:绿色 "90分")。 + * 若跑了方法学,显示结论(如:黄色 "存疑")。 +* 操作列: + * \[开始审查\]:针对待处理状态。 + * \[查看报告\]:针对已完成状态。 + +#### **3.2.3 任务发起弹窗 (The Launcher)** + +* **触发**:点击列表中的“开始审查”或底部批量操作栏的“运行”。 +* **内容**: + * 标题:“发起智能审查”。 + * 复选框组: + * \[x\] 稿约规范性智能体 (默认选中)。 + * \[ \] 方法学统计智能体。 +* **逻辑**:点击确定后,后端创建任务,列表状态变为“运行中”。 + +### **3.3 审稿详情页 (Report Detail)** + +#### **3.3.1 头部信息** + +* 展示文件名、作者(若能提取)、版本标签(MVP可暂不显示)。 +* 按钮:\[下载原稿\]、\[导出报告PDF\]。 + +#### **3.3.2 视图 A:稿约规范性报告** + +* **总评卡片**:展示总分 (0-100) 和 结论 (Pass/Fail)。 +* **检查项列表**: + * **文题字数**:提取字数,对比标准(如 \<20字)。 + * **摘要结构**:正则匹配“目的/方法/结果/结论”关键词。 + * **参考文献**:识别引用格式错误。 + * **图片质量**:显示低分辨率图片的页码和 DPI 值。 +* **交互**:每个报错项下方提供“建议修改意见”,支持点击复制。 + +#### **3.3.3 视图 B:方法学评估报告** + +* **PICO 卡片**:结构化展示提取出的 P/I/C/O 内容。 +* **逻辑推理区**: + * 展示模型对“研究类型”的判断(如:回顾性队列研究)。 + * 展示“统计方法”的合理性分析(如:发现多组比较未用 ANOVA)。 + * **红绿灯**: + * 🔴 **错误**:明确的逻辑硬伤。 + * 🟡 **存疑**:模型不确定,需人工复核。 + * 🟢 **通过**:逻辑自洽。 + +## **4\. 技术与性能要求 (Non-functional)** + +1. **响应速度**: + * 列表加载 \< 1秒。 + * 文件上传速度取决于带宽,需有进度反馈。 +2. **并发处理**: + * 支持至少 5 个任务并发运行(不需要排队太久)。 +3. **兼容性**: + * 优先支持 Chrome / Edge 浏览器。 +4. **数据安全**: + * 稿件文件存储需加密或隔离。 + * 审稿报告仅授权账号可见。 + +## **5\. 验收标准 (Acceptance Criteria)** + +1. **流程通**:用户能成功上传 5 个 PDF,勾选双模型运行,等待 3 分钟内,状态全部变为“已完成”。 +2. **报告准**: + * 上传一篇故意删掉“摘要结论”的稿件,规范性智能体必须报错。 + * 上传一篇故意混淆“t检验”和“卡方检验”的稿件,方法学智能体必须报“存疑”或“错误”。 +3. **无崩溃**:连续上传 20 个文件,系统不卡死,不白屏。 \ No newline at end of file diff --git a/docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW模块迁移计划.md b/docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW模块迁移计划.md new file mode 100644 index 00000000..933f45a9 --- /dev/null +++ b/docs/03-业务模块/RVW-稿件审查系统/04-开发计划/RVW模块迁移计划.md @@ -0,0 +1,829 @@ +# RVW稿件审查模块迁移计划(v2.1 - 稳定迁移版) + +> **文档版本:** v2.1 +> **创建日期:** 2026-01-07 +> **最后更新:** 2026-01-07 +> **维护者:** 开发团队 +> **文档目的:** 将稿件审查功能从旧架构安全迁移到新架构,同时整合MVP核心需求 + +--- + +## 📋 项目概述 + +### 1. 背景 + +稿件审查功能是2025-10-30(Day 30)独立开发的功能模块,目前位于 `backend/src/legacy/` 和 `frontend/` 目录中。现需要: + +1. **架构迁移**:迁移到标准的模块目录结构(`modules/rvw`) +2. **功能升级**:整合《智能期刊审稿系统MVP产品需求文档》的核心功能 + +### 2. 迁移原则 + +| 原则 | 说明 | +|------|------| +| **稳定优先** | 每个Phase完成后必须通过测试验证 | +| **安全可靠** | 数据库迁移前必须备份,支持回滚 | +| **阶段验证** | 每个Phase有明确的验收标准 | +| **向后兼容** | 保留旧API过渡期,不影响现有功能 | +| **渐进式** | 先核心后扩展,先后端后前端 | + +### 3. 功能范围 + +| 功能 | 本次开发 | 数据库支撑 | 说明 | +|------|:--------:|:----------:|------| +| **核心AI评估** | ✅ | ✅ | 稿约规范性+方法学 | +| **批量上传** | ✅ | ✅ | 多文件上传 | +| **审稿工作台** | ✅ | ✅ | 宽表布局+筛选 | +| **智能体选择** | ✅ | ✅ | 可选1个或2个 | +| **批量操作** | ✅ | ✅ | 批量运行审查 | +| **状态筛选** | ✅ | ✅ | 全部/待处理/已完成 | +| PDF报告导出 | ✅ | ✅ | 优化现有功能 | +| PICO卡片 | ⏸️ | ✅ | **暂不开发,数据库预留** | +| 系统设置 | ⏸️ | ✅ | **暂不开发,数据库预留** | +| 历史归档 | ⏸️ | ✅ | **暂不开发,数据库预留** | +| 登录页面 | ⏸️ | - | 暂不开发 | + +### 4. 智能体选择说明 + +用户可以灵活选择运行的智能体: + +``` +选项 A: 只选择「稿约规范性智能体」 → 只运行规范性评估 +选项 B: 只选择「方法学统计智能体」 → 只运行方法学评估 +选项 C: 同时选择两个智能体 → 同时运行两项评估(默认) +``` + +--- + +## 📊 数据库设计(完整版,支撑未来扩展) + +### 1. 期刊配置表(预留,暂不使用) + +```prisma +// review_schema + +model JournalConfig { + id String @id @default(uuid()) + name String // 期刊名称 + logoUrl String? @map("logo_url") // Logo URL(预留) + defaultModel String @default("deepseek-v3") @map("default_model") + settings Json? // 其他配置(预留) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + reviewTasks ReviewTask[] + + @@map("journal_configs") + @@schema("review_schema") +} +``` + +### 2. 审稿任务表(扩展版) + +```prisma +model ReviewTask { + id String @id @default(uuid()) + userId String @map("user_id") + journalId String? @map("journal_id") // 预留:期刊关联 + + // 文件信息 + fileName String @map("file_name") + fileSize Int @map("file_size") + filePath String? @map("file_path") + extractedText String @map("extracted_text") + wordCount Int? @map("word_count") + authorName String? @map("author_name") // 预留:作者提取 + + // 状态管理 + status String @default("pending") + // ✅ 智能体选择:可选1个或2个 + selectedAgents String[] @default(["editorial", "methodology"]) @map("selected_agents") + + // 评估结果 + editorialReview Json? @map("editorial_review") + methodologyReview Json? @map("methodology_review") + overallScore Float? @map("overall_score") + + // 结果摘要(用于列表展示) + editorialScore Float? @map("editorial_score") + methodologyStatus String? @map("methodology_status") // pass/warn/fail + + // 预留:PICO提取(暂不使用) + picoExtract Json? @map("pico_extract") + + // 元数据 + modelUsed String? @map("model_used") + startedAt DateTime? @map("started_at") + completedAt DateTime? @map("completed_at") + durationSeconds Int? @map("duration_seconds") + errorMessage String? @map("error_message") + + // 预留:归档功能(暂不使用) + isArchived Boolean @default(false) @map("is_archived") + archivedAt DateTime? @map("archived_at") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + // 关联 + user users @relation(fields: [userId], references: [id], onDelete: Cascade) + journal JournalConfig? @relation(fields: [journalId], references: [id]) + + @@index([userId]) + @@index([journalId]) + @@index([status]) + @@index([createdAt]) + @@index([isArchived]) + @@map("review_tasks") + @@schema("review_schema") +} +``` + +### 3. 字段说明 + +| 字段 | 本次使用 | 预留用途 | +|------|:--------:|---------| +| `journalId` | ❌ | 系统设置:期刊关联 | +| `authorName` | ❌ | 自动提取作者名 | +| `selectedAgents` | ✅ | 用户选择的智能体 | +| `editorialScore` | ✅ | 列表显示规范性分数 | +| `methodologyStatus` | ✅ | 列表显示方法学状态 | +| `picoExtract` | ❌ | PICO卡片数据 | +| `isArchived` | ❌ | 历史归档功能 | +| `archivedAt` | ❌ | 归档时间 | + +--- + +## 📋 安全迁移策略 + +### 1. 迁移前准备 + +```bash +# 1. 备份数据库 +pg_dump -h localhost -U postgres -d airesearch -F c -f backup_before_rvw_migration.dump + +# 2. 记录当前数据量 +SELECT COUNT(*) FROM public.review_tasks; + +# 3. 导出关键数据(可选) +COPY public.review_tasks TO '/tmp/review_tasks_backup.csv' WITH CSV HEADER; +``` + +### 2. 回滚方案 + +```bash +# 如果迁移失败,回滚数据库 +pg_restore -h localhost -U postgres -d airesearch -c backup_before_rvw_migration.dump + +# 如果只需要回滚Schema迁移 +ALTER TABLE review_schema.review_tasks SET SCHEMA public; +DROP SCHEMA review_schema; +``` + +### 3. 阶段性验证 + +每个Phase完成后必须通过以下验证: + +| Phase | 验证内容 | 验收标准 | +|-------|---------|---------| +| Phase 1 | 后端API测试 | 所有API返回正确,日志无ERROR | +| Phase 2 | 数据库迁移 | 数据完整,查询正常,索引有效 | +| Phase 3 | 前端功能测试 | 核心流程通顺,无白屏/报错 | +| Phase 4 | 集成测试 | 端到端流程正常 | +| Phase 5 | 验收测试 | 符合MVP验收标准 | + +--- + +## 📋 开发任务清单 + +### Phase 1:后端模块迁移(2天) + +#### Day 1 上午:创建模块结构 + 复用核心代码 + +**1.1 创建目录结构** + +``` +backend/src/modules/rvw/ +├── routes/ +│ └── index.ts # 路由定义 +├── controllers/ +│ └── reviewController.ts # 控制器 +├── services/ +│ ├── reviewService.ts # 主服务(复用+扩展) +│ ├── editorialService.ts # 稿约评估(复用) +│ └── methodologyService.ts # 方法学评估(复用) +├── types/ +│ └── index.ts # 类型定义 +├── prompts/ +│ ├── editorial_system.txt # 稿约Prompt +│ └── methodology_system.txt # 方法学Prompt +└── index.ts # 模块入口 +``` + +- [ ] **1.1.1** 创建目录结构 +- [ ] **1.1.2** 复制 `reviewEditorialStandards()` → `editorialService.ts` +- [ ] **1.1.3** 复制 `reviewMethodology()` → `methodologyService.ts` +- [ ] **1.1.4** 复制 `parseJSONFromLLMResponse()` → `utils.ts` +- [ ] **1.1.5** 复制 Prompt 文件到模块内 + +**1.2 云原生改造** + +- [ ] **1.2.1** 替换 `console.log` → `logger` +- [ ] **1.2.2** 移除 Mock用户ID,集成JWT认证 +- [ ] **1.2.3** 使用 `process.env` 配置 + +#### Day 1 下午:智能体选择逻辑 + +**1.3 智能体选择实现** + +```typescript +// types/index.ts +export type AgentType = 'editorial' | 'methodology'; + +export interface RunReviewParams { + taskId: string; + agents: AgentType[]; // 可选1个或2个 +} + +// services/reviewService.ts +async function runReview(params: RunReviewParams) { + const { taskId, agents } = params; + + // 验证:至少选择1个智能体 + if (agents.length === 0) { + throw new Error('请至少选择一个智能体'); + } + + // 更新任务状态 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'reviewing', + selectedAgents: agents, + startedAt: new Date() + } + }); + + // 只运行选中的智能体 + let editorialResult = null; + let methodologyResult = null; + + if (agents.includes('editorial')) { + editorialResult = await editorialService.review(taskId); + } + + if (agents.includes('methodology')) { + methodologyResult = await methodologyService.review(taskId); + } + + // 计算综合分数 + const overallScore = calculateOverallScore(editorialResult, methodologyResult, agents); + + // 更新结果 + await prisma.reviewTask.update({ + where: { id: taskId }, + data: { + status: 'completed', + editorialReview: editorialResult, + methodologyReview: methodologyResult, + editorialScore: editorialResult?.overall_score, + methodologyStatus: getMethodologyStatus(methodologyResult), + overallScore, + completedAt: new Date(), + durationSeconds: calculateDuration(taskId) + } + }); +} + +// 根据选择的智能体计算综合分数 +function calculateOverallScore(editorial: any, methodology: any, agents: AgentType[]) { + if (agents.length === 2 && editorial && methodology) { + // 两个都选:40% + 60% + return editorial.overall_score * 0.4 + methodology.overall_score * 0.6; + } else if (agents.includes('editorial') && editorial) { + // 只选规范性 + return editorial.overall_score; + } else if (agents.includes('methodology') && methodology) { + // 只选方法学 + return methodology.overall_score; + } + return null; +} +``` + +- [ ] **1.3.1** 实现智能体选择类型定义 +- [ ] **1.3.2** 实现 `runReview()` 函数 +- [ ] **1.3.3** 实现综合分数计算逻辑 +- [ ] **1.3.4** 实现方法学状态判断(pass/warn/fail) + +#### Day 2 上午:批量操作 + API扩展 + +**1.4 批量运行实现** + +```typescript +// services/reviewService.ts +async function batchRunReview(params: BatchRunParams) { + const { taskIds, agents } = params; + + // 限制并发数 + const MAX_CONCURRENT = 5; + const results = []; + + for (let i = 0; i < taskIds.length; i += MAX_CONCURRENT) { + const batch = taskIds.slice(i, i + MAX_CONCURRENT); + const batchResults = await Promise.allSettled( + batch.map(taskId => runReview({ taskId, agents })) + ); + results.push(...batchResults); + } + + return results; +} +``` + +- [ ] **1.4.1** 实现批量运行接口 +- [ ] **1.4.2** 实现并发控制(最多5个) +- [ ] **1.4.3** 实现错误处理(单个失败不影响其他) + +**1.5 API路由定义** + +```typescript +// routes/index.ts +export default async function rvwRoutes(fastify: FastifyInstance) { + // 任务管理 + fastify.post('/tasks', reviewController.createTask); // 创建/上传 + fastify.get('/tasks', reviewController.getTaskList); // 列表(筛选) + fastify.get('/tasks/:taskId', reviewController.getTaskDetail); // 详情 + fastify.get('/tasks/:taskId/report', reviewController.getTaskReport); // 报告 + fastify.delete('/tasks/:taskId', reviewController.deleteTask); // 删除 + + // 运行审查(核心功能) + fastify.post('/tasks/:taskId/run', reviewController.runReview); // 单个运行 + fastify.post('/tasks/batch/run', reviewController.batchRunReview); // 批量运行 +} +``` + +- [ ] **1.5.1** 实现路由定义 +- [ ] **1.5.2** 实现控制器方法 +- [ ] **1.5.3** 添加请求验证 + +#### Day 2 下午:注册路由 + Phase 1 验证 + +**1.6 注册新路由** + +```typescript +// backend/src/index.ts +import rvwRoutes from './modules/rvw/routes/index.js'; + +// 注册新路由(v2) +await fastify.register(rvwRoutes, { prefix: '/api/v2/rvw' }); +logger.info('✅ RVW稿件审查路由已注册: /api/v2/rvw'); + +// 保留旧路由(兼容) +await fastify.register(reviewRoutes, { prefix: '/api/v1' }); +logger.info('✅ Legacy审稿路由保留: /api/v1/review'); +``` + +- [ ] **1.6.1** 注册新路由 +- [ ] **1.6.2** 保留旧路由兼容 + +**1.7 Phase 1 验证测试** + +```http +### 1. 创建任务 +POST {{baseUrl}}/api/v2/rvw/tasks +Content-Type: multipart/form-data + +# file: test.docx + +### 2. 运行审查(只选规范性) +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ "agents": ["editorial"] } + +### 3. 运行审查(只选方法学) +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ "agents": ["methodology"] } + +### 4. 运行审查(两个都选) +POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run +Content-Type: application/json + +{ "agents": ["editorial", "methodology"] } + +### 5. 批量运行 +POST {{baseUrl}}/api/v2/rvw/tasks/batch/run +Content-Type: application/json + +{ + "taskIds": ["id1", "id2", "id3"], + "agents": ["editorial", "methodology"] +} + +### 6. 获取任务列表 +GET {{baseUrl}}/api/v2/rvw/tasks?status=pending&limit=10 + +### 7. 获取报告 +GET {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/report +``` + +**Phase 1 验收标准:** + +| 测试项 | 预期结果 | 通过 | +|--------|---------|:----:| +| 创建任务 | 返回taskId,状态pending | ⬜ | +| 只选规范性 | 只有editorialReview有值 | ⬜ | +| 只选方法学 | 只有methodologyReview有值 | ⬜ | +| 两个都选 | 两个Review都有值 | ⬜ | +| 批量运行 | 多个任务都完成 | ⬜ | +| 旧API兼容 | `/api/v1/review/*` 正常 | ⬜ | + +- [ ] **1.7.1** 编写测试用例 +- [ ] **1.7.2** 执行测试 +- [ ] **1.7.3** 修复问题 +- [ ] **1.7.4** 确认Phase 1通过 + +--- + +### Phase 2:数据库Schema迁移(0.5天) + +#### 迁移前:备份 + +- [ ] **2.1** 备份数据库 + ```bash + pg_dump -h localhost -U postgres -d airesearch -F c -f backup_phase2.dump + ``` + +- [ ] **2.2** 记录当前数据 + ```sql + SELECT COUNT(*) FROM public.review_tasks; + ``` + +#### 迁移执行 + +- [ ] **2.3** 创建迁移SQL + ```sql + -- 1. 创建新Schema + CREATE SCHEMA IF NOT EXISTS review_schema; + + -- 2. 创建期刊配置表(预留) + CREATE TABLE review_schema.journal_configs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + logo_url TEXT, + default_model VARCHAR(50) DEFAULT 'deepseek-v3', + settings JSONB, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() + ); + + -- 3. 迁移review_tasks表到新Schema + ALTER TABLE public.review_tasks SET SCHEMA review_schema; + + -- 4. 添加新字段(支持未来扩展) + ALTER TABLE review_schema.review_tasks + ADD COLUMN IF NOT EXISTS journal_id UUID REFERENCES review_schema.journal_configs(id), + ADD COLUMN IF NOT EXISTS author_name VARCHAR(255), + ADD COLUMN IF NOT EXISTS selected_agents TEXT[] DEFAULT ARRAY['editorial', 'methodology'], + ADD COLUMN IF NOT EXISTS editorial_score FLOAT, + ADD COLUMN IF NOT EXISTS methodology_status VARCHAR(20), + ADD COLUMN IF NOT EXISTS pico_extract JSONB, + ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT FALSE, + ADD COLUMN IF NOT EXISTS archived_at TIMESTAMPTZ; + + -- 5. 创建索引 + CREATE INDEX IF NOT EXISTS idx_review_tasks_journal ON review_schema.review_tasks(journal_id); + CREATE INDEX IF NOT EXISTS idx_review_tasks_archived ON review_schema.review_tasks(is_archived); + + -- 6. 创建默认期刊配置 + INSERT INTO review_schema.journal_configs (name, default_model) + VALUES ('默认期刊', 'deepseek-v3'); + ``` + +- [ ] **2.4** 更新Prisma Schema + - 修改 `@@schema("review_schema")` + - 添加新字段 + +- [ ] **2.5** 执行迁移 + ```bash + npx prisma migrate dev --name rvw_schema_migration + ``` + +#### Phase 2 验证 + +- [ ] **2.6** 验证数据完整性 + ```sql + -- 确认数据量一致 + SELECT COUNT(*) FROM review_schema.review_tasks; + + -- 确认字段可用 + SELECT id, selected_agents, editorial_score FROM review_schema.review_tasks LIMIT 5; + + -- 确认外键关系 + SELECT rt.id, rt.user_id, u.email + FROM review_schema.review_tasks rt + JOIN public.users u ON rt.user_id = u.id + LIMIT 5; + ``` + +**Phase 2 验收标准:** + +| 测试项 | 预期结果 | 通过 | +|--------|---------|:----:| +| 数据迁移 | 数据量一致 | ⬜ | +| 新字段 | 字段存在且可用 | ⬜ | +| 外键关系 | 关联正常 | ⬜ | +| 索引有效 | 查询性能正常 | ⬜ | +| API正常 | 后端API仍可用 | ⬜ | + +- [ ] **2.7** 确认Phase 2通过 + +--- + +### Phase 3:前端重构(3天) + +#### Day 3:模块结构 + API层 + +**3.1 创建模块结构** + +``` +frontend-v2/src/modules/rvw/ +├── api/ +│ └── reviewApi.ts # API封装 +├── components/ +│ ├── ScoreCard.tsx # 复用 +│ ├── EditorialReview.tsx # 复用 +│ ├── MethodologyReview.tsx # 复用 +│ ├── AgentSelector.tsx # 🆕 智能体选择弹窗 +│ ├── BatchToolbar.tsx # 🆕 批量操作栏 +│ ├── TaskTable.tsx # 🆕 任务列表表格 +│ └── StatusFilter.tsx # 🆕 状态筛选 +├── pages/ +│ ├── ReviewDashboard.tsx # 🆕 审稿工作台 +│ └── ReviewDetail.tsx # 报告详情(优化) +├── hooks/ +│ ├── useReviewTask.ts +│ └── useBatchOperation.ts +├── stores/ +│ └── useReviewStore.ts +├── types/ +│ └── index.ts +└── index.tsx +``` + +- [ ] **3.1.1** 创建目录结构 +- [ ] **3.1.2** 复用现有组件(ScoreCard、EditorialReview、MethodologyReview) + +**3.2 API封装** + +```typescript +// api/reviewApi.ts +export type AgentType = 'editorial' | 'methodology'; + +// 运行审查(支持选择智能体) +export async function runReview(taskId: string, agents: AgentType[]): Promise { + return axios.post(`/api/v2/rvw/tasks/${taskId}/run`, { agents }); +} + +// 批量运行 +export async function batchRunReview(taskIds: string[], agents: AgentType[]): Promise { + return axios.post('/api/v2/rvw/tasks/batch/run', { taskIds, agents }); +} + +// 获取任务列表(带筛选) +export interface TaskListParams { + status?: 'all' | 'pending' | 'completed'; + limit?: number; + offset?: number; +} + +export async function getTaskList(params: TaskListParams): Promise { + return axios.get('/api/v2/rvw/tasks', { params }); +} +``` + +- [ ] **3.2.1** 实现API封装 +- [ ] **3.2.2** 实现类型定义 + +#### Day 4:核心页面开发 + +**3.3 智能体选择弹窗** + +```tsx +// components/AgentSelector.tsx +interface AgentSelectorProps { + visible: boolean; + taskIds: string[]; // 支持单个或多个 + onConfirm: (agents: AgentType[]) => void; + onCancel: () => void; +} + +// 弹窗内容: +// ✅ 稿约规范性智能体(默认选中) +// 格式、参考文献、图片检查 +// ☐ 方法学统计智能体 +// DeepSeek 深度逻辑推理 +// +// 提示:可选择1个或同时选择2个智能体 +// +// [取消] [立即运行] +``` + +- [ ] **3.3.1** 实现智能体选择弹窗 +- [ ] **3.3.2** 支持单选和多选 +- [ ] **3.3.3** 默认选中规范性智能体 + +**3.4 审稿工作台** + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Header: [Logo] 智能审稿系统 [上传新稿件] │ +├─────────────────────────────────────────────────────────────────┤ +│ Filter: [全部|待处理|已完成] │ +├─────────────────────────────────────────────────────────────────┤ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ ☐ 文件名称/信息 上传时间 审稿维度 结果摘要 操作 │ │ +│ │ ☐ 替雷利珠单抗...pdf 10:30 [规范][方法] 92分 [查看] │ +│ │ ☐ 高血压药物...docx 刚刚 [未运行] 等待... [开始] │ +│ └───────────────────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ [Batch Toolbar: 3个文件已选中 | 运行智能审稿 | ✕] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +- [ ] **3.4.1** 实现页面布局 +- [ ] **3.4.2** 实现任务列表表格 +- [ ] **3.4.3** 实现状态筛选 +- [ ] **3.4.4** 实现批量操作栏 + +#### Day 5:报告详情 + Phase 3 验证 + +**3.5 报告详情页** + +- [ ] **3.5.1** 实现Tab切换(规范性/方法学) +- [ ] **3.5.2** 复用评估详情组件 +- [ ] **3.5.3** 根据选择的智能体显示对应Tab +- [ ] **3.5.4** 实现导出报告按钮 + +**3.6 路由配置** + +```typescript +// router/index.tsx +{ + path: '/rvw', + children: [ + { path: '', element: }, + { path: ':taskId', element: } + ] +} +``` + +- [ ] **3.6.1** 配置路由 +- [ ] **3.6.2** 添加导航菜单 + +**Phase 3 验证测试** + +| 测试项 | 预期结果 | 通过 | +|--------|---------|:----:| +| 页面加载 | 工作台正常显示 | ⬜ | +| 文件上传 | 支持多文件 | ⬜ | +| 智能体选择 | 可选1个或2个 | ⬜ | +| 批量操作 | 批量运行成功 | ⬜ | +| 状态筛选 | 筛选结果正确 | ⬜ | +| 报告查看 | 显示对应评估结果 | ⬜ | + +- [ ] **3.7** 确认Phase 3通过 + +--- + +### Phase 4:集成测试(1天) + +**4.1 端到端测试** + +| 测试场景 | 步骤 | 预期结果 | +|---------|------|---------| +| 单文件+单智能体 | 上传→选规范性→查看 | 只显示规范性报告 | +| 单文件+双智能体 | 上传→选两个→查看 | 显示两个报告Tab | +| 批量+双智能体 | 上传3个→批量运行 | 3个都完成 | +| 状态筛选 | 上传→筛选待处理 | 正确过滤 | + +- [ ] **4.1.1** 执行端到端测试 +- [ ] **4.1.2** 修复发现的问题 + +**4.2 性能测试** + +| 指标 | 目标 | 实际 | +|------|------|------| +| 列表加载 | < 1秒 | ⬜ | +| 单文件审查 | < 2分钟 | ⬜ | +| 5文件并发 | < 5分钟 | ⬜ | + +- [ ] **4.2.1** 执行性能测试 + +**4.3 兼容性测试** + +- [ ] **4.3.1** Chrome浏览器测试 +- [ ] **4.3.2** Edge浏览器测试 +- [ ] **4.3.3** 旧API兼容性确认 + +--- + +### Phase 5:验收与上线(0.5天) + +**5.1 MVP验收标准** + +| 验收项 | 预期结果 | 通过 | +|--------|---------|:----:| +| 流程通 | 5个PDF,3分钟内全部完成 | ⬜ | +| 规范性准确 | 删掉摘要结论必须报错 | ⬜ | +| 方法学准确 | 混淆统计方法必须报存疑 | ⬜ | +| 无崩溃 | 连续上传20个不卡死 | ⬜ | + +- [ ] **5.1.1** 执行验收测试 +- [ ] **5.1.2** 编写验收报告 + +**5.2 上线准备** + +- [ ] **5.2.1** 更新文档 +- [ ] **5.2.2** 通知相关人员 +- [ ] **5.2.3** 监控上线后状态 + +--- + +## 📅 时间估算 + +| Phase | 任务 | 预估工时 | 验证点 | +|-------|------|---------|--------| +| **Phase 1** | 后端模块迁移 | 2天 | ✓ API测试通过 | +| **Phase 2** | 数据库迁移 | 0.5天 | ✓ 数据完整性验证 | +| **Phase 3** | 前端重构 | 3天 | ✓ 功能测试通过 | +| **Phase 4** | 集成测试 | 1天 | ✓ 端到端测试通过 | +| **Phase 5** | 验收上线 | 0.5天 | ✓ MVP验收通过 | +| **总计** | - | **7天** | - | + +--- + +## ⏸️ 暂不开发的功能(数据库已预留) + +| 功能 | 预留字段 | 后续计划 | +|------|---------|---------| +| **PICO卡片** | `pico_extract` | 方法学评估扩展 | +| **系统设置** | `JournalConfig`表 | 期刊Logo/模型配置 | +| **历史归档** | `is_archived`, `archived_at` | 自动归档7天前数据 | +| **登录页面** | - | 独立产品时开发 | + +--- + +## ⚠️ 风险控制 + +### 1. 数据库迁移风险 + +| 风险 | 概率 | 影响 | 控制措施 | +|------|------|------|---------| +| 数据丢失 | 低 | 高 | 迁移前备份 | +| 迁移失败 | 中 | 中 | 准备回滚SQL | +| 性能下降 | 低 | 中 | 验证索引有效性 | + +### 2. 功能回归风险 + +| 风险 | 概率 | 影响 | 控制措施 | +|------|------|------|---------| +| 旧API中断 | 低 | 高 | 保留v1路由 | +| 评估结果异常 | 低 | 高 | 对比测试结果 | +| 前端白屏 | 中 | 中 | 阶段性测试 | + +### 3. 回滚计划 + +```bash +# 如果需要回滚到迁移前状态 + +# 1. 停止服务 +pm2 stop all + +# 2. 回滚数据库 +pg_restore -h localhost -U postgres -d airesearch -c backup_phase2.dump + +# 3. 切换到旧代码分支 +git checkout main + +# 4. 重启服务 +pm2 start all +``` + +--- + +## 📚 参考文档 + +- [智能期刊审稿系统MVP PRD](../01-需求分析/智能期刊审稿系统%20MVP%20产品需求文档.md) +- [智能审稿V7原型](../01-需求分析/智能审稿V7.html) +- [云原生开发规范](../../04-开发规范/08-云原生开发规范.md) +- [现有系统技术摸底报告](../../00-项目概述/现有系统技术摸底报告.md) + +--- + +**文档版本:** v2.1 +**最后更新:** 2026-01-07 +**下一步:** 确认后开始Phase 1 diff --git a/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md b/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md index c38dba51..0fd4d254 100644 --- a/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md +++ b/docs/03-业务模块/Redcap/01-部署与配置/10-REDCap_Docker部署操作手册.md @@ -766,3 +766,4 @@ 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 ffcf0846..58eaa722 100644 --- a/docs/03-业务模块/Redcap/README.md +++ b/docs/03-业务模块/Redcap/README.md @@ -148,3 +148,4 @@ AIclinicalresearch/redcap-docker-dev/ + diff --git a/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md b/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md index 591f7548..c91bf6e2 100644 --- a/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md +++ b/docs/05-部署文档/02-SAE部署完全指南(产品经理版).md @@ -883,5 +883,6 @@ ACR镜像仓库: + diff --git a/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md b/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md index 73834b0e..d92aaa9a 100644 --- a/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md +++ b/docs/05-部署文档/07-前端Nginx-SAE部署操作手册.md @@ -1370,5 +1370,6 @@ SAE应用配置: + diff --git a/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md b/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md index 7402acd3..2897ec8d 100644 --- a/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md +++ b/docs/05-部署文档/08-PostgreSQL数据库部署操作手册.md @@ -1186,5 +1186,6 @@ 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 75f87651..b1f1713c 100644 --- a/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md +++ b/docs/05-部署文档/10-Node.js后端-Docker镜像构建手册.md @@ -597,5 +597,6 @@ scripts/*.ts + diff --git a/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md b/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md index 8b62aa1a..460314fd 100644 --- a/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md +++ b/docs/05-部署文档/11-Node.js后端-SAE部署配置清单.md @@ -286,4 +286,5 @@ Node.js后端部署成功后: + diff --git a/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md b/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md index 4f81c443..aaafe37a 100644 --- a/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md +++ b/docs/05-部署文档/12-Node.js后端-SAE部署操作手册.md @@ -509,4 +509,5 @@ 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 606503b1..56b52eb1 100644 --- a/docs/05-部署文档/13-Node.js后端-镜像修复记录.md +++ b/docs/05-部署文档/13-Node.js后端-镜像修复记录.md @@ -224,4 +224,5 @@ 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 bd261f9b..752ea463 100644 --- a/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md +++ b/docs/05-部署文档/14-Node.js后端-pino-pretty问题修复.md @@ -262,4 +262,5 @@ npm run dev + diff --git a/docs/05-部署文档/16-前端Nginx-部署成功总结.md b/docs/05-部署文档/16-前端Nginx-部署成功总结.md index 60e1672d..76157e2b 100644 --- a/docs/05-部署文档/16-前端Nginx-部署成功总结.md +++ b/docs/05-部署文档/16-前端Nginx-部署成功总结.md @@ -486,4 +486,5 @@ pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432 + diff --git a/docs/05-部署文档/17-完整部署实战手册-2025版.md b/docs/05-部署文档/17-完整部署实战手册-2025版.md index ef8268ac..b6f94697 100644 --- a/docs/05-部署文档/17-完整部署实战手册-2025版.md +++ b/docs/05-部署文档/17-完整部署实战手册-2025版.md @@ -1814,4 +1814,5 @@ curl http://8.140.53.236/ + diff --git a/docs/05-部署文档/18-部署文档使用指南.md b/docs/05-部署文档/18-部署文档使用指南.md index e24852cf..f7527638 100644 --- a/docs/05-部署文档/18-部署文档使用指南.md +++ b/docs/05-部署文档/18-部署文档使用指南.md @@ -362,4 +362,5 @@ 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 16a9bd79..a7ff4fe3 100644 --- a/docs/05-部署文档/19-日常更新快速操作手册.md +++ b/docs/05-部署文档/19-日常更新快速操作手册.md @@ -684,4 +684,5 @@ docker login --username=gofeng117@163.com \ + diff --git a/docs/05-部署文档/文档修正报告-20251214.md b/docs/05-部署文档/文档修正报告-20251214.md index 6d557178..5c5af337 100644 --- a/docs/05-部署文档/文档修正报告-20251214.md +++ b/docs/05-部署文档/文档修正报告-20251214.md @@ -494,5 +494,6 @@ NAT网关成本¥100/月,对初创团队是一笔开销 + diff --git a/docs/07-运维文档/03-SAE环境变量配置指南.md b/docs/07-运维文档/03-SAE环境变量配置指南.md index 56f0dde0..1ef1fd1c 100644 --- a/docs/07-运维文档/03-SAE环境变量配置指南.md +++ b/docs/07-运维文档/03-SAE环境变量配置指南.md @@ -399,5 +399,6 @@ curl http://你的SAE地址:3001/health + diff --git a/docs/07-运维文档/05-Redis缓存与队列的区别说明.md b/docs/07-运维文档/05-Redis缓存与队列的区别说明.md index 134e632c..2fd441d3 100644 --- a/docs/07-运维文档/05-Redis缓存与队列的区别说明.md +++ b/docs/07-运维文档/05-Redis缓存与队列的区别说明.md @@ -731,5 +731,6 @@ const job = await queue.getJob(jobId); + diff --git a/docs/07-运维文档/06-长时间任务可靠性分析.md b/docs/07-运维文档/06-长时间任务可靠性分析.md index 45519e0e..99234a70 100644 --- a/docs/07-运维文档/06-长时间任务可靠性分析.md +++ b/docs/07-运维文档/06-长时间任务可靠性分析.md @@ -498,5 +498,6 @@ processLiteraturesInBackground(task.id, projectId, testLiteratures); + diff --git a/docs/07-运维文档/07-Redis使用需求分析(按模块).md b/docs/07-运维文档/07-Redis使用需求分析(按模块).md index 36cb7f1b..55221aa0 100644 --- a/docs/07-运维文档/07-Redis使用需求分析(按模块).md +++ b/docs/07-运维文档/07-Redis使用需求分析(按模块).md @@ -975,5 +975,6 @@ 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 ec92ede6..113c2930 100644 --- a/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md +++ b/docs/08-项目管理/03-每周计划/2025-12-13-Postgres-Only架构改造完成.md @@ -1032,5 +1032,6 @@ Redis 实例:¥500/月 + diff --git a/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md b/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md index f0d90bec..8d6db283 100644 --- a/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md +++ b/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md @@ -490,5 +490,6 @@ import { ChatContainer } from '@/shared/components/Chat'; + diff --git a/docs/08-项目管理/PKB前端问题修复报告.md b/docs/08-项目管理/PKB前端问题修复报告.md index 00a5481e..f77fc4d6 100644 --- a/docs/08-项目管理/PKB前端问题修复报告.md +++ b/docs/08-项目管理/PKB前端问题修复报告.md @@ -410,3 +410,4 @@ frontend-v2/src/modules/pkb/ + diff --git a/docs/08-项目管理/PKB前端验证指南.md b/docs/08-项目管理/PKB前端验证指南.md index 6b9d86e1..9be00afb 100644 --- a/docs/08-项目管理/PKB前端验证指南.md +++ b/docs/08-项目管理/PKB前端验证指南.md @@ -272,3 +272,4 @@ npm run dev + diff --git a/docs/08-项目管理/PKB功能审查报告-阶段0.md b/docs/08-项目管理/PKB功能审查报告-阶段0.md index 531a4479..d31817b8 100644 --- a/docs/08-项目管理/PKB功能审查报告-阶段0.md +++ b/docs/08-项目管理/PKB功能审查报告-阶段0.md @@ -787,3 +787,4 @@ AIA智能问答模块 + diff --git a/docs/08-项目管理/PKB和RVW功能迁移计划.md b/docs/08-项目管理/PKB和RVW功能迁移计划.md index 6019d628..aba843e0 100644 --- a/docs/08-项目管理/PKB和RVW功能迁移计划.md +++ b/docs/08-项目管理/PKB和RVW功能迁移计划.md @@ -932,3 +932,4 @@ 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 31736a9b..144c6a1a 100644 --- a/docs/08-项目管理/PKB精细化优化报告.md +++ b/docs/08-项目管理/PKB精细化优化报告.md @@ -585,3 +585,4 @@ const typography = { + diff --git a/docs/08-项目管理/PKB迁移-超级安全执行计划.md b/docs/08-项目管理/PKB迁移-超级安全执行计划.md index 380ba2a5..0c8758bc 100644 --- a/docs/08-项目管理/PKB迁移-超级安全执行计划.md +++ b/docs/08-项目管理/PKB迁移-超级安全执行计划.md @@ -897,3 +897,4 @@ app.use('/api/v1/knowledge', (req, res) => { + diff --git a/docs/08-项目管理/PKB迁移-阶段1完成报告.md b/docs/08-项目管理/PKB迁移-阶段1完成报告.md index 777f8c3f..e0940edd 100644 --- a/docs/08-项目管理/PKB迁移-阶段1完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段1完成报告.md @@ -211,3 +211,4 @@ rm -rf src/modules/pkb + diff --git a/docs/08-项目管理/PKB迁移-阶段2完成报告.md b/docs/08-项目管理/PKB迁移-阶段2完成报告.md index cf7bddcb..b714f46c 100644 --- a/docs/08-项目管理/PKB迁移-阶段2完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段2完成报告.md @@ -386,3 +386,4 @@ GET /api/v2/pkb/batch-tasks/batch/templates + diff --git a/docs/08-项目管理/PKB迁移-阶段2进行中.md b/docs/08-项目管理/PKB迁移-阶段2进行中.md index 47f23e3d..15040c71 100644 --- a/docs/08-项目管理/PKB迁移-阶段2进行中.md +++ b/docs/08-项目管理/PKB迁移-阶段2进行中.md @@ -30,3 +30,4 @@ import pkbRoutes from './modules/pkb/routes/index.js'; + diff --git a/docs/08-项目管理/PKB迁移-阶段3完成报告.md b/docs/08-项目管理/PKB迁移-阶段3完成报告.md index dc143633..11e11c13 100644 --- a/docs/08-项目管理/PKB迁移-阶段3完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段3完成报告.md @@ -299,3 +299,4 @@ backend/ + diff --git a/docs/08-项目管理/PKB迁移-阶段4完成报告.md b/docs/08-项目管理/PKB迁移-阶段4完成报告.md index 9552d177..df5f447d 100644 --- a/docs/08-项目管理/PKB迁移-阶段4完成报告.md +++ b/docs/08-项目管理/PKB迁移-阶段4完成报告.md @@ -510,3 +510,4 @@ const response = await fetch('/api/v2/pkb/batch-tasks/batch/execute', { + diff --git a/extraction_service/.dockerignore b/extraction_service/.dockerignore index 6d5d34fb..3b57002a 100644 --- a/extraction_service/.dockerignore +++ b/extraction_service/.dockerignore @@ -65,5 +65,6 @@ models/ + diff --git a/extraction_service/operations/__init__.py b/extraction_service/operations/__init__.py index c7930f04..e24214e8 100644 --- a/extraction_service/operations/__init__.py +++ b/extraction_service/operations/__init__.py @@ -53,5 +53,6 @@ __version__ = '1.0.0' + diff --git a/extraction_service/operations/dropna.py b/extraction_service/operations/dropna.py index 5ca44696..404c9293 100644 --- a/extraction_service/operations/dropna.py +++ b/extraction_service/operations/dropna.py @@ -186,5 +186,6 @@ def get_missing_summary(df: pd.DataFrame) -> dict: + diff --git a/extraction_service/operations/filter.py b/extraction_service/operations/filter.py index 153c73f0..4051cf46 100644 --- a/extraction_service/operations/filter.py +++ b/extraction_service/operations/filter.py @@ -146,5 +146,6 @@ def apply_filter( + diff --git a/extraction_service/operations/unpivot.py b/extraction_service/operations/unpivot.py index 9b2a3e7a..e91a0380 100644 --- a/extraction_service/operations/unpivot.py +++ b/extraction_service/operations/unpivot.py @@ -310,5 +310,6 @@ def get_unpivot_preview( + diff --git a/extraction_service/test_dc_api.py b/extraction_service/test_dc_api.py index c8f5e1e6..9dec4317 100644 --- a/extraction_service/test_dc_api.py +++ b/extraction_service/test_dc_api.py @@ -320,5 +320,6 @@ if __name__ == "__main__": + diff --git a/extraction_service/test_execute_simple.py b/extraction_service/test_execute_simple.py index bc52dd40..c6e72d7b 100644 --- a/extraction_service/test_execute_simple.py +++ b/extraction_service/test_execute_simple.py @@ -86,5 +86,6 @@ except Exception as e: + diff --git a/extraction_service/test_module.py b/extraction_service/test_module.py index 96d180a9..8e4c2e4e 100644 --- a/extraction_service/test_module.py +++ b/extraction_service/test_module.py @@ -66,5 +66,6 @@ except Exception as e: + diff --git a/frontend-v2/.dockerignore b/frontend-v2/.dockerignore index 69905592..2ce42d8f 100644 --- a/frontend-v2/.dockerignore +++ b/frontend-v2/.dockerignore @@ -85,5 +85,6 @@ vite.config.*.timestamp-* + diff --git a/frontend-v2/docker-entrypoint.sh b/frontend-v2/docker-entrypoint.sh index f9a6f6ad..453c84d3 100644 --- a/frontend-v2/docker-entrypoint.sh +++ b/frontend-v2/docker-entrypoint.sh @@ -52,5 +52,6 @@ exec nginx -g 'daemon off;' + diff --git a/frontend-v2/nginx.conf b/frontend-v2/nginx.conf index 2346abd1..73cb9f3e 100644 --- a/frontend-v2/nginx.conf +++ b/frontend-v2/nginx.conf @@ -208,5 +208,6 @@ http { + diff --git a/frontend-v2/src/framework/layout/TopNavigation.tsx b/frontend-v2/src/framework/layout/TopNavigation.tsx index 2032d2f8..52f8992e 100644 --- a/frontend-v2/src/framework/layout/TopNavigation.tsx +++ b/frontend-v2/src/framework/layout/TopNavigation.tsx @@ -137,4 +137,3 @@ const TopNavigation = () => { } export default TopNavigation - diff --git a/frontend-v2/src/framework/modules/moduleRegistry.ts b/frontend-v2/src/framework/modules/moduleRegistry.ts index e63093f9..688a365f 100644 --- a/frontend-v2/src/framework/modules/moduleRegistry.ts +++ b/frontend-v2/src/framework/modules/moduleRegistry.ts @@ -6,7 +6,8 @@ import { FolderOpenOutlined, ClearOutlined, BarChartOutlined, - LineChartOutlined + LineChartOutlined, + AuditOutlined } from '@ant-design/icons' /** @@ -78,6 +79,16 @@ export const MODULES: ModuleDefinition[] = [ description: '统计分析工具集(Java团队开发)', isExternal: true, // 外部模块 }, + { + id: 'review-system', + name: '预审稿', + path: '/rvw', + icon: AuditOutlined, + component: lazy(() => import('@/modules/rvw')), + placeholder: false, // RVW模块已开发 + requiredVersion: 'basic', + description: '智能期刊审稿系统(稿约评审+方法学评审)', + }, ] /** diff --git a/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx b/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx index 41bc3cc7..f7ff5d42 100644 --- a/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx +++ b/frontend-v2/src/modules/asl/components/FulltextDetailDrawer.tsx @@ -555,5 +555,6 @@ export default FulltextDetailDrawer; + diff --git a/frontend-v2/src/modules/dc/hooks/useAssets.ts b/frontend-v2/src/modules/dc/hooks/useAssets.ts index c35b796a..4499abf3 100644 --- a/frontend-v2/src/modules/dc/hooks/useAssets.ts +++ b/frontend-v2/src/modules/dc/hooks/useAssets.ts @@ -148,5 +148,6 @@ 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 f25cfc68..415d2fa5 100644 --- a/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts +++ b/frontend-v2/src/modules/dc/hooks/useRecentTasks.ts @@ -138,5 +138,6 @@ 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 a63cd36b..2306d4bf 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,5 +337,6 @@ 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 10e0539e..7a344de4 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,5 +422,6 @@ 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 62af1bdf..6af5c068 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,5 +308,6 @@ 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 b5b253c7..45fc7f54 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,5 +108,6 @@ 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 83d066e3..a049d6d0 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,5 +100,6 @@ export interface DataStats { + diff --git a/frontend-v2/src/modules/dc/types/portal.ts b/frontend-v2/src/modules/dc/types/portal.ts index 0b1e3544..620d6b9e 100644 --- a/frontend-v2/src/modules/dc/types/portal.ts +++ b/frontend-v2/src/modules/dc/types/portal.ts @@ -96,5 +96,6 @@ 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 365a73c4..0c31b48f 100644 --- a/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts +++ b/frontend-v2/src/modules/pkb/api/knowledgeBaseApi.ts @@ -217,3 +217,4 @@ export const documentSelectionApi = { + diff --git a/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx b/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx index cf61f2fa..ab071d54 100644 --- a/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx +++ b/frontend-v2/src/modules/pkb/pages/KnowledgePage.tsx @@ -285,3 +285,4 @@ export default KnowledgePage; + diff --git a/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts b/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts index e7245ffd..d149ac84 100644 --- a/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts +++ b/frontend-v2/src/modules/pkb/stores/useKnowledgeBaseStore.ts @@ -223,3 +223,4 @@ 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 314429af..040aa405 100644 --- a/frontend-v2/src/modules/pkb/types/workspace.ts +++ b/frontend-v2/src/modules/pkb/types/workspace.ts @@ -40,3 +40,4 @@ export interface BatchTemplate { + diff --git a/frontend-v2/src/modules/rvw/index.tsx b/frontend-v2/src/modules/rvw/index.tsx new file mode 100644 index 00000000..fe3ad4fd --- /dev/null +++ b/frontend-v2/src/modules/rvw/index.tsx @@ -0,0 +1,502 @@ +/** + * RVW - 预审稿模块入口 + * + * @description 智能期刊审稿系统 + * - 稿约评审:评估稿件是否符合期刊投稿要求 + * - 方法学评审:评估临床研究的方法学质量 + * + * @version Phase 3 - 前端重构 + */ + +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}
  • + ))} +
+
+
+
+ )} +
+
+ ) +} + +// 模块主入口 +const RVWModule: React.FC = () => { + return ( + + } /> + } /> + + ) +} + +export default RVWModule + diff --git a/frontend-v2/src/shared/components/index.ts b/frontend-v2/src/shared/components/index.ts index a4763ab0..f605b9bb 100644 --- a/frontend-v2/src/shared/components/index.ts +++ b/frontend-v2/src/shared/components/index.ts @@ -51,5 +51,6 @@ 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 baf820b4..2e41cc87 100644 --- a/frontend-v2/src/vite-env.d.ts +++ b/frontend-v2/src/vite-env.d.ts @@ -31,5 +31,6 @@ interface ImportMeta { + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b81d9de2..2aa5e726 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "html2canvas": "^1.4.1", "js-yaml": "^4.1.0", "jspdf": "^3.0.3", + "lucide-react": "^0.562.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^10.1.0", @@ -4483,6 +4484,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 70fb4c3d..a0d0b1da 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "html2canvas": "^1.4.1", "js-yaml": "^4.1.0", "jspdf": "^3.0.3", + "lucide-react": "^0.562.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^10.1.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1e194ff7..73b663da 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,10 +6,15 @@ import ChatPage from './pages/ChatPage' import KnowledgePage from './pages/KnowledgePage' import HistoryPage from './pages/HistoryPage' import ReviewPage from './pages/ReviewPage' +import { RvwDashboard } from './pages/rvw' function App() { return ( + {/* RVW模块 - 独立布局 */} + } /> + + {/* 主系统 */} }> } /> } /> diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 301b28e9..c231b0e4 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -13,6 +13,7 @@ import { UserOutlined, SettingOutlined, LogoutOutlined, + FileSearchOutlined, } from '@ant-design/icons' import type { MenuProps } from 'antd' import { ProjectSelector } from '../components/ProjectSelector' @@ -174,16 +175,32 @@ const MainLayout = () => { borderBottom: '1px solid #f0f0f0', }} > - +
diff --git a/frontend/src/pages/rvw/Dashboard.tsx b/frontend/src/pages/rvw/Dashboard.tsx new file mode 100644 index 00000000..8837c485 --- /dev/null +++ b/frontend/src/pages/rvw/Dashboard.tsx @@ -0,0 +1,236 @@ +/** + * RVW审稿系统 - 主Dashboard页面 + */ +import { useState, useEffect, useCallback } from 'react'; +import { message } from 'antd'; +import { + Sidebar, + Header, + FilterChips, + TaskTable, + BatchToolbar, + AgentModal, + ReportDetail, +} from './components'; +import * as api from './api'; +import type { ReviewTask, ReviewReport, TaskFilters, AgentType } from './types'; +import './styles.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 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[]) => { + try { + if (pendingTaskForRun) { + // 单个任务 + message.loading({ content: '正在启动审查...', key: 'run' }); + await api.runTask(pendingTaskForRun.id, agents); + message.success({ content: '审查已启动', key: 'run', duration: 2 }); + } 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) => { + try { + message.loading({ content: '加载报告中...', key: 'report' }); + const report = await api.getTaskReport(task.id); + setReportDetail(report); + message.destroy('report'); + } catch (error: any) { + message.error({ content: '加载报告失败', key: 'report', duration: 3 }); + } + }; + + const handleBackToList = () => { + setReportDetail(null); + }; + + // ==================== 渲染 ==================== + + // 报告详情视图 + if (reportDetail) { + return ( +
+ + +
+ ); + } + + // 主仪表盘视图 + return ( +
+ + +
+
+ {/* 顶部操作区 */} +
+
+ +
+ + {/* 列表区域 */} +
+ {loading ? ( +
+
+
+ ) : ( + + )} +
+
+
+ + {/* 批量操作工具栏 */} + setSelectedIds([])} + /> + + {/* 智能体选择弹窗 */} + { + setAgentModalVisible(false); + setPendingTaskForRun(null); + }} + onConfirm={handleConfirmRun} + /> +
+ ); +} + diff --git a/frontend/src/pages/rvw/api.ts b/frontend/src/pages/rvw/api.ts new file mode 100644 index 00000000..f6d5b332 --- /dev/null +++ b/frontend/src/pages/rvw/api.ts @@ -0,0 +1,129 @@ +/** + * 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!; +} + +// 运行审查任务 +export async function runTask(taskId: string, agents: AgentType[]): Promise { + const response = await axios.post>(`${API_BASE}/tasks/${taskId}/run`, { agents }); + if (!response.data.success) { + throw new Error(response.data.error || '运行失败'); + } +} + +// 批量运行审查任务 +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/src/pages/rvw/components/AgentModal.tsx b/frontend/src/pages/rvw/components/AgentModal.tsx new file mode 100644 index 00000000..070622c9 --- /dev/null +++ b/frontend/src/pages/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(selectedAgents); + onClose(); + }; + + if (!visible) return null; + + return ( +
+
+ {/* 头部 */} +
+

+ + 发起智能审稿 +

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

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

+ + {/* 规范性智能体 */} + + + {/* 方法学智能体 */} + +
+ + {/* 底部按钮 */} +
+ + +
+
+
+ ); +} + diff --git a/frontend/src/pages/rvw/components/BatchToolbar.tsx b/frontend/src/pages/rvw/components/BatchToolbar.tsx new file mode 100644 index 00000000..b33998ef --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/EditorialReport.tsx b/frontend/src/pages/rvw/components/EditorialReport.tsx new file mode 100644 index 00000000..9061c596 --- /dev/null +++ b/frontend/src/pages/rvw/components/EditorialReport.tsx @@ -0,0 +1,108 @@ +/** + * 规范性评估报告组件 + */ +import { AlertTriangle, CheckCircle, XCircle } from 'lucide-react'; +import type { EditorialReviewResult } from '../types'; +import ScoreRing from './ScoreRing'; + +interface EditorialReportProps { + data: EditorialReviewResult; +} + +export default function EditorialReport({ data }: EditorialReportProps) { + const getStatusIcon = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': + return ; + case 'warning': + return ; + case 'fail': + return ; + } + }; + + const getStatusTag = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': + return 通过; + case 'warning': + return 警告; + case 'fail': + return 不通过; + } + }; + + const getItemBgClass = (status: 'pass' | 'warning' | 'fail') => { + switch (status) { + case 'pass': + return ''; + case 'warning': + return 'bg-amber-50/50'; + case 'fail': + return 'bg-red-50/50'; + } + }; + + return ( +
+ {/* 总分卡片 */} +
= 80 ? 'border-green-200' : + data.overall_score >= 60 ? 'border-amber-200' : 'border-red-200' + }`}> + +
+

+ {data.overall_score >= 80 ? '基本符合稿约规范' : + data.overall_score >= 60 ? '部分符合稿约规范' : '不符合稿约规范'} +

+

{data.summary}

+
+
+ + {/* 检测详情 */} +
+
+ 检测详情({data.items.length}项) +
+ +
+ {data.items.map((item, index) => ( +
+
+
{getStatusIcon(item.status)}
+
+
+

{item.criterion}

+
+ {item.score}分 + {getStatusTag(item.status)} +
+
+ + {item.issues && item.issues.length > 0 && ( +
+ {item.issues.map((issue, i) => ( +

• {issue}

+ ))} +
+ )} + + {item.suggestions && item.suggestions.length > 0 && ( +
+

建议:

+ {item.suggestions.map((suggestion, i) => ( +

• {suggestion}

+ ))} +
+ )} +
+
+
+ ))} +
+
+
+ ); +} + diff --git a/frontend/src/pages/rvw/components/FilterChips.tsx b/frontend/src/pages/rvw/components/FilterChips.tsx new file mode 100644 index 00000000..f9a4fb51 --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/Header.tsx b/frontend/src/pages/rvw/components/Header.tsx new file mode 100644 index 00000000..8cf751ad --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/MethodologyReport.tsx b/frontend/src/pages/rvw/components/MethodologyReport.tsx new file mode 100644 index 00000000..f74713fb --- /dev/null +++ b/frontend/src/pages/rvw/components/MethodologyReport.tsx @@ -0,0 +1,113 @@ +/** + * 方法学评估报告组件 + */ +import { XCircle, AlertTriangle, CheckCircle } from 'lucide-react'; +import type { MethodologyReviewResult } from '../types'; +import ScoreRing from './ScoreRing'; + +interface MethodologyReportProps { + data: MethodologyReviewResult; +} + +export default function MethodologyReport({ data }: MethodologyReportProps) { + const getSeverityStyle = (severity: 'major' | 'minor') => { + return severity === 'major' + ? { border: 'border-red-200', bg: 'bg-red-50', icon: , label: '严重' } + : { border: 'border-amber-200', bg: 'bg-amber-50', icon: , label: '轻微' }; + }; + + const getOverallStatus = () => { + if (data.overall_score >= 80) return { label: '通过', color: 'text-green-700', bg: 'bg-green-50' }; + if (data.overall_score >= 60) return { label: '存疑', color: 'text-amber-700', bg: 'bg-amber-50' }; + return { label: '不通过', color: 'text-red-700', bg: 'bg-red-50' }; + }; + + const status = getOverallStatus(); + + return ( +
+ {/* 总分卡片 */} +
= 80 ? 'border-green-200' : + data.overall_score >= 60 ? 'border-amber-200' : 'border-red-200' + }`}> + +
+

+ 方法学评估 + + {status.label} + +

+

{data.summary}

+
+
+ + {/* 分项详情 */} + {data.parts.map((part, partIndex) => ( +
+
+ {part.part} +
+ {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} +

+
+ )} +
+
+
+ ); + })} +
+ ) : ( +
+ + 该部分未发现问题 +
+ )} +
+ ))} +
+ ); +} + diff --git a/frontend/src/pages/rvw/components/ReportDetail.tsx b/frontend/src/pages/rvw/components/ReportDetail.tsx new file mode 100644 index 00000000..ca27b024 --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/ScoreRing.tsx b/frontend/src/pages/rvw/components/ScoreRing.tsx new file mode 100644 index 00000000..3af1fdc3 --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/Sidebar.tsx b/frontend/src/pages/rvw/components/Sidebar.tsx new file mode 100644 index 00000000..959a0186 --- /dev/null +++ b/frontend/src/pages/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/src/pages/rvw/components/TaskTable.tsx b/frontend/src/pages/rvw/components/TaskTable.tsx new file mode 100644 index 00000000..ae1d628f --- /dev/null +++ b/frontend/src/pages/rvw/components/TaskTable.tsx @@ -0,0 +1,252 @@ +/** + * 任务表格组件 + */ +import { FileText, FileType2, Loader2, Play, Eye } 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; +} + +export default function TaskTable({ + tasks, + selectedIds, + onSelectChange, + onViewReport, + onRunTask +}: 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.methodologyStatus && ( +
+
+ 方法学: + + {task.methodologyStatus} + +
+ )} +
+ ); + } + + return null; + }; + + // 渲染操作按钮 + const renderActions = (task: ReviewTask) => { + if (task.status === 'completed') { + return ( + + ); + } + + if (task.status === 'pending') { + return ( + + ); + } + + if (task.status === 'extracting' || task.status === 'reviewing') { + 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/src/pages/rvw/components/index.ts b/frontend/src/pages/rvw/components/index.ts new file mode 100644 index 00000000..fc62afab --- /dev/null +++ b/frontend/src/pages/rvw/components/index.ts @@ -0,0 +1,14 @@ +/** + * 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'; + diff --git a/frontend/src/pages/rvw/index.ts b/frontend/src/pages/rvw/index.ts new file mode 100644 index 00000000..c75dddc8 --- /dev/null +++ b/frontend/src/pages/rvw/index.ts @@ -0,0 +1,7 @@ +/** + * RVW模块入口 + */ +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 new file mode 100644 index 00000000..fa2a0483 --- /dev/null +++ b/frontend/src/pages/rvw/styles.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/src/pages/rvw/types.ts b/frontend/src/pages/rvw/types.ts new file mode 100644 index 00000000..38810399 --- /dev/null +++ b/frontend/src/pages/rvw/types.ts @@ -0,0 +1,92 @@ +/** + * RVW模块类型定义 + */ + +// 任务状态 +export type TaskStatus = + | 'pending' // 待处理 + | 'extracting' // 提取文本中 + | 'reviewing' // 审查中 + | '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; + 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/git-cleanup-redcap.ps1 b/git-cleanup-redcap.ps1 index 6ed3040e..42d9161d 100644 --- a/git-cleanup-redcap.ps1 +++ b/git-cleanup-redcap.ps1 @@ -26,3 +26,4 @@ Write-Host "Next step: Run the commit command" -ForegroundColor Cyan + diff --git a/git-commit-day1.ps1 b/git-commit-day1.ps1 index 1f901b22..9a21753c 100644 --- a/git-commit-day1.ps1 +++ b/git-commit-day1.ps1 @@ -82,3 +82,4 @@ Write-Host "Git commit and push completed!" -ForegroundColor Green + diff --git a/git-fix-lock.ps1 b/git-fix-lock.ps1 index d81aa008..185b9b07 100644 --- a/git-fix-lock.ps1 +++ b/git-fix-lock.ps1 @@ -30,3 +30,4 @@ 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 c7930f04..e24214e8 100644 --- a/python-microservice/operations/__init__.py +++ b/python-microservice/operations/__init__.py @@ -53,5 +53,6 @@ __version__ = '1.0.0' + diff --git a/python-microservice/operations/binning.py b/python-microservice/operations/binning.py index 5ce3ea70..3ddaec12 100644 --- a/python-microservice/operations/binning.py +++ b/python-microservice/operations/binning.py @@ -160,5 +160,6 @@ def apply_binning( + diff --git a/python-microservice/operations/filter.py b/python-microservice/operations/filter.py index 153c73f0..4051cf46 100644 --- a/python-microservice/operations/filter.py +++ b/python-microservice/operations/filter.py @@ -146,5 +146,6 @@ def apply_filter( + diff --git a/python-microservice/operations/recode.py b/python-microservice/operations/recode.py index 1850f062..803051d8 100644 --- a/python-microservice/operations/recode.py +++ b/python-microservice/operations/recode.py @@ -116,5 +116,6 @@ def apply_recode( + diff --git a/recover_dc_code.py b/recover_dc_code.py index 42f9148c..d2e8f283 100644 --- a/recover_dc_code.py +++ b/recover_dc_code.py @@ -260,5 +260,6 @@ if __name__ == "__main__": + diff --git a/redcap-docker-dev/.gitattributes b/redcap-docker-dev/.gitattributes index 6dcd0a96..4f9440f5 100644 --- a/redcap-docker-dev/.gitattributes +++ b/redcap-docker-dev/.gitattributes @@ -42,3 +42,4 @@ + diff --git a/redcap-docker-dev/.gitignore b/redcap-docker-dev/.gitignore index f2a55302..b5e0faaf 100644 --- a/redcap-docker-dev/.gitignore +++ b/redcap-docker-dev/.gitignore @@ -73,3 +73,4 @@ Desktop.ini + diff --git a/redcap-docker-dev/README.md b/redcap-docker-dev/README.md index 2386dd5f..b9ef16ef 100644 --- a/redcap-docker-dev/README.md +++ b/redcap-docker-dev/README.md @@ -374,3 +374,4 @@ 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 80b96d23..ec31db04 100644 --- a/redcap-docker-dev/docker-compose.prod.yml +++ b/redcap-docker-dev/docker-compose.prod.yml @@ -135,3 +135,4 @@ volumes: + diff --git a/redcap-docker-dev/docker-compose.yml b/redcap-docker-dev/docker-compose.yml index b358f950..fde115c4 100644 --- a/redcap-docker-dev/docker-compose.yml +++ b/redcap-docker-dev/docker-compose.yml @@ -133,3 +133,4 @@ volumes: + diff --git a/redcap-docker-dev/env.template b/redcap-docker-dev/env.template index 24a85c7a..718fbab9 100644 --- a/redcap-docker-dev/env.template +++ b/redcap-docker-dev/env.template @@ -69,3 +69,4 @@ PMA_UPLOAD_LIMIT=50M + diff --git a/redcap-docker-dev/scripts/clean-redcap.ps1 b/redcap-docker-dev/scripts/clean-redcap.ps1 index d9c57de8..f3222c4d 100644 --- a/redcap-docker-dev/scripts/clean-redcap.ps1 +++ b/redcap-docker-dev/scripts/clean-redcap.ps1 @@ -77,3 +77,4 @@ Write-Host "" + diff --git a/redcap-docker-dev/scripts/create-redcap-password.php b/redcap-docker-dev/scripts/create-redcap-password.php index 96559117..dd0a070e 100644 --- a/redcap-docker-dev/scripts/create-redcap-password.php +++ b/redcap-docker-dev/scripts/create-redcap-password.php @@ -55,3 +55,4 @@ try { + diff --git a/redcap-docker-dev/scripts/logs-redcap.ps1 b/redcap-docker-dev/scripts/logs-redcap.ps1 index 7c478e78..1c220ba1 100644 --- a/redcap-docker-dev/scripts/logs-redcap.ps1 +++ b/redcap-docker-dev/scripts/logs-redcap.ps1 @@ -68,3 +68,4 @@ Write-Host "" + diff --git a/redcap-docker-dev/scripts/reset-admin-password.php b/redcap-docker-dev/scripts/reset-admin-password.php index 97b78957..50b68c1a 100644 --- a/redcap-docker-dev/scripts/reset-admin-password.php +++ b/redcap-docker-dev/scripts/reset-admin-password.php @@ -31,3 +31,4 @@ if ($result) { + diff --git a/redcap-docker-dev/scripts/start-redcap.ps1 b/redcap-docker-dev/scripts/start-redcap.ps1 index 64d155fd..8a6292c7 100644 --- a/redcap-docker-dev/scripts/start-redcap.ps1 +++ b/redcap-docker-dev/scripts/start-redcap.ps1 @@ -53,3 +53,4 @@ if ($LASTEXITCODE -eq 0) { + diff --git a/redcap-docker-dev/scripts/stop-redcap.ps1 b/redcap-docker-dev/scripts/stop-redcap.ps1 index 3449601b..a68ac4ab 100644 --- a/redcap-docker-dev/scripts/stop-redcap.ps1 +++ b/redcap-docker-dev/scripts/stop-redcap.ps1 @@ -39,3 +39,4 @@ if ($LASTEXITCODE -eq 0) { + diff --git a/run_recovery.ps1 b/run_recovery.ps1 index d207b586..2b4f8ab6 100644 --- a/run_recovery.ps1 +++ b/run_recovery.ps1 @@ -84,5 +84,6 @@ Write-Host "==================================================================== + diff --git a/tests/QUICKSTART_快速开始.md b/tests/QUICKSTART_快速开始.md index f7a03e86..4c7f803f 100644 --- a/tests/QUICKSTART_快速开始.md +++ b/tests/QUICKSTART_快速开始.md @@ -131,5 +131,6 @@ INFO: Uvicorn running on http://0.0.0.0:8001 + diff --git a/tests/README_测试说明.md b/tests/README_测试说明.md index 7d8c9008..e54c8afe 100644 --- a/tests/README_测试说明.md +++ b/tests/README_测试说明.md @@ -287,5 +287,6 @@ df_numeric.to_excel('test_data/numeric_test.xlsx', index=False) + diff --git a/tests/run_tests.bat b/tests/run_tests.bat index f966ceae..c64256c5 100644 --- a/tests/run_tests.bat +++ b/tests/run_tests.bat @@ -82,5 +82,6 @@ pause + diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 51120318..097fe797 100644 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -78,5 +78,6 @@ echo "========================================" + diff --git a/快速部署到SAE.md b/快速部署到SAE.md index 63ee4d02..02d7e83e 100644 --- a/快速部署到SAE.md +++ b/快速部署到SAE.md @@ -343,5 +343,6 @@ OSS AccessKeySecret:_______________ + diff --git a/部署检查清单.md b/部署检查清单.md index 48373b29..1e4c920b 100644 --- a/部署检查清单.md +++ b/部署检查清单.md @@ -379,5 +379,6 @@ OSS配置: +