mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-28 01:57:25 +08:00
feat: 重构预设词系统,支持通用预设词
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,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
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -182,18 +182,14 @@
|
||||
},
|
||||
"presets": {
|
||||
"title": "系统提示词",
|
||||
"import": "导入预设"
|
||||
},
|
||||
"group": {
|
||||
"title": "群聊系统提示词",
|
||||
"add": "添加"
|
||||
},
|
||||
"private": {
|
||||
"title": "私聊系统提示词",
|
||||
"add": "添加"
|
||||
"add": "添加预设",
|
||||
"import": "导入预设",
|
||||
"description": "提示词同时用于群聊和私聊分析,系统会自动根据分析类型调整相关内容"
|
||||
},
|
||||
"preset": {
|
||||
"builtIn": "内置",
|
||||
"groupOnly": "仅群聊",
|
||||
"privateOnly": "仅私聊",
|
||||
"view": "查看",
|
||||
"edit": "编辑",
|
||||
"copy": "复制",
|
||||
|
||||
@@ -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
@@ -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
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user