diff --git a/docs/AI陪练二期开发推进计划.md b/docs/AI陪练二期开发推进计划.md index fbf07212..06604c83 100644 --- a/docs/AI陪练二期开发推进计划.md +++ b/docs/AI陪练二期开发推进计划.md @@ -127,7 +127,7 @@ - `frontend/src/api/aihr/practice.ts`:补场景/Rubric API。 - `frontend/src/views/train/practice.vue`:先加轻量场景选择和 Rubric 预览,不做复杂 CRUD。 -- 提示词模板管理可先挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。 +- 提示词模板管理已挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。 移动端: diff --git a/frontend/src/views/system/model/index.vue b/frontend/src/views/system/model/index.vue index c4debd32..aa3d78c1 100644 --- a/frontend/src/views/system/model/index.vue +++ b/frontend/src/views/system/model/index.vue @@ -166,6 +166,38 @@ +
+
+

提示词模板

+
+ 刷新 + 新增模板 +
+
+ + + + + + + + + + + + + +
+
调用优先级:模型配置覆盖供应商默认值;未配置时回退 seed,接口不返回 API Key。 @@ -241,6 +273,45 @@ 保存 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -266,6 +337,13 @@ import { type ModelProviderPayload, type VectorIndexStatus } from '@/api/aihr/model'; +import { + createPromptTemplate, + listPromptTemplates, + updatePromptTemplate, + type PromptTemplateRequest, + type PromptTemplateResponse +} from '@/api/aihr/practice'; type ProviderView = ModelProvider & { logo: string }; type ProviderFormState = ModelProviderPayload & { @@ -282,6 +360,9 @@ type ModelFormState = ModelConfigPayload & { apiKey: string; modelDescribe: string; }; +type PromptTemplateFormState = PromptTemplateRequest & { + id: number; +}; const seedProviders: ProviderView[] = [ { @@ -430,13 +511,17 @@ const keyword = ref(''); const loading = ref(false); const testing = ref(false); const saving = ref(false); +const promptLoading = ref(false); +const promptSaving = ref(false); const vectorLoading = ref(false); const rebuilding = ref(false); const showKey = ref(false); const providerDialogVisible = ref(false); const modelDialogVisible = ref(false); +const promptDialogVisible = ref(false); const lastTestAt = ref('未测试'); const vectorStatus = ref(); +const promptTemplates = ref([]); const providerForm = reactive({ id: 0, @@ -463,6 +548,20 @@ const modelForm = reactive({ enabled: true }); +const promptForm = reactive({ + id: 0, + code: '', + name: '', + category: 'practice', + outputFormat: '', + systemPrompt: '', + template: '', + variables: '', + version: 'v1', + enabled: true, + description: '' +}); + const filteredProviders = computed(() => { const word = keyword.value.trim().toLowerCase(); if (!word) return providers.value; @@ -549,6 +648,18 @@ const loadVectorStatus = async () => { } }; +const loadPromptTemplates = async () => { + promptLoading.value = true; + try { + const result = await listPromptTemplates(); + promptTemplates.value = result.data ?? []; + } catch { + promptTemplates.value = []; + } finally { + promptLoading.value = false; + } +}; + const runVectorRebuild = async () => { try { await ElMessageBox.confirm('将清空旧片段向量并重建 Qdrant 索引,继续吗?', '重建向量索引', { @@ -734,9 +845,78 @@ const toggleModelEnabled = async (row: ModelConfig, enabled: boolean) => { } }; +const openPromptDialog = (template?: PromptTemplateResponse) => { + Object.assign(promptForm, { + id: template?.id ?? 0, + code: template?.code ?? '', + name: template?.name ?? '', + category: template?.category ?? 'practice', + outputFormat: template?.outputFormat ?? '', + systemPrompt: template?.systemPrompt ?? '', + template: template?.template ?? '', + variables: template?.variables ?? '', + version: template?.version ?? 'v1', + enabled: template?.enabled ?? true, + description: template?.description ?? '' + }); + promptDialogVisible.value = true; +}; + +const promptPayload = (): PromptTemplateRequest | undefined => { + const code = promptForm.code.trim(); + const name = promptForm.name.trim(); + const template = promptForm.template.trim(); + if (!code || !name || !template) { + ElMessage.warning('请填写模板代码、名称和提示词模板'); + return undefined; + } + return { + code, + name, + category: promptForm.category.trim() || 'practice', + outputFormat: promptForm.outputFormat.trim() || code, + systemPrompt: promptForm.systemPrompt.trim(), + template, + variables: promptForm.variables.trim(), + version: promptForm.version.trim() || 'v1', + enabled: promptForm.enabled, + description: promptForm.description.trim() + }; +}; + +const submitPromptTemplate = async () => { + const payload = promptPayload(); + if (!payload) return; + + promptSaving.value = true; + try { + if (promptForm.id > 0) { + await updatePromptTemplate(promptForm.id, payload); + } else { + await createPromptTemplate(payload); + } + promptDialogVisible.value = false; + await loadPromptTemplates(); + ElMessage.success('提示词模板已保存'); + } finally { + promptSaving.value = false; + } +}; + +const togglePromptEnabled = async (row: PromptTemplateResponse, enabled: boolean) => { + try { + await updatePromptTemplate(row.id, { ...row, enabled }); + row.enabled = enabled; + ElMessage.success(enabled ? '模板已启用' : '模板已停用'); + } catch { + row.enabled = !enabled; + } +}; + onMounted(() => { loadData(); loadVectorStatus(); + loadPromptTemplates(); }); @@ -941,7 +1121,8 @@ onMounted(() => { .detail-panel, .vector-panel, -.model-panel { +.model-panel, +.prompt-panel { padding-bottom: 18px; } diff --git a/scripts/demo-check.sh b/scripts/demo-check.sh index a35ed931..09e540d9 100755 --- a/scripts/demo-check.sh +++ b/scripts/demo-check.sh @@ -110,10 +110,13 @@ contains frontend/src/views/train/practice.vue "导师改写" contains frontend/src/views/knowledge/cases.vue "真实语音" contains frontend/src/views/knowledge/sop.vue "训练题已生成" contains frontend/src/views/system/model/index.vue "调用优先级" +contains frontend/src/views/system/model/index.vue "提示词模板" contains frontend/src/api/aihr/interview.ts "/api/recruit/interview/start" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrInterviewController.java "/api/recruit/interview" contains frontend/src/api/aihr/practice.ts "/api/train/practice/start" +contains frontend/src/api/aihr/practice.ts "/api/aihr/prompt-templates" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPracticeController.java "/api/train/practice" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrPromptTemplateController.java "/api/aihr/prompt-templates" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "mobileCompletedCount" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "aihr_practice_session" contains backend/script/sql/aihr_practice_mysql8.sql "aihr_practice_scenario" @@ -142,6 +145,9 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control contains mobile/src/App.vue "贡献度加分" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed" contains mobile/src/App.vue "标记已复盘" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "source = 'retry'" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "maskSensitiveText" +contains mobile/src/App.vue "submitDailyDrill" contains frontend/src/api/aihr/case.ts "/api/knowledge/case/upload" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrCaseController.java "/api/knowledge/case" contains frontend/src/api/aihr/sop.ts "/api/knowledge/search" @@ -152,6 +158,7 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/doc/upload" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "仅支持 txt/md/markdown/pdf/doc/docx/xls/xlsx/ppt/pptx/图片文件" contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "/embeddings" +contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "aihr_knowledge_gap" contains backend/script/sql/aihr_knowledge_mysql8.sql "embedding_json" contains frontend/src/views/knowledge/sop.vue "上传文档" contains frontend/src/views/knowledge/sop.vue ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"