feat: 重构预设词系统,支持通用预设词

This commit is contained in:
digua
2026-01-11 19:20:46 +08:00
committed by digua
parent d3f0b93453
commit 6a01177ca5
14 changed files with 406 additions and 413 deletions
+10 -17
View File
@@ -43,36 +43,29 @@ const {
// Store
const promptStore = usePromptStore()
const { groupPresets, privatePresets, aiPromptSettings } = storeToRefs(promptStore)
const { aiPromptSettings, activePreset } = storeToRefs(promptStore)
// 当前聊天类型
const currentChatType = computed(() => props.chatType ?? 'group')
// 当前类型对应的预设列表
const currentPresets = computed(() => (currentChatType.value === 'group' ? groupPresets.value : privatePresets.value))
// 当前类型对应的预设列表(根据 applicableTo 过滤)
const currentPresets = computed(() => promptStore.getPresetsForChatType(currentChatType.value))
// 当前激活的预设 ID
const currentActivePresetId = computed(() =>
currentChatType.value === 'group'
? aiPromptSettings.value.activeGroupPresetId
: aiPromptSettings.value.activePrivatePresetId
)
const currentActivePresetId = computed(() => aiPromptSettings.value.activePresetId)
// 当前激活的预设
const currentActivePreset = computed(
() => currentPresets.value.find((p) => p.id === currentActivePresetId.value) || currentPresets.value[0]
)
// 当前激活的预设(如果当前激活的预设不适用于当前类型,使用第一个可用预设)
const currentActivePreset = computed(() => {
const activeInList = currentPresets.value.find((p) => p.id === currentActivePresetId.value)
return activeInList || activePreset.value
})
// 预设下拉菜单状态
const isPresetPopoverOpen = ref(false)
// 设置激活预设
function setActivePreset(presetId: string) {
if (currentChatType.value === 'group') {
promptStore.setActiveGroupPreset(presetId)
} else {
promptStore.setActivePrivatePreset(presetId)
}
promptStore.setActivePreset(presetId)
// 关闭下拉菜单
isPresetPopoverOpen.value = false
}
@@ -5,7 +5,7 @@
*/
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import dayjs from 'dayjs'
import type { ChatRecordMessage } from './types'
import { useSessionStore } from '@/stores/session'
@@ -3,7 +3,7 @@
* 会话时间线组件
* 使用 @tanstack/vue-virtual 实现虚拟滚动
*/
import { ref, computed, watch, onMounted, nextTick, onUnmounted } from 'vue'
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { useVirtualizer } from '@tanstack/vue-virtual'
@@ -167,9 +167,7 @@ function scrollToBottom() {
// 滚动到指定会话
function scrollToSession(sessionId: number) {
const index = flatList.value.findIndex(
(item) => item.type === 'session' && item.session.id === sessionId
)
const index = flatList.value.findIndex((item) => item.type === 'session' && item.session.id === sessionId)
if (index !== -1) {
virtualizer.value.scrollToIndex(index, { align: 'center' })
}
@@ -249,7 +247,7 @@ onMounted(() => {
<div class="relative w-full" :style="{ height: `${totalSize}px` }">
<div
v-for="virtualItem in virtualItems"
:key="virtualItem.key"
:key="String(virtualItem.key)"
:ref="(el) => measureElement(el as Element)"
class="absolute left-0 top-0 w-full"
:style="{ transform: `translateY(${virtualItem.start}px)` }"
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { PromptPreset } from '@/types/ai'
import type { PromptPreset, PresetApplicableType } from '@/types/ai'
import {
getDefaultRoleDefinition,
getDefaultResponseRules,
@@ -18,7 +18,6 @@ const props = defineProps<{
open: boolean
mode: 'add' | 'edit'
preset: PromptPreset | null
defaultChatType: 'group' | 'private'
}>()
// Emits
@@ -33,9 +32,10 @@ const promptStore = usePromptStore()
// 表单数据
const formData = ref({
name: '',
chatType: 'group' as 'group' | 'private',
roleDefinition: '',
responseRules: '',
supportGroup: true,
supportPrivate: true,
})
// 计算属性
@@ -55,6 +55,29 @@ const canSave = computed(() => {
return formData.value.name.trim() && formData.value.roleDefinition.trim() && formData.value.responseRules.trim()
})
/**
* 将 applicableTo 转换为勾选状态
*/
function applicableToCheckboxes(applicableTo?: PresetApplicableType): { group: boolean; private: boolean } {
if (!applicableTo || applicableTo === 'common') {
return { group: true, private: true }
}
return {
group: applicableTo === 'group',
private: applicableTo === 'private',
}
}
/**
* 将勾选状态转换为 applicableTo
*/
function checkboxesToApplicableTo(group: boolean, private_: boolean): PresetApplicableType {
if (group && private_) return 'common'
if (group) return 'group'
if (private_) return 'private'
return 'common' // 默认全选
}
// 监听打开状态,初始化表单
watch(
() => props.open,
@@ -62,19 +85,22 @@ watch(
if (newVal) {
if (props.preset) {
// 编辑模式:加载现有预设
const checkboxes = applicableToCheckboxes(props.preset.applicableTo)
formData.value = {
name: props.preset.name,
chatType: props.preset.chatType,
roleDefinition: props.preset.roleDefinition,
responseRules: props.preset.responseRules,
supportGroup: checkboxes.group,
supportPrivate: checkboxes.private,
}
} else {
// 添加模式:重置为默认(根据当前选中的聊天类型)
// 添加模式:重置为默认
formData.value = {
name: '',
chatType: props.defaultChatType,
roleDefinition: getDefaultRoleDefinition(props.defaultChatType, locale.value as LocaleType),
responseRules: getDefaultResponseRules(props.defaultChatType, locale.value as LocaleType),
roleDefinition: getDefaultRoleDefinition(locale.value as LocaleType),
responseRules: getDefaultResponseRules(locale.value as LocaleType),
supportGroup: true,
supportPrivate: true,
}
}
}
@@ -90,21 +116,32 @@ function closeModal() {
function handleSave() {
if (!canSave.value) return
const applicableTo = checkboxesToApplicableTo(formData.value.supportGroup, formData.value.supportPrivate)
if (isEditMode.value && props.preset) {
// 更新现有预设(支持内置和自定义)
promptStore.updatePromptPreset(props.preset.id, {
const updates: {
name: string
roleDefinition: string
responseRules: string
applicableTo?: PresetApplicableType
} = {
name: formData.value.name.trim(),
chatType: formData.value.chatType,
roleDefinition: formData.value.roleDefinition.trim(),
responseRules: formData.value.responseRules.trim(),
})
}
// 内置预设不更新 applicableTo
if (!isBuiltIn.value) {
updates.applicableTo = applicableTo
}
promptStore.updatePromptPreset(props.preset.id, updates)
} else {
// 添加新预设
promptStore.addPromptPreset({
name: formData.value.name.trim(),
chatType: formData.value.chatType,
roleDefinition: formData.value.roleDefinition.trim(),
responseRules: formData.value.responseRules.trim(),
applicableTo,
})
}
@@ -121,21 +158,20 @@ function handleReset() {
// 重置表单为原始值
formData.value = {
name: original.name,
chatType: original.chatType,
roleDefinition: original.roleDefinition,
responseRules: original.responseRules,
supportGroup: true,
supportPrivate: true,
}
// 清除覆盖
promptStore.resetBuiltinPreset(props.preset.id)
}
}
// 完整提示词预览
// 完整提示词预览(使用群聊模式作为示例)
const previewContent = computed(() => {
const chatType = formData.value.chatType
// 获取锁定的系统部分(用于预览)
const lockedSection = getLockedPromptSectionPreview(chatType, undefined, locale.value as LocaleType)
// 获取锁定的系统部分(用于预览,默认使用群聊模式)
const lockedSection = getLockedPromptSectionPreview('group', undefined, locale.value as LocaleType)
// 组合完整提示词
const responseRulesLabel = locale.value === 'zh-CN' ? '回答要求:' : 'Response requirements:'
@@ -162,27 +198,48 @@ ${formData.value.responseRules}`
<div class="max-h-[500px] space-y-4 overflow-y-auto pr-1">
<!-- 预设名称 -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('modal.presetName') }}</label>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{
t('modal.presetName')
}}</label>
<UInput v-model="formData.name" :placeholder="t('modal.presetNamePlaceholder')" class="w-60" />
</div>
<!-- 适用类型只读显示 -->
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
<UIcon
:name="formData.chatType === 'group' ? 'i-heroicons-chat-bubble-left-right' : 'i-heroicons-user'"
class="h-4 w-4"
/>
<span>{{ t('modal.appliesTo') }}{{ formData.chatType === 'group' ? t('modal.groupChat') : t('modal.privateChat') }}</span>
<!-- 适用场景仅自定义预设显示 -->
<div v-if="!isBuiltIn">
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('modal.applicableTo') }}
<span class="font-normal text-gray-500">{{ t('modal.applicableToHint') }}</span>
</label>
<div class="flex items-center gap-4">
<label class="flex cursor-pointer items-center gap-2">
<input
type="checkbox"
v-model="formData.supportGroup"
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('modal.groupChat') }}</span>
</label>
<label class="flex cursor-pointer items-center gap-2">
<input
type="checkbox"
v-model="formData.supportPrivate"
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('modal.privateChat') }}</span>
</label>
</div>
</div>
<!-- 角色定义 -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('modal.roleDefinition') }}</label>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{
t('modal.roleDefinition')
}}</label>
<UTextarea
v-model="formData.roleDefinition"
:rows="8"
:placeholder="t('modal.roleDefinitionPlaceholder')"
class="font-mono text-sm w-120"
class="w-120 font-mono text-sm"
/>
</div>
@@ -196,7 +253,7 @@ ${formData.value.responseRules}`
v-model="formData.responseRules"
:rows="5"
:placeholder="t('modal.responseRulesPlaceholder')"
class="font-mono text-sm w-120"
class="w-120 font-mono text-sm"
/>
</div>
@@ -205,6 +262,7 @@ ${formData.value.responseRules}`
<label class="mb-1.5 flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
<UIcon name="i-heroicons-eye" class="h-4 w-4 text-violet-500" />
{{ t('modal.preview') }}
<span class="font-normal text-gray-500">{{ t('modal.previewHint') }}</span>
</label>
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
<pre class="whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-300">{{ previewContent }}</pre>
@@ -238,15 +296,17 @@ ${formData.value.responseRules}`
"addCustom": "添加自定义提示词",
"presetName": "预设名称",
"presetNamePlaceholder": "为预设起个名字",
"appliesTo": "适用",
"groupChat": "群聊",
"privateChat": "私聊",
"applicableTo": "适用场景",
"applicableToHint": "(勾选后可在对应分析类型中使用)",
"groupChat": "群聊分析",
"privateChat": "私聊分析",
"roleDefinition": "角色定义",
"roleDefinitionPlaceholder": "定义 AI 助手的角色和任务...",
"responseRules": "回答要求",
"responseRulesHint": "(指导 AI 如何回答)",
"responseRulesPlaceholder": "定义 AI 回答的格式和要求...",
"preview": "完整提示词预览",
"previewHint": "(预览为群聊模式,实际会根据分析类型自动调整)",
"resetToDefault": "重置为默认",
"cancel": "取消",
"saveChanges": "保存修改",
@@ -260,7 +320,8 @@ ${formData.value.responseRules}`
"addCustom": "Add Custom Prompt",
"presetName": "Preset Name",
"presetNamePlaceholder": "Give your preset a name",
"appliesTo": "Applies to ",
"applicableTo": "Applicable To",
"applicableToHint": " (Check to enable for corresponding analysis type)",
"groupChat": "Group Chat",
"privateChat": "Private Chat",
"roleDefinition": "Role Definition",
@@ -269,6 +330,7 @@ ${formData.value.responseRules}`
"responseRulesHint": " (Guide how AI should respond)",
"responseRulesPlaceholder": "Define AI response format and requirements...",
"preview": "Full Prompt Preview",
"previewHint": " (Preview shows group chat mode, actual will adjust based on analysis type)",
"resetToDefault": "Reset to Default",
"cancel": "Cancel",
"saveChanges": "Save Changes",
@@ -11,7 +11,7 @@ const { t } = useI18n()
// Store
const promptStore = usePromptStore()
const { groupPresets, privatePresets, aiPromptSettings } = storeToRefs(promptStore)
const { allPromptPresets, aiPromptSettings } = storeToRefs(promptStore)
// Emits
const emit = defineEmits<{
@@ -23,13 +23,11 @@ const showEditModal = ref(false)
const showImportModal = ref(false)
const editMode = ref<'add' | 'edit'>('add')
const editingPreset = ref<PromptPreset | null>(null)
const defaultChatType = ref<'group' | 'private'>('group')
/** 打开新增预设弹窗 */
function openAddModal(chatType: 'group' | 'private') {
function openAddModal() {
editMode.value = 'add'
editingPreset.value = null
defaultChatType.value = chatType
showEditModal.value = true
}
@@ -37,7 +35,6 @@ function openAddModal(chatType: 'group' | 'private') {
function openEditModal(preset: PromptPreset) {
editMode.value = 'edit'
editingPreset.value = preset
defaultChatType.value = preset.chatType
showEditModal.value = true
}
@@ -47,12 +44,8 @@ function handleModalSaved() {
}
/** 设置当前激活的预设 */
function setActivePreset(presetId: string, chatType: 'group' | 'private') {
if (chatType === 'group') {
promptStore.setActiveGroupPreset(presetId)
} else {
promptStore.setActivePrivatePreset(presetId)
}
function setActivePreset(presetId: string) {
promptStore.setActivePreset(presetId)
emit('config-changed')
}
@@ -69,11 +62,8 @@ function deletePreset(presetId: string) {
}
/** 判断预设是否处于激活状态 */
function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolean {
if (chatType === 'group') {
return aiPromptSettings.value.activeGroupPresetId === presetId
}
return aiPromptSettings.value.activePrivatePresetId === presetId
function isActivePreset(presetId: string): boolean {
return aiPromptSettings.value.activePresetId === presetId
}
/** 导入预设后的回调 */
@@ -84,182 +74,105 @@ function handleImportPresetAdded() {
<template>
<div class="space-y-6">
<!-- 系统提示词标题和导入按钮 -->
<!-- 系统提示词标题和操作按钮 -->
<div class="flex items-center justify-between">
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-document-text" class="h-4 w-4 text-amber-500" />
{{ t('settings.aiPrompt.presets.title') }}
</h4>
<UButton variant="soft" color="primary" size="xs" @click="showImportModal = true">
<UIcon name="i-heroicons-cloud-arrow-down" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.presets.import') }}
</UButton>
<div class="flex items-center gap-2">
<UButton variant="ghost" color="gray" size="xs" @click="openAddModal">
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.presets.add') }}
</UButton>
<UButton variant="soft" color="primary" size="xs" @click="showImportModal = true">
<UIcon name="i-heroicons-cloud-arrow-down" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.presets.import') }}
</UButton>
</div>
</div>
<!-- 群聊和私聊系统提示词 -->
<div class="space-y-6">
<!-- 群聊预设组 -->
<div>
<div class="mb-3 flex items-center justify-between">
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-chat-bubble-left-right" class="h-4 w-4 text-violet-500" />
{{ t('settings.aiPrompt.group.title') }}
</h4>
<UButton variant="ghost" color="gray" size="xs" @click="openAddModal('group')">
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.group.add') }}
</UButton>
</div>
<div class="space-y-2">
<!-- 预设列表 -->
<div class="space-y-2">
<div
v-for="preset in allPromptPresets"
:key="preset.id"
class="group flex cursor-pointer items-center justify-between rounded-lg border p-2.5 transition-colors"
:class="[
isActivePreset(preset.id)
? 'border-primary-300 bg-primary-50 dark:border-primary-700 dark:bg-primary-900/20'
: 'border-gray-200 bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-gray-800',
]"
@click="setActivePreset(preset.id)"
>
<!-- 预设信息 -->
<div class="flex items-center gap-2">
<div
v-for="preset in groupPresets"
:key="preset.id"
class="group flex cursor-pointer items-center justify-between rounded-lg border p-2.5 transition-colors"
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
:class="[
isActivePreset(preset.id, 'group')
? 'border-primary-300 bg-primary-50 dark:border-primary-700 dark:bg-primary-900/20'
: 'border-gray-200 bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-gray-800',
isActivePreset(preset.id)
? 'bg-primary-500 text-white'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400',
]"
@click="setActivePreset(preset.id, 'group')"
>
<!-- 预设信息 -->
<div class="flex items-center gap-2">
<div
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
:class="[
isActivePreset(preset.id, 'group')
? 'bg-primary-500 text-white'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400',
]"
>
<UIcon
:name="isActivePreset(preset.id, 'group') ? 'i-heroicons-check' : 'i-heroicons-document-text'"
class="h-3 w-3"
/>
</div>
<div class="flex items-center gap-1.5">
<span class="text-xs font-medium text-gray-900 dark:text-white">{{ preset.name }}</span>
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft" size="xs">
{{ t('settings.aiPrompt.preset.builtIn') }}
</UBadge>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100" @click.stop>
<UButton
color="neutral"
variant="ghost"
size="xs"
:icon="preset.isBuiltIn ? 'i-heroicons-eye' : 'i-heroicons-pencil-square'"
@click="openEditModal(preset)"
/>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-heroicons-document-duplicate"
@click="duplicatePreset(preset.id)"
/>
<UButton
v-if="!preset.isBuiltIn"
color="error"
variant="ghost"
size="xs"
icon="i-heroicons-trash"
@click="deletePreset(preset.id)"
/>
</div>
<UIcon
:name="isActivePreset(preset.id) ? 'i-heroicons-check' : 'i-heroicons-document-text'"
class="h-3 w-3"
/>
</div>
<div class="flex items-center gap-1.5">
<span class="text-xs font-medium text-gray-900 dark:text-white">{{ preset.name }}</span>
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft" size="xs">
{{ t('settings.aiPrompt.preset.builtIn') }}
</UBadge>
<!-- 适用场景标签仅非通用预设显示 -->
<UBadge
v-if="!preset.isBuiltIn && preset.applicableTo && preset.applicableTo !== 'common'"
:color="preset.applicableTo === 'group' ? 'violet' : 'blue'"
variant="soft"
size="xs"
>
{{ preset.applicableTo === 'group' ? t('settings.aiPrompt.preset.groupOnly') : t('settings.aiPrompt.preset.privateOnly') }}
</UBadge>
</div>
</div>
</div>
<!-- 私聊系统提示词 -->
<div>
<div class="mb-3 flex items-center justify-between">
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-user" class="h-4 w-4 text-blue-500" />
{{ t('settings.aiPrompt.private.title') }}
</h4>
<UButton variant="ghost" color="gray" size="xs" @click="openAddModal('private')">
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.private.add') }}
</UButton>
</div>
<div class="space-y-2">
<div
v-for="preset in privatePresets"
:key="preset.id"
class="group flex cursor-pointer items-center justify-between rounded-lg border p-2.5 transition-colors"
:class="[
isActivePreset(preset.id, 'private')
? 'border-primary-300 bg-primary-50 dark:border-primary-700 dark:bg-primary-900/20'
: 'border-gray-200 bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-900 dark:hover:bg-gray-800',
]"
@click="setActivePreset(preset.id, 'private')"
>
<!-- 预设信息 -->
<div class="flex items-center gap-2">
<div
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full"
:class="[
isActivePreset(preset.id, 'private')
? 'bg-primary-500 text-white'
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400',
]"
>
<UIcon
:name="isActivePreset(preset.id, 'private') ? 'i-heroicons-check' : 'i-heroicons-document-text'"
class="h-3 w-3"
/>
</div>
<div class="flex items-center gap-1.5">
<span class="text-xs font-medium text-gray-900 dark:text-white">{{ preset.name }}</span>
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft" size="xs">
{{ t('settings.aiPrompt.preset.builtIn') }}
</UBadge>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100" @click.stop>
<UButton
color="neutral"
variant="ghost"
size="xs"
:icon="preset.isBuiltIn ? 'i-heroicons-eye' : 'i-heroicons-pencil-square'"
@click="openEditModal(preset)"
/>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-heroicons-document-duplicate"
@click="duplicatePreset(preset.id)"
/>
<UButton
v-if="!preset.isBuiltIn"
color="error"
variant="ghost"
size="xs"
icon="i-heroicons-trash"
@click="deletePreset(preset.id)"
/>
</div>
</div>
<!-- 操作按钮 -->
<div class="flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100" @click.stop>
<UButton
color="neutral"
variant="ghost"
size="xs"
:icon="preset.isBuiltIn ? 'i-heroicons-eye' : 'i-heroicons-pencil-square'"
@click="openEditModal(preset)"
/>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-heroicons-document-duplicate"
@click="duplicatePreset(preset.id)"
/>
<UButton
v-if="!preset.isBuiltIn"
color="error"
variant="ghost"
size="xs"
icon="i-heroicons-trash"
@click="deletePreset(preset.id)"
/>
</div>
</div>
</div>
<!-- 说明文字 -->
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.presets.description') }}
</p>
</div>
<!-- 编辑/添加弹窗 -->
<AIPromptEditModal
v-model:open="showEditModal"
:mode="editMode"
:preset="editingPreset"
:default-chat-type="defaultChatType"
@saved="handleModalSaved"
/>
<AIPromptEditModal v-model:open="showEditModal" :mode="editMode" :preset="editingPreset" @saved="handleModalSaved" />
<!-- 导入预设弹窗 -->
<ImportPresetModal v-model:open="showImportModal" @preset-added="handleImportPresetAdded" />
@@ -22,7 +22,10 @@ const isLoading = ref(false)
const error = ref('')
const remotePresets = ref<RemotePresetData[]>([])
// 分组预设
// 分组预设common 类型单独展示,group 和 private 分别展示)
const commonRemotePresets = computed(() =>
remotePresets.value.filter((p) => p.chatType === 'common' || !p.chatType)
)
const groupRemotePresets = computed(() => remotePresets.value.filter((p) => p.chatType === 'group'))
const privateRemotePresets = computed(() => remotePresets.value.filter((p) => p.chatType === 'private'))
@@ -104,6 +107,42 @@ watch(
<!-- 预设列表 -->
<div v-else class="space-y-4">
<!-- 通用预设群聊私聊都适用 -->
<div v-if="commonRemotePresets.length > 0">
<h4 class="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
<UIcon name="i-heroicons-squares-2x2" class="h-4 w-4 text-emerald-500" />
{{ t('importPreset.commonPresets') }}
</h4>
<div class="space-y-2">
<div
v-for="preset in commonRemotePresets"
:key="preset.id"
class="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800"
>
<div class="flex-1">
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name }}</p>
<p class="mt-0.5 line-clamp-1 text-xs text-gray-500 dark:text-gray-400">
{{ preset.roleDefinition.slice(0, 50) }}...
</p>
</div>
<UButton
v-if="promptStore.isRemotePresetAdded(preset.id)"
variant="soft"
color="gray"
size="xs"
disabled
>
<UIcon name="i-heroicons-check" class="mr-1 h-3.5 w-3.5" />
{{ t('importPreset.added') }}
</UButton>
<UButton v-else variant="soft" color="primary" size="xs" @click="handleAddPreset(preset)">
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
{{ t('importPreset.add') }}
</UButton>
</div>
</div>
</div>
<!-- 群聊预设 -->
<div v-if="groupRemotePresets.length > 0">
<h4 class="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
@@ -192,8 +231,9 @@ watch(
"loadError": "加载远程预设失败",
"noPresets": "暂无可用的远程预设",
"retry": "重试",
"groupPresets": "群聊预设",
"privatePresets": "私聊预设",
"commonPresets": "通用预设",
"groupPresets": "群聊专用预设",
"privatePresets": "私聊专用预设",
"add": "添加",
"added": "已添加"
}
@@ -206,8 +246,9 @@ watch(
"loadError": "Failed to load remote presets",
"noPresets": "No remote presets available",
"retry": "Retry",
"groupPresets": "Group Chat Presets",
"privatePresets": "Private Chat Presets",
"commonPresets": "Universal Presets",
"groupPresets": "Group Chat Only",
"privatePresets": "Private Chat Only",
"add": "Add",
"added": "Added"
}
+4 -5
View File
@@ -120,14 +120,13 @@ export function useAIChat(
// 获取 chat store 中的提示词配置和全局设置
const promptStore = usePromptStore()
const sessionStore = useSessionStore()
const { activeGroupPreset, activePrivatePreset, aiGlobalSettings } = storeToRefs(promptStore)
const { activePreset, aiGlobalSettings } = storeToRefs(promptStore)
// 获取当前聊天类型对应的提示词配置
// 获取当前聊天类型对应的提示词配置(使用统一的激活预设)
const currentPromptConfig = computed(() => {
const preset = chatType === 'group' ? activeGroupPreset.value : activePrivatePreset.value
return {
roleDefinition: preset.roleDefinition,
responseRules: preset.responseRules,
roleDefinition: activePreset.value.roleDefinition,
responseRules: activePreset.value.responseRules,
}
})
+1 -2
View File
@@ -1,4 +1,4 @@
import { ref, computed, onMounted, onUnmounted, type Ref, type ComputedRef } from 'vue'
import { ref, onMounted, onUnmounted, type Ref, type ComputedRef } from 'vue'
/**
* 导航项配置
@@ -102,4 +102,3 @@ export function useSubTabsScroll(navItems: ComputedRef<SubTabNavItem[]> | Ref<Su
handleNavChange,
}
}
+36 -87
View File
@@ -2,11 +2,11 @@
* AI 提示词统一配置
*
* 本文件集中管理所有 AI 提示词相关的配置:
* - 内置预设定义
* - 内置预设定义(统一版本,不再区分群聊/私聊)
* - 默认角色定义/回答要求
* - 锁定部分说明(用于前端预览)
*
* 主进程 (agent.ts) 的锁定部分逻辑需要独立维护,因为包含动态日期
* 注意:群聊/私聊的差异化内容(如成员查询策略)由后端 agent.ts 根据运行时 chatType 自动处理。
*/
import type { PromptPreset } from '@/types/ai'
@@ -19,34 +19,14 @@ export type LocaleType = 'zh-CN' | 'en-US'
const i18nContent = {
'zh-CN': {
presetNames: {
group: '默认群聊分析',
private: '默认私聊分析',
},
roleDefinition: {
group: `你是一个专业的群聊记录分析助手。
你的任务是帮助用户理解和分析他们的群聊记录数据。`,
private: `你是一个专业的私聊记录分析助手。
你的任务是帮助用户理解和分析他们的私聊记录数据。
注意:这是一个私聊对话,只有两个人参与。你的分析应该关注:
- 两人之间的对话互动
- 谁更主动、谁回复更多
- 对话的主题和内容变化
- 不要使用"群"这个词,使用"对话"或"聊天"`,
},
responseRules: {
group: `1. 基于工具返回的数据回答,不要编造信息
presetName: '默认分析助手',
roleDefinition: `你是一个专业的聊天记录分析助手。
你的任务是帮助用户理解和分析他们的聊天记录数据。`,
responseRules: `1. 基于工具返回的数据回答,不要编造信息
2. 如果数据不足以回答问题,请说明
3. 回答要简洁明了,使用 Markdown 格式
4. 可以引用具体的发言作为证据
5. 对于统计数据,可以适当总结趋势和特点`,
private: `1. 基于工具返回的数据回答,不要编造信息
2. 如果数据不足以回答问题,请说明
3. 回答要简洁明了,使用 Markdown 格式
4. 可以引用具体的发言作为证据
5. 关注两人之间的互动模式和对话特点`,
},
lockedSection: {
chatContext: {
group: '群聊',
@@ -80,34 +60,14 @@ const i18nContent = {
},
},
'en-US': {
presetNames: {
group: 'Default Group Analysis',
private: 'Default Private Analysis',
},
roleDefinition: {
group: `You are a professional group chat analysis assistant.
Your task is to help users understand and analyze their group chat records.`,
private: `You are a professional private chat analysis assistant.
Your task is to help users understand and analyze their private chat records.
Note: This is a private conversation with only two participants. Your analysis should focus on:
- The interaction between the two parties
- Who initiates more, who responds more
- Changes in conversation topics and content
- Do not use the word "group", use "conversation" or "chat" instead`,
},
responseRules: {
group: `1. Answer based on data returned by tools, do not fabricate information
presetName: 'Default Analysis Assistant',
roleDefinition: `You are a professional chat analysis assistant.
Your task is to help users understand and analyze their chat records.`,
responseRules: `1. Answer based on data returned by tools, do not fabricate information
2. If data is insufficient to answer the question, explain
3. Keep answers concise and clear, use Markdown format
4. Quote specific messages as evidence when possible
5. For statistics, summarize trends and characteristics appropriately`,
private: `1. Answer based on data returned by tools, do not fabricate information
2. If data is insufficient to answer the question, explain
3. Keep answers concise and clear, use Markdown format
4. Quote specific messages as evidence when possible
5. Focus on the interaction patterns and conversation characteristics between the two parties`,
},
lockedSection: {
chatContext: {
group: 'group chat',
@@ -136,7 +96,8 @@ Note: This is a private conversation with only two participants. Your analysis s
- "October 1st" → year: ${year}, month: 10, day: 1
- "October 1st 3pm" → year: ${year}, month: 10, day: 1, hour: 15
Default to ${year} if year not specified, use ${prevYear} if the month hasn't arrived yet`,
conclusion: 'Based on the user\'s question, select appropriate tools to retrieve data, then provide an answer based on the data.',
conclusion:
"Based on the user's question, select appropriate tools to retrieve data, then provide an answer based on the data.",
responseRulesLabel: 'Response requirements:',
},
},
@@ -144,41 +105,41 @@ Default to ${year} if year not specified, use ${prevYear} if the month hasn't ar
// ==================== 预设 ID 常量 ====================
/** 默认群聊预设ID */
export const DEFAULT_GROUP_PRESET_ID = 'builtin-group-default'
/** 默认私聊预设ID */
export const DEFAULT_PRIVATE_PRESET_ID = 'builtin-private-default'
/** 默认预设ID */
export const DEFAULT_PRESET_ID = 'builtin-default'
/** @deprecated 使用 DEFAULT_PRESET_ID 代替 */
export const DEFAULT_GROUP_PRESET_ID = DEFAULT_PRESET_ID
/** @deprecated 使用 DEFAULT_PRESET_ID 代替 */
export const DEFAULT_PRIVATE_PRESET_ID = DEFAULT_PRESET_ID
// ==================== 默认提示词内容 ====================
/**
* 获取默认角色定义
* @param chatType 聊天类型
* @param locale 语言设置
*/
export function getDefaultRoleDefinition(chatType: 'group' | 'private', locale: LocaleType = 'zh-CN'): string {
export function getDefaultRoleDefinition(locale: LocaleType = 'zh-CN'): string {
const content = i18nContent[locale] || i18nContent['zh-CN']
return content.roleDefinition[chatType]
return content.roleDefinition
}
/**
* 获取默认回答要求
* @param chatType 聊天类型
* @param locale 语言设置
*/
export function getDefaultResponseRules(chatType: 'group' | 'private', locale: LocaleType = 'zh-CN'): string {
export function getDefaultResponseRules(locale: LocaleType = 'zh-CN'): string {
const content = i18nContent[locale] || i18nContent['zh-CN']
return content.responseRules[chatType]
return content.responseRules
}
/**
* 获取内置预设名称
* @param chatType 聊天类型
* @param locale 语言设置
*/
export function getBuiltinPresetName(chatType: 'group' | 'private', locale: LocaleType = 'zh-CN'): string {
export function getBuiltinPresetName(locale: LocaleType = 'zh-CN'): string {
const content = i18nContent[locale] || i18nContent['zh-CN']
return content.presetNames[chatType]
return content.presetName
}
// ==================== 内置预设定义 ====================
@@ -190,29 +151,17 @@ export function getBuiltinPresetName(chatType: 'group' | 'private', locale: Loca
export function getBuiltinPresets(locale: LocaleType = 'zh-CN'): PromptPreset[] {
const now = Date.now()
const BUILTIN_GROUP_DEFAULT: PromptPreset = {
id: DEFAULT_GROUP_PRESET_ID,
name: getBuiltinPresetName('group', locale),
chatType: 'group',
roleDefinition: getDefaultRoleDefinition('group', locale),
responseRules: getDefaultResponseRules('group', locale),
const BUILTIN_DEFAULT: PromptPreset = {
id: DEFAULT_PRESET_ID,
name: getBuiltinPresetName(locale),
roleDefinition: getDefaultRoleDefinition(locale),
responseRules: getDefaultResponseRules(locale),
isBuiltIn: true,
createdAt: now,
updatedAt: now,
}
const BUILTIN_PRIVATE_DEFAULT: PromptPreset = {
id: DEFAULT_PRIVATE_PRESET_ID,
name: getBuiltinPresetName('private', locale),
chatType: 'private',
roleDefinition: getDefaultRoleDefinition('private', locale),
responseRules: getDefaultResponseRules('private', locale),
isBuiltIn: true,
createdAt: now,
updatedAt: now,
}
return [BUILTIN_GROUP_DEFAULT, BUILTIN_PRIVATE_DEFAULT]
return [BUILTIN_DEFAULT]
}
/** 所有内置预设(原始版本,用于重置)- 默认中文 */
@@ -237,14 +186,14 @@ export interface OwnerInfoPreview {
/**
* 获取锁定部分的提示词预览
* 注意:实际执行时由主进程 agent.ts 生成,包含动态日期
* 注意:实际执行时由主进程 agent.ts 生成,包含动态日期和差异化内容
*
* @param chatType 聊天类型
* @param chatType 聊天类型(用于展示对应的成员策略)
* @param ownerInfo Owner 信息(可选,用于预览时显示)
* @param locale 语言设置
*/
export function getLockedPromptSectionPreview(
chatType: 'group' | 'private',
chatType: 'group' | 'private' = 'group',
ownerInfo?: OwnerInfoPreview,
locale: LocaleType = 'zh-CN'
): string {
@@ -282,14 +231,14 @@ ${content.lockedSection.conclusion}`
* 构建完整提示词预览(用于前端展示)
* @param roleDefinition 角色定义
* @param responseRules 回答要求
* @param chatType 聊天类型
* @param chatType 聊天类型(用于展示对应的锁定部分)
* @param ownerInfo Owner 信息(可选)
* @param locale 语言设置
*/
export function buildPromptPreview(
roleDefinition: string,
responseRules: string,
chatType: 'group' | 'private',
chatType: 'group' | 'private' = 'group',
ownerInfo?: OwnerInfoPreview,
locale: LocaleType = 'zh-CN'
): string {
+5 -9
View File
@@ -182,18 +182,14 @@
},
"presets": {
"title": "System Prompts",
"import": "Import Presets"
},
"group": {
"title": "Group Chat System Prompts",
"add": "Add"
},
"private": {
"title": "Private Chat System Prompts",
"add": "Add"
"add": "Add Preset",
"import": "Import Presets",
"description": "Prompts are used for both group and private chat analysis, system adjusts content based on analysis type"
},
"preset": {
"builtIn": "Built-in",
"groupOnly": "Group Only",
"privateOnly": "Private Only",
"view": "View",
"edit": "Edit",
"copy": "Copy",
+5 -9
View File
@@ -182,18 +182,14 @@
},
"presets": {
"title": "系统提示词",
"import": "导入预设"
},
"group": {
"title": "群聊系统提示词",
"add": "添加"
},
"private": {
"title": "私聊系统提示词",
"add": "添加"
"add": "添加预设",
"import": "导入预设",
"description": "提示词同时用于群聊和私聊分析,系统会自动根据分析类型调整相关内容"
},
"preset": {
"builtIn": "内置",
"groupOnly": "仅群聊",
"privateOnly": "仅私聊",
"view": "查看",
"edit": "编辑",
"copy": "复制",
+1 -1
View File
@@ -58,7 +58,7 @@ const features = computed(() => [
<!-- Feature Text -->
<div class="xl:mb-16 mb-12 flex flex-wrap items-center justify-center gap-x-8 gap-y-4 px-4">
<template v-for="(feature, index) in features" :key="feature.title">
<template v-for="feature in features" :key="feature.title">
<div class="group flex items-center gap-2 cursor-default">
<UIcon
name="i-heroicons-check-circle"
+93 -62
View File
@@ -2,13 +2,7 @@ import { defineStore, storeToRefs } from 'pinia'
import { ref, computed } from 'vue'
import type { PromptPreset, AIPromptSettings } from '@/types/ai'
import type { KeywordTemplate } from '@/types/analysis'
import {
DEFAULT_GROUP_PRESET_ID,
DEFAULT_PRIVATE_PRESET_ID,
getBuiltinPresets,
getOriginalBuiltinPreset,
type LocaleType,
} from '@/config/prompts'
import { DEFAULT_PRESET_ID, getBuiltinPresets, getOriginalBuiltinPreset, type LocaleType } from '@/config/prompts'
import { useSettingsStore } from './settings'
// 远程预设配置 URL 基础地址
@@ -20,9 +14,10 @@ const REMOTE_PRESET_BASE_URL = 'https://chatlab.fun'
export interface RemotePresetData {
id: string
name: string
chatType: 'group' | 'private'
roleDefinition: string
responseRules: string
/** 适用场景:common(通用)、group(仅群聊)、private(仅私聊) */
chatType?: 'common' | 'group' | 'private'
}
/**
@@ -40,8 +35,7 @@ export const usePromptStore = defineStore(
Record<string, { name?: string; roleDefinition?: string; responseRules?: string; updatedAt?: number }>
>({})
const aiPromptSettings = ref<AIPromptSettings>({
activeGroupPresetId: DEFAULT_GROUP_PRESET_ID,
activePrivatePresetId: DEFAULT_PRIVATE_PRESET_ID,
activePresetId: DEFAULT_PRESET_ID,
})
const aiConfigVersion = ref(0)
const aiGlobalSettings = ref({
@@ -70,23 +64,26 @@ export const usePromptStore = defineStore(
return [...mergedBuiltins, ...customPromptPresets.value]
})
/** 群聊预设列表 */
const groupPresets = computed(() => allPromptPresets.value.filter((p) => p.chatType === 'group'))
/** 私聊预设列表 */
const privatePresets = computed(() => allPromptPresets.value.filter((p) => p.chatType === 'private'))
/** 当前激活的群聊预设 */
const activeGroupPreset = computed(() => {
const preset = allPromptPresets.value.find((p) => p.id === aiPromptSettings.value.activeGroupPresetId)
return preset || builtinPresets.value.find((p) => p.id === DEFAULT_GROUP_PRESET_ID)!
/** 当前激活的预设 */
const activePreset = computed(() => {
const preset = allPromptPresets.value.find((p) => p.id === aiPromptSettings.value.activePresetId)
return preset || builtinPresets.value.find((p) => p.id === DEFAULT_PRESET_ID)!
})
/** 当前激活的私聊预设 */
const activePrivatePreset = computed(() => {
const preset = allPromptPresets.value.find((p) => p.id === aiPromptSettings.value.activePrivatePresetId)
return preset || builtinPresets.value.find((p) => p.id === DEFAULT_PRIVATE_PRESET_ID)!
})
/**
* 获取适用于指定聊天类型的预设列表
* @param chatType 聊天类型
*/
function getPresetsForChatType(chatType: 'group' | 'private'): PromptPreset[] {
return allPromptPresets.value.filter((preset) => {
// 内置预设始终适用
if (preset.isBuiltIn) return true
// 未设置 applicableTo 或 common 适用于所有类型
if (!preset.applicableTo || preset.applicableTo === 'common') return true
// 检查是否匹配当前类型
return preset.applicableTo === chatType
})
}
/**
* 通知外部 AI 配置已经被修改
@@ -154,14 +151,17 @@ export const usePromptStore = defineStore(
*/
function addPromptPreset(preset: {
name: string
chatType: PromptPreset['chatType']
roleDefinition: string
responseRules: string
applicableTo?: 'common' | 'group' | 'private'
}) {
const newPreset: PromptPreset = {
...preset,
id: `custom-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
name: preset.name,
roleDefinition: preset.roleDefinition,
responseRules: preset.responseRules,
isBuiltIn: false,
applicableTo: preset.applicableTo || 'common',
createdAt: Date.now(),
updatedAt: Date.now(),
}
@@ -174,7 +174,12 @@ export const usePromptStore = defineStore(
*/
function updatePromptPreset(
presetId: string,
updates: { name?: string; chatType?: PromptPreset['chatType']; roleDefinition?: string; responseRules?: string }
updates: {
name?: string
roleDefinition?: string
responseRules?: string
applicableTo?: 'common' | 'group' | 'private'
}
) {
const isBuiltin = builtinPresets.value.some((p) => p.id === presetId)
if (isBuiltin) {
@@ -222,11 +227,9 @@ export const usePromptStore = defineStore(
const index = customPromptPresets.value.findIndex((p) => p.id === presetId)
if (index !== -1) {
customPromptPresets.value.splice(index, 1)
if (aiPromptSettings.value.activeGroupPresetId === presetId) {
aiPromptSettings.value.activeGroupPresetId = DEFAULT_GROUP_PRESET_ID
}
if (aiPromptSettings.value.activePrivatePresetId === presetId) {
aiPromptSettings.value.activePrivatePresetId = DEFAULT_PRIVATE_PRESET_ID
// 如果删除的是当前激活的预设,切换回默认
if (aiPromptSettings.value.activePresetId === presetId) {
aiPromptSettings.value.activePresetId = DEFAULT_PRESET_ID
}
}
}
@@ -240,7 +243,6 @@ export const usePromptStore = defineStore(
const copySuffix = locale.value === 'zh-CN' ? '(副本)' : '(Copy)'
return addPromptPreset({
name: `${source.name} ${copySuffix}`,
chatType: source.chatType,
roleDefinition: source.roleDefinition,
responseRules: source.responseRules,
})
@@ -249,32 +251,22 @@ export const usePromptStore = defineStore(
}
/**
* 设置当前激活的群聊预设
* 设置当前激活的预设
*/
function setActiveGroupPreset(presetId: string) {
function setActivePreset(presetId: string) {
const preset = allPromptPresets.value.find((p) => p.id === presetId)
if (preset && preset.chatType === 'group') {
aiPromptSettings.value.activeGroupPresetId = presetId
if (preset) {
aiPromptSettings.value.activePresetId = presetId
notifyAIConfigChanged()
}
}
/**
* 设置当前激活的私聊预设
* 获取当前激活的预设
* @param _chatType 已弃用,保留参数兼容旧代码
*/
function setActivePrivatePreset(presetId: string) {
const preset = allPromptPresets.value.find((p) => p.id === presetId)
if (preset && preset.chatType === 'private') {
aiPromptSettings.value.activePrivatePresetId = presetId
notifyAIConfigChanged()
}
}
/**
* 获取指定聊天类型对应的激活预设
*/
function getActivePresetForChatType(chatType: 'group' | 'private'): PromptPreset {
return chatType === 'group' ? activeGroupPreset.value : activePrivatePreset.value
function getActivePresetForChatType(_chatType?: 'group' | 'private'): PromptPreset {
return activePreset.value
}
/**
@@ -298,9 +290,7 @@ export const usePromptStore = defineStore(
}
// 过滤无效数据
return remotePresets.filter(
(preset) => preset.id && preset.name && preset.chatType && preset.roleDefinition && preset.responseRules
)
return remotePresets.filter((preset) => preset.id && preset.name && preset.roleDefinition && preset.responseRules)
} catch {
return []
}
@@ -318,13 +308,16 @@ export const usePromptStore = defineStore(
}
const now = Date.now()
// 将远程 chatType 映射为本地 applicableTo
const applicableTo = preset.chatType || 'common'
const newPreset: PromptPreset = {
id: preset.id,
name: preset.name,
chatType: preset.chatType,
roleDefinition: preset.roleDefinition,
responseRules: preset.responseRules,
isBuiltIn: false,
applicableTo,
createdAt: now,
updatedAt: now,
}
@@ -342,6 +335,47 @@ export const usePromptStore = defineStore(
return fetchedRemotePresetIds.value.includes(presetId)
}
// ==================== 数据迁移(兼容旧版本) ====================
/**
* 迁移旧版本的预设数据
* 将群聊/私聊分离的预设合并为统一预设
*/
function migrateOldPresets() {
// 检查是否存在旧版本数据结构
const oldSettings = aiPromptSettings.value as unknown as {
activeGroupPresetId?: string
activePrivatePresetId?: string
activePresetId?: string
}
// 如果存在旧字段,进行迁移
if (oldSettings.activeGroupPresetId && !oldSettings.activePresetId) {
// 优先使用群聊预设,因为使用频率更高
const oldGroupId = oldSettings.activeGroupPresetId
// 如果是旧的内置预设 ID,映射到新的统一 ID
if (oldGroupId === 'builtin-group-default' || oldGroupId === 'builtin-private-default') {
aiPromptSettings.value.activePresetId = DEFAULT_PRESET_ID
} else {
aiPromptSettings.value.activePresetId = oldGroupId
}
// 清理旧字段
delete (aiPromptSettings.value as Record<string, unknown>).activeGroupPresetId
delete (aiPromptSettings.value as Record<string, unknown>).activePrivatePresetId
}
// 迁移自定义预设中的 chatType 字段
for (const preset of customPromptPresets.value) {
const oldPreset = preset as PromptPreset & { chatType?: string }
if (oldPreset.chatType) {
delete oldPreset.chatType
}
}
}
// 初始化时执行迁移
migrateOldPresets()
return {
// state
customPromptPresets,
@@ -354,10 +388,7 @@ export const usePromptStore = defineStore(
fetchedRemotePresetIds,
// getters
allPromptPresets,
groupPresets,
privatePresets,
activeGroupPreset,
activePrivatePreset,
activePreset,
// actions
notifyAIConfigChanged,
updateAIGlobalSettings,
@@ -371,9 +402,9 @@ export const usePromptStore = defineStore(
isBuiltinPresetModified,
removePromptPreset,
duplicatePromptPreset,
setActiveGroupPreset,
setActivePrivatePreset,
setActivePreset,
getActivePresetForChatType,
getPresetsForChatType,
fetchRemotePresets,
addRemotePreset,
isRemotePresetAdded,
+21 -5
View File
@@ -6,20 +6,30 @@
// ==================== AI 提示词预设 ====================
/**
* 提示词预设适用的聊天类型
* 预设适用的聊天类型
* - 'group': 仅群聊
* - 'private': 仅私聊
* - 'common': 通用(群聊和私聊都适用)
*/
export type PromptPresetChatType = 'group' | 'private'
export type PresetApplicableType = 'group' | 'private' | 'common'
/**
* AI 提示词预设
*
* applicableTo 表示预设适用的场景:
* - 'common' 表示群聊和私聊都适用(默认)
* - 'group' 表示仅群聊
* - 'private' 表示仅私聊
*
* 后端会根据运行时的 chatType 自动处理差异化内容(如成员查询策略)。
*/
export interface PromptPreset {
id: string
name: string // 预设名称
chatType: PromptPresetChatType // 适用类型
roleDefinition: string // 角色定义(可编辑)
responseRules: string // 回答要求(可编辑)
isBuiltIn: boolean // 是否内置(内置不可删除)
applicableTo?: PresetApplicableType // 适用场景,默认 'common'
createdAt: number
updatedAt: number
}
@@ -28,7 +38,13 @@ export interface PromptPreset {
* AI 提示词配置(激活的预设)
*/
export interface AIPromptSettings {
activeGroupPresetId: string // 群聊激活的预设ID
activePrivatePresetId: string // 私聊激活的预设ID
activePresetId: string // 当前激活的预设ID
}
// ==================== 兼容旧版本 ====================
/**
* @deprecated 使用 PresetApplicableType 代替
*/
export type PromptPresetChatType = 'group' | 'private'