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 @@
+
+
+
提示词模板
+
+ 刷新
+ 新增模板
+
+
+
+
+
+
+
+
+
+
+ togglePromptEnabled(row, Boolean(value))"
+ />
+
+
+
+
+ 编辑
+
+
+
+
+
调用优先级:模型配置覆盖供应商默认值;未配置时回退 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"