- feat: Create ASLLayout component with 7-module left navigation - feat: Implement Title Screening Settings page with optimized PICOS layout - feat: Add placeholder pages for Workbench and Results - fix: Fix nested routing structure for React Router v6 - fix: Resolve Spin component warning in MainLayout - fix: Add QueryClientProvider to App.tsx - style: Optimize PICOS form layout (P+I left, C+O+S right) - style: Align Inclusion/Exclusion criteria side-by-side - docs: Add architecture refactoring and routing fix reports Ref: Week 2 Frontend Development Scope: ASL module MVP - Title Abstract Screening
569 lines
20 KiB
Plaintext
569 lines
20 KiB
Plaintext
// This is your Prisma schema file,
|
||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
schemas = ["platform_schema", "aia_schema", "pkb_schema", "asl_schema", "common_schema", "dc_schema", "rvw_schema", "admin_schema", "ssa_schema", "st_schema", "public"]
|
||
}
|
||
|
||
// ==================== 用户模块 ====================
|
||
|
||
model User {
|
||
id String @id @default(uuid())
|
||
email String @unique
|
||
password String
|
||
name String?
|
||
avatarUrl String? @map("avatar_url")
|
||
|
||
role String @default("user")
|
||
status String @default("active")
|
||
|
||
kbQuota Int @default(3) @map("kb_quota")
|
||
kbUsed Int @default(0) @map("kb_used")
|
||
|
||
trialEndsAt DateTime? @map("trial_ends_at")
|
||
isTrial Boolean @default(true) @map("is_trial")
|
||
|
||
lastLoginAt DateTime? @map("last_login_at")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
projects Project[]
|
||
conversations Conversation[]
|
||
knowledgeBases KnowledgeBase[]
|
||
documents Document[]
|
||
adminLogs AdminLog[]
|
||
generalConversations GeneralConversation[]
|
||
batchTasks BatchTask[] // Phase 3: 批处理任务
|
||
taskTemplates TaskTemplate[] // Phase 3: 任务模板
|
||
reviewTasks ReviewTask[] // 稿件审查任务
|
||
aslProjects AslScreeningProject[] @relation("AslProjects") // ASL智能文献项目
|
||
|
||
@@index([email])
|
||
@@index([status])
|
||
@@index([createdAt])
|
||
@@map("users")
|
||
@@schema("platform_schema")
|
||
}
|
||
|
||
// ==================== 项目模块 ====================
|
||
|
||
model Project {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
name String
|
||
background String @default("") @db.Text
|
||
researchType String @default("observational") @map("research_type")
|
||
conversationCount Int @default(0) @map("conversation_count")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
deletedAt DateTime? @map("deleted_at")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
conversations Conversation[]
|
||
|
||
@@index([userId])
|
||
@@index([createdAt])
|
||
@@index([deletedAt])
|
||
@@map("projects")
|
||
@@schema("aia_schema")
|
||
}
|
||
|
||
// ==================== 对话模块 ====================
|
||
|
||
model Conversation {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
projectId String? @map("project_id")
|
||
agentId String @map("agent_id")
|
||
title String
|
||
modelName String @default("deepseek-v3") @map("model_name")
|
||
messageCount Int @default(0) @map("message_count")
|
||
totalTokens Int @default(0) @map("total_tokens")
|
||
metadata Json?
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
deletedAt DateTime? @map("deleted_at")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
messages Message[]
|
||
|
||
@@index([userId])
|
||
@@index([projectId])
|
||
@@index([agentId])
|
||
@@index([createdAt])
|
||
@@index([deletedAt])
|
||
@@map("conversations")
|
||
@@schema("aia_schema")
|
||
}
|
||
|
||
model Message {
|
||
id String @id @default(uuid())
|
||
conversationId String @map("conversation_id")
|
||
role String
|
||
content String @db.Text
|
||
model String?
|
||
metadata Json?
|
||
tokens Int?
|
||
isPinned Boolean @default(false) @map("is_pinned")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([conversationId])
|
||
@@index([createdAt])
|
||
@@index([isPinned])
|
||
@@map("messages")
|
||
@@schema("aia_schema")
|
||
}
|
||
|
||
// ==================== 知识库模块 ====================
|
||
|
||
model KnowledgeBase {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
name String
|
||
description String?
|
||
difyDatasetId String @map("dify_dataset_id")
|
||
fileCount Int @default(0) @map("file_count")
|
||
totalSizeBytes BigInt @default(0) @map("total_size_bytes")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
documents Document[]
|
||
batchTasks BatchTask[] // Phase 3: 批处理任务
|
||
|
||
@@index([userId])
|
||
@@index([difyDatasetId])
|
||
@@map("knowledge_bases")
|
||
@@schema("pkb_schema")
|
||
}
|
||
|
||
model Document {
|
||
id String @id @default(uuid())
|
||
kbId String @map("kb_id")
|
||
userId String @map("user_id")
|
||
filename String
|
||
fileType String @map("file_type")
|
||
fileSizeBytes BigInt @map("file_size_bytes")
|
||
fileUrl String @map("file_url")
|
||
difyDocumentId String @map("dify_document_id")
|
||
status String @default("uploading")
|
||
progress Int @default(0)
|
||
errorMessage String? @map("error_message")
|
||
segmentsCount Int? @map("segments_count")
|
||
tokensCount Int? @map("tokens_count")
|
||
|
||
// Phase 2: 全文阅读模式新增字段
|
||
extractionMethod String? @map("extraction_method") // pymupdf/nougat/mammoth/direct
|
||
extractionQuality Float? @map("extraction_quality") // 0-1质量分数
|
||
charCount Int? @map("char_count") // 字符数
|
||
language String? // 检测到的语言 (chinese/english)
|
||
extractedText String? @map("extracted_text") @db.Text // 提取的文本内容
|
||
|
||
uploadedAt DateTime @default(now()) @map("uploaded_at")
|
||
processedAt DateTime? @map("processed_at")
|
||
|
||
knowledgeBase KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade)
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
batchResults BatchResult[] // Phase 3: 批处理结果
|
||
|
||
@@index([kbId])
|
||
@@index([userId])
|
||
@@index([status])
|
||
@@index([difyDocumentId])
|
||
@@index([extractionMethod])
|
||
@@map("documents")
|
||
@@schema("pkb_schema")
|
||
}
|
||
|
||
// ==================== Phase 3: 批处理模块 ====================
|
||
|
||
// 批处理任务
|
||
model BatchTask {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
kbId String @map("kb_id")
|
||
|
||
// 任务基本信息
|
||
name String // 任务名称(用户可自定义)
|
||
templateType String @map("template_type") // 'preset' | 'custom'
|
||
templateId String? @map("template_id") // 预设模板ID(如'clinical_research')
|
||
prompt String @db.Text // 提示词(完整的)
|
||
|
||
// 执行状态
|
||
status String // 'processing' | 'completed' | 'failed' | 'paused'
|
||
totalDocuments Int @map("total_documents")
|
||
completedCount Int @default(0) @map("completed_count")
|
||
failedCount Int @default(0) @map("failed_count")
|
||
|
||
// 配置
|
||
modelType String @map("model_type") // 使用的模型
|
||
concurrency Int @default(3) // 固定为3
|
||
|
||
// 时间统计
|
||
startedAt DateTime? @map("started_at")
|
||
completedAt DateTime? @map("completed_at")
|
||
durationSeconds Int? @map("duration_seconds") // 执行时长(秒)
|
||
|
||
// 关联
|
||
results BatchResult[]
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
knowledgeBase KnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@index([userId])
|
||
@@index([kbId])
|
||
@@index([status])
|
||
@@index([createdAt])
|
||
@@map("batch_tasks")
|
||
@@schema("pkb_schema")
|
||
}
|
||
|
||
// 批处理结果(每篇文献一条)
|
||
model BatchResult {
|
||
id String @id @default(uuid())
|
||
taskId String @map("task_id")
|
||
documentId String @map("document_id")
|
||
|
||
// 执行结果
|
||
status String // 'success' | 'failed'
|
||
data Json? // 提取的结构化数据(预设模板)或文本(自定义)
|
||
rawOutput String? @db.Text @map("raw_output") // AI原始输出(备份)
|
||
errorMessage String? @db.Text @map("error_message") // 错误信息
|
||
|
||
// 性能指标
|
||
processingTimeMs Int? @map("processing_time_ms") // 处理时长(毫秒)
|
||
tokensUsed Int? @map("tokens_used") // Token使用量
|
||
|
||
// 关联
|
||
task BatchTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([taskId])
|
||
@@index([documentId])
|
||
@@index([status])
|
||
@@map("batch_results")
|
||
@@schema("pkb_schema")
|
||
}
|
||
|
||
// 任务模板(暂不实现,预留)
|
||
model TaskTemplate {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
|
||
name String
|
||
description String?
|
||
prompt String @db.Text
|
||
outputFields Json // 期望的输出字段定义
|
||
isPublic Boolean @default(false) @map("is_public")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@index([userId])
|
||
@@map("task_templates")
|
||
@@schema("pkb_schema")
|
||
}
|
||
|
||
// ==================== 运营管理模块 ====================
|
||
|
||
model AdminLog {
|
||
id Int @id @default(autoincrement())
|
||
adminId String @map("admin_id")
|
||
action String
|
||
resourceType String? @map("resource_type")
|
||
resourceId String? @map("resource_id")
|
||
details Json?
|
||
ipAddress String? @map("ip_address")
|
||
userAgent String? @map("user_agent")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
admin User @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([adminId])
|
||
@@index([createdAt])
|
||
@@index([action])
|
||
@@map("admin_logs")
|
||
@@schema("public")
|
||
}
|
||
|
||
// ==================== 通用对话模块 ====================
|
||
|
||
model GeneralConversation {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
title String
|
||
modelName String? @map("model_name")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||
deletedAt DateTime? @map("deleted_at")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
messages GeneralMessage[]
|
||
|
||
@@index([userId])
|
||
@@index([createdAt])
|
||
@@index([updatedAt])
|
||
@@map("general_conversations")
|
||
@@schema("aia_schema")
|
||
}
|
||
|
||
model GeneralMessage {
|
||
id String @id @default(uuid())
|
||
conversationId String @map("conversation_id")
|
||
role String
|
||
content String @db.Text
|
||
model String?
|
||
metadata Json?
|
||
tokens Int?
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
conversation GeneralConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([conversationId])
|
||
@@index([createdAt])
|
||
@@map("general_messages")
|
||
@@schema("aia_schema")
|
||
}
|
||
|
||
// ==================== 稿件审查模块 ====================
|
||
|
||
// 稿件审查任务
|
||
model ReviewTask {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
|
||
// 文件信息
|
||
fileName String @map("file_name")
|
||
fileSize Int @map("file_size")
|
||
filePath String? @map("file_path")
|
||
|
||
// 文档内容
|
||
extractedText String @map("extracted_text") @db.Text
|
||
wordCount Int? @map("word_count")
|
||
|
||
// 执行状态
|
||
status String @default("pending")
|
||
// pending, extracting, reviewing_editorial, reviewing_methodology, completed, failed
|
||
|
||
// 评估结果(JSON)
|
||
editorialReview Json? @map("editorial_review")
|
||
methodologyReview Json? @map("methodology_review")
|
||
overallScore Float? @map("overall_score")
|
||
|
||
// 执行信息
|
||
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") @db.Text
|
||
|
||
// 元数据
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
// 关联
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([userId])
|
||
@@index([status])
|
||
@@index([createdAt])
|
||
@@map("review_tasks")
|
||
@@schema("public")
|
||
}
|
||
|
||
// ==================== ASL智能文献模块 ====================
|
||
|
||
// ASL 筛选项目表
|
||
model AslScreeningProject {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
user User @relation("AslProjects", fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
projectName String @map("project_name")
|
||
|
||
// PICO标准
|
||
picoCriteria Json @map("pico_criteria") // { population, intervention, comparison, outcome, studyDesign }
|
||
|
||
// 筛选标准
|
||
inclusionCriteria String @map("inclusion_criteria") @db.Text
|
||
exclusionCriteria String @map("exclusion_criteria") @db.Text
|
||
|
||
// 状态
|
||
status String @default("draft") // draft, screening, completed
|
||
|
||
// 筛选配置
|
||
screeningConfig Json? @map("screening_config") // { models: ["deepseek", "qwen"], temperature: 0 }
|
||
|
||
// 关联
|
||
literatures AslLiterature[]
|
||
screeningTasks AslScreeningTask[]
|
||
screeningResults AslScreeningResult[]
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_projects")
|
||
@@schema("asl_schema")
|
||
@@index([userId])
|
||
@@index([status])
|
||
}
|
||
|
||
// ASL 文献条目表
|
||
model AslLiterature {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
// 文献基本信息
|
||
pmid String?
|
||
title String @db.Text
|
||
abstract String @db.Text
|
||
authors String?
|
||
journal String?
|
||
publicationYear Int? @map("publication_year")
|
||
doi String?
|
||
|
||
// 云原生存储字段(V1.0 阶段使用,MVP阶段预留)
|
||
pdfUrl String? @map("pdf_url") // PDF访问URL
|
||
pdfOssKey String? @map("pdf_oss_key") // OSS存储Key(用于删除)
|
||
pdfFileSize Int? @map("pdf_file_size") // 文件大小(字节)
|
||
|
||
// 关联
|
||
screeningResults AslScreeningResult[]
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("literatures")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([doi])
|
||
@@unique([projectId, pmid])
|
||
}
|
||
|
||
// ASL 筛选结果表
|
||
model AslScreeningResult {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
literatureId String @map("literature_id")
|
||
literature AslLiterature @relation(fields: [literatureId], references: [id], onDelete: Cascade)
|
||
|
||
// DeepSeek模型判断
|
||
dsModelName String @map("ds_model_name") // "deepseek-chat"
|
||
dsPJudgment String? @map("ds_p_judgment") // "match" | "partial" | "mismatch"
|
||
dsIJudgment String? @map("ds_i_judgment")
|
||
dsCJudgment String? @map("ds_c_judgment")
|
||
dsSJudgment String? @map("ds_s_judgment")
|
||
dsConclusion String? @map("ds_conclusion") // "include" | "exclude" | "uncertain"
|
||
dsConfidence Float? @map("ds_confidence") // 0-1
|
||
|
||
// DeepSeek模型证据
|
||
dsPEvidence String? @map("ds_p_evidence") @db.Text
|
||
dsIEvidence String? @map("ds_i_evidence") @db.Text
|
||
dsCEvidence String? @map("ds_c_evidence") @db.Text
|
||
dsSEvidence String? @map("ds_s_evidence") @db.Text
|
||
dsReason String? @map("ds_reason") @db.Text
|
||
|
||
// Qwen模型判断
|
||
qwenModelName String @map("qwen_model_name") // "qwen-max"
|
||
qwenPJudgment String? @map("qwen_p_judgment")
|
||
qwenIJudgment String? @map("qwen_i_judgment")
|
||
qwenCJudgment String? @map("qwen_c_judgment")
|
||
qwenSJudgment String? @map("qwen_s_judgment")
|
||
qwenConclusion String? @map("qwen_conclusion")
|
||
qwenConfidence Float? @map("qwen_confidence")
|
||
|
||
// Qwen模型证据
|
||
qwenPEvidence String? @map("qwen_p_evidence") @db.Text
|
||
qwenIEvidence String? @map("qwen_i_evidence") @db.Text
|
||
qwenCEvidence String? @map("qwen_c_evidence") @db.Text
|
||
qwenSEvidence String? @map("qwen_s_evidence") @db.Text
|
||
qwenReason String? @map("qwen_reason") @db.Text
|
||
|
||
// 冲突状态
|
||
conflictStatus String @default("none") @map("conflict_status") // "none" | "conflict" | "resolved"
|
||
conflictFields Json? @map("conflict_fields") // ["P", "I", "conclusion"]
|
||
|
||
// 最终决策
|
||
finalDecision String? @map("final_decision") // "include" | "exclude" | "pending"
|
||
finalDecisionBy String? @map("final_decision_by") // userId
|
||
finalDecisionAt DateTime? @map("final_decision_at")
|
||
exclusionReason String? @map("exclusion_reason") @db.Text
|
||
|
||
// AI处理状态
|
||
aiProcessingStatus String @default("pending") @map("ai_processing_status") // "pending" | "processing" | "completed" | "failed"
|
||
aiProcessedAt DateTime? @map("ai_processed_at")
|
||
aiErrorMessage String? @map("ai_error_message") @db.Text
|
||
|
||
// 可追溯信息
|
||
promptVersion String @default("v1.0.0") @map("prompt_version")
|
||
rawOutput Json? @map("raw_output") // 原始LLM输出(备份)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_results")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([literatureId])
|
||
@@index([conflictStatus])
|
||
@@index([finalDecision])
|
||
@@unique([projectId, literatureId])
|
||
}
|
||
|
||
// ASL 筛选任务表
|
||
model AslScreeningTask {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
taskType String @map("task_type") // "title_abstract" | "full_text"
|
||
status String @default("pending") // "pending" | "running" | "completed" | "failed"
|
||
|
||
// 进度统计
|
||
totalItems Int @map("total_items")
|
||
processedItems Int @default(0) @map("processed_items")
|
||
successItems Int @default(0) @map("success_items")
|
||
failedItems Int @default(0) @map("failed_items")
|
||
conflictItems Int @default(0) @map("conflict_items")
|
||
|
||
// 时间信息
|
||
startedAt DateTime? @map("started_at")
|
||
completedAt DateTime? @map("completed_at")
|
||
estimatedEndAt DateTime? @map("estimated_end_at")
|
||
|
||
// 错误信息
|
||
errorMessage String? @map("error_message") @db.Text
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_tasks")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([status])
|
||
}
|