Files
AIclinicalresearch/backend/scripts/migrate-rvw-prompts.ts
HaHafeng 5523ef36ea feat(admin): Complete Phase 3.5.1-3.5.4 Prompt Management System (83%)
Summary:
- Implement Prompt management infrastructure and core services
- Build admin portal frontend with light theme
- Integrate CodeMirror 6 editor for non-technical users

Phase 3.5.1: Infrastructure Setup
- Create capability_schema for Prompt storage
- Add prompt_templates and prompt_versions tables
- Add prompt:view/edit/debug/publish permissions
- Migrate RVW prompts to database (RVW_EDITORIAL, RVW_METHODOLOGY)

Phase 3.5.2: PromptService Core
- Implement gray preview logic (DRAFT for debuggers, ACTIVE for users)
- Module-level debug control (setDebugMode)
- Handlebars template rendering
- Variable extraction and validation (extractVariables, validateVariables)
- Three-level disaster recovery (database -> cache -> hardcoded fallback)

Phase 3.5.3: Management API
- 8 RESTful endpoints (/api/admin/prompts/*)
- Permission control (PROMPT_ENGINEER can edit, SUPER_ADMIN can publish)

Phase 3.5.4: Frontend Management UI
- Build admin portal architecture (AdminLayout, OrgLayout)
- Add route system (/admin/*, /org/*)
- Implement PromptListPage (filter, search, debug switch)
- Implement PromptEditor (CodeMirror 6 simplified for clinical users)
- Implement PromptEditorPage (edit, save, publish, test, version history)

Technical Details:
- Backend: 6 files, ~2044 lines (prompt.service.ts 596 lines)
- Frontend: 9 files, ~1735 lines (PromptEditorPage.tsx 399 lines)
- CodeMirror 6: Line numbers, auto-wrap, variable highlight, search, undo/redo
- Chinese-friendly: 15px font, 1.8 line-height, system fonts

Next Step: Phase 3.5.5 - Integrate RVW module with PromptService

Tested: Backend API tests passed (8/8), Frontend pending user testing
Status: Ready for Phase 3.5.5 RVW integration
2026-01-11 21:25:16 +08:00

161 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* RVW模块 Prompt 迁移脚本
*
* 将现有文件Prompt迁移到数据库
*
* 迁移内容:
* 1. RVW_EDITORIAL - 稿约规范性评估 (review_editorial_system.txt)
* 2. RVW_METHODOLOGY - 方法学质量评估 (review_methodology_system.txt)
* 3. RVW_TOPIC_SYSTEM - 选题评估系统提示 (topic_evaluation_system.txt)
* 4. RVW_TOPIC_USER - 选题评估用户模板 (topic_evaluation_user.txt)
*/
import { PrismaClient, PromptStatus } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const prisma = new PrismaClient();
// 变量提取函数
function extractVariables(content: string): string[] {
const regex = /\{\{(\w+)\}\}/g;
const variables = new Set<string>();
let match;
while ((match = regex.exec(content)) !== null) {
// 排除 Handlebars 控制语句如 #if, /if
if (!match[1].startsWith('#') && !match[1].startsWith('/')) {
variables.add(match[1]);
}
}
return Array.from(variables);
}
// RVW Prompt 配置(只有 2 个)
// 注意topic_evaluation_* 是"选题评估"功能,不属于 RVW 审稿模块
const rvwPrompts = [
{
code: 'RVW_EDITORIAL',
name: '稿约规范性评估',
module: 'RVW',
description: '评估医学稿件是否符合期刊稿约规范包括文题、摘要、参考文献等11项标准。输出JSON格式的评分和建议。',
file: 'review_editorial_system.txt',
modelConfig: { model: 'deepseek-v3', temperature: 0.3 },
},
{
code: 'RVW_METHODOLOGY',
name: '方法学质量评估',
module: 'RVW',
description: '评估医学稿件的科研设计、统计学方法和统计分析质量共20个检查点。输出JSON格式的评分和建议。',
file: 'review_methodology_system.txt',
modelConfig: { model: 'deepseek-v3', temperature: 0.3 },
},
];
async function main() {
console.log('🚀 开始迁移 RVW Prompt 到数据库...\n');
const promptsDir = path.join(__dirname, '..', 'prompts');
for (const prompt of rvwPrompts) {
console.log(`📄 处理: ${prompt.code} (${prompt.name})`);
// 读取文件内容
const filePath = path.join(promptsDir, prompt.file);
if (!fs.existsSync(filePath)) {
console.log(` ⚠️ 文件不存在: ${filePath}`);
continue;
}
const content = fs.readFileSync(filePath, 'utf-8').trim();
const variables = extractVariables(content);
console.log(` 📝 内容长度: ${content.length} 字符`);
console.log(` 🔤 提取变量: [${variables.join(', ')}]`);
// 创建或更新模板
const template = await prisma.prompt_templates.upsert({
where: { code: prompt.code },
update: {
name: prompt.name,
description: prompt.description,
variables: variables.length > 0 ? variables : null,
updated_at: new Date(),
},
create: {
code: prompt.code,
name: prompt.name,
module: prompt.module,
description: prompt.description,
variables: variables.length > 0 ? variables : null,
},
});
// 检查是否已有 ACTIVE 版本
const existingActive = await prisma.prompt_versions.findFirst({
where: {
template_id: template.id,
status: PromptStatus.ACTIVE,
},
});
if (existingActive) {
console.log(` ✅ 已存在 ACTIVE 版本 (v${existingActive.version})`);
} else {
// 创建第一个 ACTIVE 版本
await prisma.prompt_versions.create({
data: {
template_id: template.id,
version: 1,
content: content,
model_config: prompt.modelConfig,
status: PromptStatus.ACTIVE,
changelog: '从文件迁移的初始版本',
created_by: 'system-migration',
},
});
console.log(` ✅ 创建 ACTIVE 版本 (v1)`);
}
console.log('');
}
// 验证结果
console.log('═══════════════════════════════════════════════════════');
console.log('📊 迁移结果验证\n');
const templates = await prisma.prompt_templates.findMany({
where: { module: 'RVW' },
include: {
versions: {
orderBy: { version: 'desc' },
take: 1,
},
},
});
console.log(`✅ 共迁移 ${templates.length} 个 RVW Prompt:\n`);
for (const t of templates) {
const latestVersion = t.versions[0];
console.log(` 📋 ${t.code}`);
console.log(` 名称: ${t.name}`);
console.log(` 变量: ${t.variables ? JSON.stringify(t.variables) : '无'}`);
console.log(` 最新版本: v${latestVersion?.version} (${latestVersion?.status})`);
console.log('');
}
console.log('✅ RVW Prompt 迁移完成!');
}
main()
.catch((error) => {
console.error('❌ 迁移失败:', error);
process.exit(1);
})
.finally(() => prisma.$disconnect());