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
This commit is contained in:
2026-01-11 21:25:16 +08:00
parent cdfbc9927a
commit 5523ef36ea
297 changed files with 15914 additions and 1266 deletions

View File

@@ -0,0 +1,108 @@
/**
* 测试 PromptService
*/
import { PrismaClient } from '@prisma/client';
import { getPromptService } from '../src/common/prompt/index.js';
const prisma = new PrismaClient();
async function main() {
console.log('🧪 测试 PromptService...\n');
const promptService = getPromptService(prisma);
// 1. 测试获取 ACTIVE Prompt
console.log('═══════════════════════════════════════════════════════');
console.log('📋 Test 1: 获取 ACTIVE Prompt (RVW_EDITORIAL)\n');
const editorial = await promptService.get('RVW_EDITORIAL');
console.log(` 版本: v${editorial.version}`);
console.log(` 是否草稿: ${editorial.isDraft}`);
console.log(` 模型配置: ${JSON.stringify(editorial.modelConfig)}`);
console.log(` 内容长度: ${editorial.content.length} 字符`);
console.log(` 内容预览: ${editorial.content.substring(0, 100)}...`);
// 2. 测试变量提取
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 2: 变量提取\n');
const template = '请评估以下稿件:{{title}}\n作者{{author}}\n{{#if abstract}}摘要:{{abstract}}{{/if}}';
const variables = promptService.extractVariables(template);
console.log(` 模板: ${template}`);
console.log(` 提取的变量: [${variables.join(', ')}]`);
// 3. 测试变量校验
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 3: 变量校验\n');
const validation = promptService.validateVariables(template, { title: '测试标题' });
console.log(` 有效: ${validation.isValid}`);
console.log(` 缺失变量: [${validation.missingVariables.join(', ')}]`);
console.log(` 多余变量: [${validation.extraVariables.join(', ')}]`);
// 4. 测试模板渲染
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 4: 模板渲染\n');
const rendered = promptService.render(template, {
title: '测试论文标题',
author: '张三',
abstract: '这是摘要内容',
});
console.log(` 渲染结果:\n${rendered}`);
// 5. 测试调试模式
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 5: 调试模式\n');
const testUserId = 'test-user-123';
console.log(` 设置调试模式: userId=${testUserId}, modules=[RVW]`);
promptService.setDebugMode(testUserId, ['RVW'], true);
const isDebugging = promptService.isDebugging(testUserId, 'RVW_EDITORIAL');
console.log(` 是否在调试 RVW_EDITORIAL: ${isDebugging}`);
const isDebuggingASL = promptService.isDebugging(testUserId, 'ASL_SCREENING');
console.log(` 是否在调试 ASL_SCREENING: ${isDebuggingASL}`);
const debugState = promptService.getDebugState(testUserId);
console.log(` 调试状态: modules=[${Array.from(debugState?.modules || []).join(', ')}]`);
console.log(` 关闭调试模式`);
promptService.setDebugMode(testUserId, [], false);
// 6. 测试列表模板
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 6: 列表所有模板\n');
const templates = await promptService.listTemplates();
console.log(`${templates.length} 个模板:`);
for (const t of templates) {
const latest = t.versions[0];
console.log(` - ${t.code} (${t.name}) - v${latest?.version || 0} ${latest?.status || 'N/A'}`);
}
// 7. 测试兜底 Prompt
console.log('\n═══════════════════════════════════════════════════════');
console.log('📋 Test 7: 兜底 Prompt\n');
try {
// 测试不存在且无兜底的 Prompt应该抛错
await promptService.get('NON_EXISTENT_CODE');
console.log(' ❌ 应该抛出错误但没有');
} catch (e) {
console.log(' ✅ 正确抛出错误不存在的Prompt且无兜底');
}
// 测试有兜底的 ASL_SCREENING (虽然DB里有但演示兜底机制)
console.log(' 兜底Prompt列表: RVW_EDITORIAL, RVW_METHODOLOGY, ASL_SCREENING');
console.log('\n✅ 所有测试完成!');
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());