feat(aihr): add prompt template management UI
This commit is contained in:
@@ -127,7 +127,7 @@
|
||||
|
||||
- `frontend/src/api/aihr/practice.ts`:补场景/Rubric API。
|
||||
- `frontend/src/views/train/practice.vue`:先加轻量场景选择和 Rubric 预览,不做复杂 CRUD。
|
||||
- 提示词模板管理可先挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。
|
||||
- 提示词模板管理已挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。
|
||||
|
||||
移动端:
|
||||
|
||||
|
||||
@@ -166,6 +166,38 @@
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="panel prompt-panel" v-loading="promptLoading">
|
||||
<div class="panel-title with-actions">
|
||||
<h2>提示词模板</h2>
|
||||
<div>
|
||||
<el-button :icon="Refresh" @click="loadPromptTemplates">刷新</el-button>
|
||||
<el-button type="danger" :icon="Plus" @click="openPromptDialog()">新增模板</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="promptTemplates" class="model-table" size="large">
|
||||
<el-table-column prop="code" label="代码" min-width="150" />
|
||||
<el-table-column prop="name" label="名称" min-width="160" />
|
||||
<el-table-column prop="category" label="分类" width="120" />
|
||||
<el-table-column prop="outputFormat" label="文体" width="120" />
|
||||
<el-table-column prop="version" label="版本" width="100" />
|
||||
<el-table-column label="启用" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
active-color="#e60012"
|
||||
inactive-color="#d7dee8"
|
||||
@change="(value) => togglePromptEnabled(row, Boolean(value))"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" @click="openPromptDialog(row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<div class="tip-bar">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
<span>调用优先级:模型配置覆盖供应商默认值;未配置时回退 seed,接口不返回 API Key。</span>
|
||||
@@ -241,6 +273,45 @@
|
||||
<el-button type="danger" :loading="saving" @click="submitModel">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="promptDialogVisible" :title="promptForm.id > 0 ? '编辑提示词模板' : '新增提示词模板'" width="720px">
|
||||
<el-form label-width="104px">
|
||||
<el-form-item label="模板代码" required>
|
||||
<el-input v-model="promptForm.code" placeholder="summary_card" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模板名称" required>
|
||||
<el-input v-model="promptForm.name" placeholder="总结卡" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="promptForm.category" placeholder="practice" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出文体">
|
||||
<el-input v-model="promptForm.outputFormat" placeholder="summary_card" />
|
||||
</el-form-item>
|
||||
<el-form-item label="系统提示词">
|
||||
<el-input v-model="promptForm.systemPrompt" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="提示词模板" required>
|
||||
<el-input v-model="promptForm.template" type="textarea" :rows="5" />
|
||||
</el-form-item>
|
||||
<el-form-item label="变量">
|
||||
<el-input v-model="promptForm.variables" placeholder='["question","answer"]' />
|
||||
</el-form-item>
|
||||
<el-form-item label="版本">
|
||||
<el-input v-model="promptForm.version" placeholder="v1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="promptForm.description" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="promptForm.enabled" active-text="启用" inactive-text="停用" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="promptDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" :loading="promptSaving" @click="submitPromptTemplate">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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<VectorIndexStatus>();
|
||||
const promptTemplates = ref<PromptTemplateResponse[]>([]);
|
||||
|
||||
const providerForm = reactive<ProviderFormState>({
|
||||
id: 0,
|
||||
@@ -463,6 +548,20 @@ const modelForm = reactive<ModelFormState>({
|
||||
enabled: true
|
||||
});
|
||||
|
||||
const promptForm = reactive<PromptTemplateFormState>({
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -941,7 +1121,8 @@ onMounted(() => {
|
||||
|
||||
.detail-panel,
|
||||
.vector-panel,
|
||||
.model-panel {
|
||||
.model-panel,
|
||||
.prompt-panel {
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user