feat(admin): PromptsPane 重组(company_facts 主面+引擎级折叠)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,100 +1,137 @@
|
|||||||
<!--
|
<!--
|
||||||
提示词四件套编辑(Phase 3 Task 7b;消费 config/center/model.ts 纯逻辑)
|
提示词四件套编辑(Phase 3 Task 7b;Batch 3 重组:company_facts 主面 + 引擎级折叠)
|
||||||
- GET config 回显 prompts 组(四键 dict)→ 四个等宽 textarea 编辑 →
|
- ConfigCard 卡壳:company_facts(公司事实卡)为主面编辑器——AI 向客户
|
||||||
PUT config/prompts 整组提交(prompts 是 AI 行为源头——保存备注**必填**)
|
介绍公司/产品的事实依据;system/extractor/evaluator 三件收进「AI 引擎级
|
||||||
|
提示词」折叠区(默认收起——改错直接影响 AI 行为)
|
||||||
|
- GET config 回显 prompts 组(四键 dict)→ textarea 编辑 → PUT config/prompts
|
||||||
|
整组提交(读-改-写:折叠区收起不丢数据——normalizePromptsGroup 从快照取
|
||||||
|
全四键;prompts 是 AI 行为源头,保存备注**必填**)
|
||||||
- 字节数实时提示(UTF-8 口径 TextEncoder,与后端 len(encode) 一致):
|
- 字节数实时提示(UTF-8 口径 TextEncoder,与后端 len(encode) 一致):
|
||||||
company_facts 50KB / 其余 100KB;超限红显 + 禁存(后端同样硬拒)
|
company_facts 50KB / 其余 100KB;超限红显 + 禁存(后端同样硬拒)
|
||||||
- system.md 编辑器旁固定 $DEFLECTION_POOL 说明卡:该占位符由安全词表
|
- system.md 编辑器旁固定 $DEFLECTION_POOL 说明卡:该占位符由安全词表
|
||||||
迂回话术池自动注入,勿在提示词里手写话术清单(话术单一事实源)
|
迂回话术池自动注入,勿在提示词里手写话术清单(话术单一事实源)
|
||||||
- 重置出厂(Task 7d):头部危险色小按钮 → 确认框 → POST config/prompts/reset
|
- 重置出厂:底部危险行按钮 → 确认框 → POST config/prompts/reset
|
||||||
→ emit saved 回写快照组 → watch 重置表单为出厂档案值
|
→ emit saved 回写快照组 → watch 重置表单为出厂档案值
|
||||||
- 400 校验 detail(带字段路径)内联展示在保存按钮上方
|
- 400 校验 detail(带字段路径)内联展示在主面顶部
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<ElCard shadow="never" class="form-card prompts-pane">
|
<ConfigCard
|
||||||
<template #header>
|
class="prompts-pane"
|
||||||
<div class="card-header">
|
icon="solar:building-line-duotone"
|
||||||
<span class="card-title">提示词(prompts 组 · 四件套整组替换)</span>
|
title="公司资料"
|
||||||
<div class="header-side">
|
subtitle="AI 向客户介绍你公司/产品时依据的内容——保持事实准确、精炼"
|
||||||
<span class="card-sub">保存即写新版本并热生效——备注必填(AI 行为源头)</span>
|
info="AI 行为源头:改动必须填保存备注;每轮对话整段注入,超 50KB 禁存"
|
||||||
<ElButton
|
danger-label="恢复出厂:把提示词四件套重置为初始档案值"
|
||||||
size="small"
|
>
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:loading="resetting"
|
|
||||||
@click="onReset"
|
|
||||||
>
|
|
||||||
重置出厂
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElAlert
|
<ElAlert
|
||||||
v-if="actionError"
|
v-if="actionError"
|
||||||
type="error"
|
type="error"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:title="actionError"
|
|
||||||
class="pane-error"
|
class="pane-error"
|
||||||
|
:title="actionError"
|
||||||
@close="actionError = ''"
|
@close="actionError = ''"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- system.md + $DEFLECTION_POOL 说明卡 -->
|
<!-- 1. company_facts 主编辑器(常用面:公司事实卡) -->
|
||||||
<div class="prompt-block">
|
<div class="prompt-block">
|
||||||
<div class="prompt-head">
|
<div class="prompt-head">
|
||||||
<span class="prompt-label mono">{{ meta.key }}.md</span>
|
<span class="prompt-label mono">{{ factsMeta.key }}.md</span>
|
||||||
<span class="prompt-name">{{ meta.label }}</span>
|
<span class="prompt-name">{{ factsMeta.label }}</span>
|
||||||
<span class="prompt-bytes" :class="{ over: byteInfos.system.over }">
|
<span class="prompt-bytes" :class="{ over: byteInfos.company_facts.over }">
|
||||||
{{ byteInfos.system.label }}(UTF-8 字节)
|
{{ byteInfos.company_facts.label }}(UTF-8 字节)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="prompt-hint">{{ meta.hint }}</p>
|
<p class="prompt-hint">{{ factsMeta.hint }}</p>
|
||||||
<div class="system-row">
|
|
||||||
<ElInput
|
|
||||||
v-model="draft.system"
|
|
||||||
type="textarea"
|
|
||||||
:rows="meta.rows"
|
|
||||||
resize="vertical"
|
|
||||||
class="mono-input"
|
|
||||||
/>
|
|
||||||
<div class="deflection-note">
|
|
||||||
<p class="note-title">$DEFLECTION_POOL 占位符</p>
|
|
||||||
<p class="note-text">
|
|
||||||
此占位符自动注入安全词表(wall 组)deflections 迂回话术池,
|
|
||||||
<b>勿在提示词里手写话术清单</b>——改话术请去「安全词表」tab,
|
|
||||||
这里只保留 <span class="mono">$DEFLECTION_POOL</span> 占位符本身。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 其余三件 -->
|
|
||||||
<div v-for="item in restMeta" :key="item.key" class="prompt-block">
|
|
||||||
<div class="prompt-head">
|
|
||||||
<span class="prompt-label mono">{{ item.key }}.md</span>
|
|
||||||
<span class="prompt-name">{{ item.label }}</span>
|
|
||||||
<span class="prompt-bytes" :class="{ over: byteInfos[item.key].over }">
|
|
||||||
{{ byteInfos[item.key].label }}(UTF-8 字节)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="prompt-hint">{{ item.hint }}</p>
|
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="draft[item.key]"
|
v-model="draft.company_facts"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="item.rows"
|
:rows="factsMeta.rows"
|
||||||
resize="vertical"
|
resize="vertical"
|
||||||
class="mono-input"
|
class="mono-input"
|
||||||
/>
|
/>
|
||||||
<ElAlert
|
<ElAlert
|
||||||
v-if="byteInfos[item.key].over"
|
v-if="byteInfos.company_facts.over"
|
||||||
type="error"
|
type="error"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
class="over-alert"
|
class="over-alert"
|
||||||
:title="`${item.key} 超过 ${formatKB(byteInfos[item.key].cap)} 上限——每轮整段拼入 system prompt,请精炼(后端将拒绝保存)`"
|
:title="`company_facts 超过 ${formatKB(byteInfos.company_facts.cap)} 上限——每轮整段拼入 system prompt,请精炼(后端将拒绝保存)`"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="save-bar">
|
<!-- 2. AI 引擎级提示词折叠区(默认收起) -->
|
||||||
|
<ElCollapse class="engine-collapse">
|
||||||
|
<ElCollapseItem name="engine">
|
||||||
|
<template #title>
|
||||||
|
<span class="engine-title">AI 引擎级提示词(高级——改错直接影响 AI 行为)</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<p class="engine-warning">
|
||||||
|
以下三件是引擎级行为定义——没有把握不要修改,改坏可用版本历史回滚
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- system.md + $DEFLECTION_POOL 说明卡 -->
|
||||||
|
<div class="prompt-block">
|
||||||
|
<div class="prompt-head">
|
||||||
|
<span class="prompt-label mono">{{ systemMeta.key }}.md</span>
|
||||||
|
<span class="prompt-name">{{ systemMeta.label }}</span>
|
||||||
|
<span class="prompt-bytes" :class="{ over: byteInfos.system.over }">
|
||||||
|
{{ byteInfos.system.label }}(UTF-8 字节)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="prompt-hint">{{ systemMeta.hint }}</p>
|
||||||
|
<div class="system-row">
|
||||||
|
<ElInput
|
||||||
|
v-model="draft.system"
|
||||||
|
type="textarea"
|
||||||
|
:rows="systemMeta.rows"
|
||||||
|
resize="vertical"
|
||||||
|
class="mono-input"
|
||||||
|
/>
|
||||||
|
<div class="deflection-note">
|
||||||
|
<p class="note-title">$DEFLECTION_POOL 占位符</p>
|
||||||
|
<p class="note-text">
|
||||||
|
此占位符自动注入安全词表(wall 组)deflections 迂回话术池,
|
||||||
|
<b>勿在提示词里手写话术清单</b>——改话术请去「安全词表」tab,
|
||||||
|
这里只保留 <span class="mono">$DEFLECTION_POOL</span> 占位符本身。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- extractor / evaluator -->
|
||||||
|
<div v-for="item in engineRestMeta" :key="item.key" class="prompt-block">
|
||||||
|
<div class="prompt-head">
|
||||||
|
<span class="prompt-label mono">{{ item.key }}.md</span>
|
||||||
|
<span class="prompt-name">{{ item.label }}</span>
|
||||||
|
<span class="prompt-bytes" :class="{ over: byteInfos[item.key].over }">
|
||||||
|
{{ byteInfos[item.key].label }}(UTF-8 字节)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="prompt-hint">{{ item.hint }}</p>
|
||||||
|
<ElInput
|
||||||
|
v-model="draft[item.key]"
|
||||||
|
type="textarea"
|
||||||
|
:rows="item.rows"
|
||||||
|
resize="vertical"
|
||||||
|
class="mono-input"
|
||||||
|
/>
|
||||||
|
<ElAlert
|
||||||
|
v-if="byteInfos[item.key].over"
|
||||||
|
type="error"
|
||||||
|
:closable="false"
|
||||||
|
class="over-alert"
|
||||||
|
:title="`${item.key} 超过 ${formatKB(byteInfos[item.key].cap)} 上限——每轮整段拼入 system prompt,请精炼(后端将拒绝保存)`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
|
|
||||||
|
<template #footer-left>
|
||||||
|
<ElButton link type="primary" size="small" @click="emit('navigate', 'versions')">
|
||||||
|
出问题?回滚 →
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #footer-right>
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="saveComment"
|
v-model="saveComment"
|
||||||
maxlength="200"
|
maxlength="200"
|
||||||
@@ -107,10 +144,15 @@
|
|||||||
:disabled="!canSave"
|
:disabled="!canSave"
|
||||||
@click="onSave"
|
@click="onSave"
|
||||||
>
|
>
|
||||||
保存提示词组(整组提交,热生效)
|
保存
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</template>
|
||||||
</ElCard>
|
<template #danger>
|
||||||
|
<ElButton size="small" type="danger" plain :loading="resetting" @click="onReset">
|
||||||
|
重置出厂…
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ConfigCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -125,6 +167,7 @@
|
|||||||
} from './model'
|
} from './model'
|
||||||
import { putConfigGroup } from '@/api/config'
|
import { putConfigGroup } from '@/api/config'
|
||||||
import { confirmAndResetGroup } from './reset'
|
import { confirmAndResetGroup } from './reset'
|
||||||
|
import ConfigCard from './ConfigCard.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'PromptsPane' })
|
defineOptions({ name: 'PromptsPane' })
|
||||||
|
|
||||||
@@ -138,6 +181,8 @@
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
/** 保存成功:父层更新版本号与快照组 */
|
/** 保存成功:父层更新版本号与快照组 */
|
||||||
(e: 'saved', payload: { version: number; group: 'prompts'; config: Record<string, unknown> }): void
|
(e: 'saved', payload: { version: number; group: 'prompts'; config: Record<string, unknown> }): void
|
||||||
|
/** footer 回滚链接:请求父层切到版本历史组 */
|
||||||
|
(e: 'navigate', group: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// ---- 编辑态(group 引用变化 = 租户切换/重载 → 重置表单) ----------------------------
|
// ---- 编辑态(group 引用变化 = 租户切换/重载 → 重置表单) ----------------------------
|
||||||
@@ -167,9 +212,11 @@
|
|||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
|
|
||||||
/** system 单列渲染(旁边挂 $DEFLECTION_POOL 说明卡),其余三件走列表 */
|
/** company_facts 主面单渲;system 折叠内单渲(旁挂 $DEFLECTION_POOL 说明卡);
|
||||||
const meta = PROMPTS_META[0]
|
* extractor/evaluator 折叠内列表(PROMPTS_META 数据不变,仅展示位置分三层) */
|
||||||
const restMeta = PROMPTS_META.slice(1)
|
const factsMeta = PROMPTS_META.find((m) => m.key === 'company_facts')!
|
||||||
|
const systemMeta = PROMPTS_META.find((m) => m.key === 'system')!
|
||||||
|
const engineRestMeta = PROMPTS_META.filter((m) => m.key === 'extractor' || m.key === 'evaluator')
|
||||||
|
|
||||||
const canSave = computed(
|
const canSave = computed(
|
||||||
() => saveComment.value.trim() !== '' && !promptsOverCap(draft)
|
() => saveComment.value.trim() !== '' && !promptsOverCap(draft)
|
||||||
@@ -205,7 +252,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 重置出厂(Task 7d:确认 → POST reset → emit saved 回写快照组 →
|
// ---- 重置出厂(确认 → POST reset → emit saved 回写快照组 →
|
||||||
// group 引用变化触发上方 watch 重置表单为出厂档案值) -------------------------------
|
// group 引用变化触发上方 watch 重置表单为出厂档案值) -------------------------------
|
||||||
|
|
||||||
const resetting = ref(false)
|
const resetting = ref(false)
|
||||||
@@ -233,30 +280,6 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-sub {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--art-gray-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 头部右列:副标题 + 重置出厂小按钮(Task 7d)
|
|
||||||
.header-side {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pane-error {
|
.pane-error {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
@@ -299,6 +322,32 @@
|
|||||||
color: var(--art-gray-400);
|
color: var(--art-gray-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 引擎级折叠区 -------------------------------------------------------------------
|
||||||
|
|
||||||
|
.engine-collapse {
|
||||||
|
.engine-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__content) {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__wrap) {
|
||||||
|
// 内容区与主面区隔:折叠边框只包住引擎级三件
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.engine-warning {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
.system-row {
|
.system-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -347,14 +396,7 @@
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-bar {
|
.comment-input {
|
||||||
display: flex;
|
width: 360px;
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.comment-input {
|
|
||||||
width: 360px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -426,6 +426,7 @@
|
|||||||
:slug="activeSlug"
|
:slug="activeSlug"
|
||||||
:group="snapshot?.prompts"
|
:group="snapshot?.prompts"
|
||||||
@saved="onPaneSaved"
|
@saved="onPaneSaved"
|
||||||
|
@navigate="onGroupSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user