- 优化 DimensionAnalysisPanel 维度分析面板 - 更新 LlmResultPanel LLM 结果展示组件 - 完善 PromptEditor 提示词编辑器功能 - 改进 ReportForm 报告表单交互 - 优化 ReportEditDrawer 报告编辑抽屉 - 调整 prisoner 页面显示 - 更新 evaluation-report 和 report API Co-Authored-By: Claude <noreply@anthropic.com>
291 lines
7.5 KiB
Vue
291 lines
7.5 KiB
Vue
<template>
|
||
<div class="prompt-editor">
|
||
<div class="prompt-toggle">
|
||
<div class="toggle-title">
|
||
<el-icon><Document /></el-icon>
|
||
<span>提示词与变量</span>
|
||
</div>
|
||
<el-button link type="primary" @click="showPromptDetails = !showPromptDetails">
|
||
{{ showPromptDetails ? '收起' : '展开' }}
|
||
</el-button>
|
||
</div>
|
||
|
||
<div v-show="showPromptDetails">
|
||
<!-- 系统提示词 -->
|
||
<div class="prompt-section">
|
||
<div class="section-header">
|
||
<div class="header-left">
|
||
<el-icon><Document /></el-icon>
|
||
<span>系统提示词</span>
|
||
<el-tag size="small" type="info">只读</el-tag>
|
||
</div>
|
||
</div>
|
||
<el-input
|
||
type="textarea"
|
||
v-model="systemPrompt"
|
||
:rows="4"
|
||
readonly
|
||
placeholder="系统提示词由维度配置自动生成"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 用户提示词 -->
|
||
<div class="prompt-section">
|
||
<div class="section-header">
|
||
<div class="header-left">
|
||
<el-icon><Edit /></el-icon>
|
||
<span>自定义提示词</span>
|
||
</div>
|
||
<div class="header-actions">
|
||
<el-button link type="primary" @click="resetToDefault">
|
||
<el-icon><Refresh /></el-icon>
|
||
恢复默认
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<el-input
|
||
type="textarea"
|
||
v-model="customPrompt"
|
||
:rows="4"
|
||
placeholder="可在此处添加自定义提示词,指导AI生成更符合要求的评估内容"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 变量预览 -->
|
||
<div class="prompt-section">
|
||
<div class="section-header">
|
||
<div class="header-left">
|
||
<el-icon><InfoFilled /></el-icon>
|
||
<span>变量预览</span>
|
||
</div>
|
||
<div class="header-actions">
|
||
<el-button link type="primary" @click="showVariableHelp = true">
|
||
<el-icon><QuestionFilled /></el-icon>
|
||
帮助
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="variable-preview">
|
||
<el-tag
|
||
v-for="variable in variables"
|
||
:key="variable.key"
|
||
class="variable-tag"
|
||
@click="insertVariable(variable.key)"
|
||
>
|
||
<code>{{ getVariableDisplay(variable.key) }}</code>
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="action-buttons">
|
||
<el-button
|
||
type="primary"
|
||
:loading="generating"
|
||
@click="handleGenerate"
|
||
>
|
||
<el-icon><MagicStick /></el-icon>
|
||
AI自动生成
|
||
</el-button>
|
||
<el-button
|
||
:disabled="!generatedContent"
|
||
@click="handleRegenerate"
|
||
>
|
||
<el-icon><Refresh /></el-icon>
|
||
重新生成
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 变量说明对话框 -->
|
||
<el-dialog
|
||
v-model="showVariableHelp"
|
||
title="提示词变量说明"
|
||
width="500px"
|
||
>
|
||
<el-table :data="variables" stripe>
|
||
<el-table-column prop="key" label="变量" width="120">
|
||
<template #default="{ row }">
|
||
<code>{{ getVariableDisplay(row.key) }}</code>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="desc" label="说明" />
|
||
</el-table>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { Document, Edit, Refresh, InfoFilled, QuestionFilled, MagicStick } from '@element-plus/icons-vue'
|
||
import { DimensionVO } from '@/api/prison/evaluation-report'
|
||
import { computed } from 'vue'
|
||
|
||
defineOptions({ name: 'PromptEditor' })
|
||
|
||
const props = defineProps<{
|
||
dimension: DimensionVO | undefined
|
||
prisoner?: { name?: string; prisonerNo?: string }
|
||
generating?: boolean
|
||
generatedContent?: string
|
||
}>()
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'generate', customPrompt?: string, systemPrompt?: string): void
|
||
(e: 'regenerate', customPrompt?: string, systemPrompt?: string): void
|
||
}>()
|
||
|
||
// 生成状态(优先使用 props,其次从注入获取)
|
||
const injectedGenerating = inject<Ref<boolean>>('generating', ref(false))
|
||
const injectedGeneratedContent = inject<Ref<string>>('generatedContent', ref(''))
|
||
|
||
const generating = computed(() => props.generating ?? injectedGenerating.value)
|
||
const generatedContent = computed(() => props.generatedContent ?? injectedGeneratedContent.value)
|
||
|
||
// 系统提示词
|
||
const systemPrompt = computed(() => {
|
||
if (!props.dimension) return ''
|
||
return `你是一位专业的监狱评估专家,负责撰写服刑人员的评估报告。
|
||
当前评估维度:${props.dimension.name}
|
||
|
||
评估要求:
|
||
${props.dimension.aiPrompt || `根据服刑人员的基本信息和相关数据,撰写关于「${props.dimension.name}」的客观评估。`}
|
||
|
||
请直接返回评估内容,格式为段落文本。`
|
||
})
|
||
|
||
// 用户自定义提示词
|
||
const customPrompt = ref('')
|
||
|
||
// 变量列表
|
||
const variables = [
|
||
{ key: 'prisoner.name', desc: '罪犯姓名' },
|
||
{ key: 'prisoner.no', desc: '罪犯编号' },
|
||
{ key: 'dimension.name', desc: '维度名称' },
|
||
{ key: 'dimension.desc', desc: '维度描述' },
|
||
{ key: 'data.summary', desc: '数据汇总' }
|
||
]
|
||
|
||
const showPromptDetails = ref(false)
|
||
|
||
// 显示变量帮助
|
||
const showVariableHelp = ref(false)
|
||
|
||
// 恢复默认提示词
|
||
const resetToDefault = () => {
|
||
customPrompt.value = ''
|
||
}
|
||
|
||
// 获取变量显示格式
|
||
const getVariableDisplay = (key: string) => {
|
||
return `{{${key}}}`
|
||
}
|
||
|
||
// 插入变量
|
||
const insertVariable = (key: string) => {
|
||
const textarea = document.querySelector('.prompt-section:nth-child(2) textarea') as HTMLTextAreaElement
|
||
if (textarea) {
|
||
const start = textarea.selectionStart
|
||
const end = textarea.selectionEnd
|
||
const text = customPrompt.value
|
||
const variable = getVariableDisplay(key)
|
||
customPrompt.value = text.substring(0, start) + variable + text.substring(end)
|
||
textarea.focus()
|
||
textarea.setSelectionRange(start + variable.length, start + variable.length)
|
||
}
|
||
}
|
||
|
||
// 生成
|
||
const handleGenerate = () => {
|
||
emit('generate', customPrompt.value || undefined, systemPrompt.value || undefined)
|
||
}
|
||
|
||
// 重新生成
|
||
const handleRegenerate = () => {
|
||
emit('regenerate', customPrompt.value || undefined, systemPrompt.value || undefined)
|
||
}
|
||
|
||
// 获取自定义提示词(供父组件调用)
|
||
const getCustomPrompt = () => {
|
||
return customPrompt.value || undefined
|
||
}
|
||
|
||
defineExpose({ getCustomPrompt })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.prompt-editor {
|
||
padding: 16px;
|
||
background: var(--el-bg-color);
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
}
|
||
|
||
.prompt-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 8px;
|
||
margin-bottom: 12px;
|
||
border-bottom: 1px dashed var(--el-border-color);
|
||
|
||
.toggle-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.prompt-section {
|
||
margin-bottom: 16px;
|
||
|
||
&:last-of-type {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
}
|
||
|
||
.variable-preview {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
|
||
.variable-tag {
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
transform: scale(1.05);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
padding-top: 8px;
|
||
}
|
||
</style>
|