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/api/aihr/practice.ts`:补场景/Rubric API。
|
||||||
- `frontend/src/views/train/practice.vue`:先加轻量场景选择和 Rubric 预览,不做复杂 CRUD。
|
- `frontend/src/views/train/practice.vue`:先加轻量场景选择和 Rubric 预览,不做复杂 CRUD。
|
||||||
- 提示词模板管理可先挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。
|
- 提示词模板管理已挂在“系统设置-模型配置”下:支持名称、代码、分类、输出格式、系统提示词、提示词模板、描述、启用状态;模板变量用 `{{变量名}}`。
|
||||||
|
|
||||||
移动端:
|
移动端:
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,38 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section>
|
</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">
|
<div class="tip-bar">
|
||||||
<el-icon><InfoFilled /></el-icon>
|
<el-icon><InfoFilled /></el-icon>
|
||||||
<span>调用优先级:模型配置覆盖供应商默认值;未配置时回退 seed,接口不返回 API Key。</span>
|
<span>调用优先级:模型配置覆盖供应商默认值;未配置时回退 seed,接口不返回 API Key。</span>
|
||||||
@@ -241,6 +273,45 @@
|
|||||||
<el-button type="danger" :loading="saving" @click="submitModel">保存</el-button>
|
<el-button type="danger" :loading="saving" @click="submitModel">保存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -266,6 +337,13 @@ import {
|
|||||||
type ModelProviderPayload,
|
type ModelProviderPayload,
|
||||||
type VectorIndexStatus
|
type VectorIndexStatus
|
||||||
} from '@/api/aihr/model';
|
} from '@/api/aihr/model';
|
||||||
|
import {
|
||||||
|
createPromptTemplate,
|
||||||
|
listPromptTemplates,
|
||||||
|
updatePromptTemplate,
|
||||||
|
type PromptTemplateRequest,
|
||||||
|
type PromptTemplateResponse
|
||||||
|
} from '@/api/aihr/practice';
|
||||||
|
|
||||||
type ProviderView = ModelProvider & { logo: string };
|
type ProviderView = ModelProvider & { logo: string };
|
||||||
type ProviderFormState = ModelProviderPayload & {
|
type ProviderFormState = ModelProviderPayload & {
|
||||||
@@ -282,6 +360,9 @@ type ModelFormState = ModelConfigPayload & {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
modelDescribe: string;
|
modelDescribe: string;
|
||||||
};
|
};
|
||||||
|
type PromptTemplateFormState = PromptTemplateRequest & {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
const seedProviders: ProviderView[] = [
|
const seedProviders: ProviderView[] = [
|
||||||
{
|
{
|
||||||
@@ -430,13 +511,17 @@ const keyword = ref('');
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const testing = ref(false);
|
const testing = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
const promptLoading = ref(false);
|
||||||
|
const promptSaving = ref(false);
|
||||||
const vectorLoading = ref(false);
|
const vectorLoading = ref(false);
|
||||||
const rebuilding = ref(false);
|
const rebuilding = ref(false);
|
||||||
const showKey = ref(false);
|
const showKey = ref(false);
|
||||||
const providerDialogVisible = ref(false);
|
const providerDialogVisible = ref(false);
|
||||||
const modelDialogVisible = ref(false);
|
const modelDialogVisible = ref(false);
|
||||||
|
const promptDialogVisible = ref(false);
|
||||||
const lastTestAt = ref('未测试');
|
const lastTestAt = ref('未测试');
|
||||||
const vectorStatus = ref<VectorIndexStatus>();
|
const vectorStatus = ref<VectorIndexStatus>();
|
||||||
|
const promptTemplates = ref<PromptTemplateResponse[]>([]);
|
||||||
|
|
||||||
const providerForm = reactive<ProviderFormState>({
|
const providerForm = reactive<ProviderFormState>({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -463,6 +548,20 @@ const modelForm = reactive<ModelFormState>({
|
|||||||
enabled: true
|
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 filteredProviders = computed(() => {
|
||||||
const word = keyword.value.trim().toLowerCase();
|
const word = keyword.value.trim().toLowerCase();
|
||||||
if (!word) return providers.value;
|
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 () => {
|
const runVectorRebuild = async () => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm('将清空旧片段向量并重建 Qdrant 索引,继续吗?', '重建向量索引', {
|
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(() => {
|
onMounted(() => {
|
||||||
loadData();
|
loadData();
|
||||||
loadVectorStatus();
|
loadVectorStatus();
|
||||||
|
loadPromptTemplates();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -941,7 +1121,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
.detail-panel,
|
.detail-panel,
|
||||||
.vector-panel,
|
.vector-panel,
|
||||||
.model-panel {
|
.model-panel,
|
||||||
|
.prompt-panel {
|
||||||
padding-bottom: 18px;
|
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/cases.vue "真实语音"
|
||||||
contains frontend/src/views/knowledge/sop.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/views/system/model/index.vue "提示词模板"
|
||||||
contains frontend/src/api/aihr/interview.ts "/api/recruit/interview/start"
|
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 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/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/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 "mobileCompletedCount"
|
||||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrPracticeSeedService.java "aihr_practice_session"
|
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"
|
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 mobile/src/App.vue "贡献度加分"
|
||||||
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrMobileController.java "/practice/reviews/{id}/reviewed"
|
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 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 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 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"
|
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/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 "仅支持 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 "/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 backend/script/sql/aihr_knowledge_mysql8.sql "embedding_json"
|
||||||
contains frontend/src/views/knowledge/sop.vue "上传文档"
|
contains frontend/src/views/knowledge/sop.vue "上传文档"
|
||||||
contains frontend/src/views/knowledge/sop.vue ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
|
contains frontend/src/views/knowledge/sop.vue ".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
|
||||||
|
|||||||
Reference in New Issue
Block a user