mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-04-23 01:39:37 +08:00
feat: 移除旧版提示词系统
This commit is contained in:
@@ -1,183 +1,217 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { PromptPreset } from '@/types/ai'
|
||||
import AIPromptEditModal from './AIPromptEditModal.vue'
|
||||
import ImportPresetModal from './ImportPresetModal.vue'
|
||||
import { usePromptStore } from '@/stores/prompt'
|
||||
import { useToast } from '@nuxt/ui/runtime/composables/useToast.js'
|
||||
|
||||
interface LegacyPromptStoreData {
|
||||
customPromptPresets?: Array<{
|
||||
id?: string
|
||||
name?: string
|
||||
systemPrompt?: string
|
||||
applicableTo?: 'common' | 'group' | 'private'
|
||||
}>
|
||||
builtinPresetOverrides?: Record<string, { name?: string; systemPrompt?: string }>
|
||||
fetchedRemotePresetIds?: string[]
|
||||
aiPromptSettings?: { activePresetId?: string }
|
||||
activeGroupPresetId?: string
|
||||
activePrivatePresetId?: string
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
// Store
|
||||
const promptStore = usePromptStore()
|
||||
const { allPromptPresets, aiPromptSettings } = storeToRefs(promptStore)
|
||||
const rawPromptStore = ref<LegacyPromptStoreData | null>(null)
|
||||
const rawPromptText = ref('')
|
||||
const parseError = ref('')
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'config-changed': []
|
||||
}>()
|
||||
/**
|
||||
* 旧版提示词已经不再参与运行,这里只保留原始数据查看与复制能力。
|
||||
*/
|
||||
function loadLegacyPromptStore() {
|
||||
const raw = localStorage.getItem('prompt')
|
||||
rawPromptText.value = raw || ''
|
||||
parseError.value = ''
|
||||
rawPromptStore.value = null
|
||||
|
||||
// 弹窗状态
|
||||
const showEditModal = ref(false)
|
||||
const showImportModal = ref(false)
|
||||
const editMode = ref<'add' | 'edit'>('add')
|
||||
const editingPreset = ref<PromptPreset | null>(null)
|
||||
if (!raw) return
|
||||
|
||||
/** 打开新增预设弹窗 */
|
||||
function openAddModal() {
|
||||
editMode.value = 'add'
|
||||
editingPreset.value = null
|
||||
showEditModal.value = true
|
||||
try {
|
||||
rawPromptStore.value = JSON.parse(raw) as LegacyPromptStoreData
|
||||
} catch (error) {
|
||||
parseError.value = String(error)
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开编辑预设弹窗 */
|
||||
function openEditModal(preset: PromptPreset) {
|
||||
editMode.value = 'edit'
|
||||
editingPreset.value = preset
|
||||
showEditModal.value = true
|
||||
const hasLegacyPromptStore = computed(() => rawPromptText.value.trim().length > 0)
|
||||
|
||||
const customPromptPresets = computed(() => {
|
||||
return Array.isArray(rawPromptStore.value?.customPromptPresets) ? rawPromptStore.value!.customPromptPresets : []
|
||||
})
|
||||
|
||||
const remotePresetIds = computed(() => {
|
||||
return Array.isArray(rawPromptStore.value?.fetchedRemotePresetIds) ? rawPromptStore.value!.fetchedRemotePresetIds : []
|
||||
})
|
||||
|
||||
const activePresetId = computed(() => {
|
||||
const settings = rawPromptStore.value?.aiPromptSettings
|
||||
return (
|
||||
settings?.activePresetId ||
|
||||
rawPromptStore.value?.activeGroupPresetId ||
|
||||
rawPromptStore.value?.activePrivatePresetId ||
|
||||
''
|
||||
)
|
||||
})
|
||||
|
||||
const formattedPromptStoreJson = computed(() => {
|
||||
if (!rawPromptText.value) return ''
|
||||
if (!rawPromptStore.value) return rawPromptText.value
|
||||
return JSON.stringify(rawPromptStore.value, null, 2)
|
||||
})
|
||||
|
||||
function getApplicableLabel(applicableTo?: 'common' | 'group' | 'private'): string {
|
||||
if (applicableTo === 'group') return t('settings.aiPrompt.legacyPrompt.groupOnly')
|
||||
if (applicableTo === 'private') return t('settings.aiPrompt.legacyPrompt.privateOnly')
|
||||
return t('settings.aiPrompt.legacyPrompt.common')
|
||||
}
|
||||
|
||||
/** 处理子弹窗保存后的回调 */
|
||||
function handleModalSaved() {
|
||||
emit('config-changed')
|
||||
async function handleCopyJson() {
|
||||
if (!formattedPromptStoreJson.value) return
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(formattedPromptStoreJson.value)
|
||||
toast.add({
|
||||
title: t('settings.aiPrompt.legacyPrompt.copySuccess'),
|
||||
color: 'primary',
|
||||
icon: 'i-heroicons-clipboard-document-check',
|
||||
duration: 2000,
|
||||
})
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
title: t('settings.aiPrompt.legacyPrompt.copyFailed'),
|
||||
description: String(error),
|
||||
color: 'error',
|
||||
icon: 'i-heroicons-x-circle',
|
||||
duration: 3000,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置当前激活的预设 */
|
||||
function setActivePreset(presetId: string) {
|
||||
promptStore.setActivePreset(presetId)
|
||||
emit('config-changed')
|
||||
}
|
||||
|
||||
/** 复制选中的预设 */
|
||||
function duplicatePreset(presetId: string) {
|
||||
promptStore.duplicatePromptPreset(presetId)
|
||||
emit('config-changed')
|
||||
}
|
||||
|
||||
/** 删除选中的预设 */
|
||||
function deletePreset(presetId: string) {
|
||||
promptStore.removePromptPreset(presetId)
|
||||
emit('config-changed')
|
||||
}
|
||||
|
||||
/** 判断预设是否处于激活状态 */
|
||||
function isActivePreset(presetId: string): boolean {
|
||||
return aiPromptSettings.value.activePresetId === presetId
|
||||
}
|
||||
|
||||
/** 导入预设后的回调 */
|
||||
function handleImportPresetAdded() {
|
||||
emit('config-changed')
|
||||
}
|
||||
onMounted(() => {
|
||||
loadLegacyPromptStore()
|
||||
})
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<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 class="rounded-xl border border-amber-200 bg-amber-50/80 p-4 dark:border-amber-900/60 dark:bg-amber-950/20">
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-100 text-amber-600 dark:bg-amber-900/40 dark:text-amber-300"
|
||||
>
|
||||
<UIcon name="i-heroicons-archive-box" class="h-4.5 w-4.5" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<h4 class="text-sm font-semibold text-amber-900 dark:text-amber-100">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.title') }}
|
||||
</h4>
|
||||
<p class="mt-1 text-sm leading-6 text-amber-800 dark:text-amber-200">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预设列表 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<UButton color="primary" size="xs" :disabled="!hasLegacyPromptStore" @click="handleCopyJson">
|
||||
<UIcon name="i-heroicons-document-duplicate" class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t('settings.aiPrompt.legacyPrompt.copyJson') }}
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!hasLegacyPromptStore"
|
||||
class="rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-gray-700 dark:bg-gray-800/50"
|
||||
>
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.emptyTitle') }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.emptyDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 这里只保留最必要的旧数据摘要,避免遗留信息继续分散注意力。 -->
|
||||
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.aiPrompt.legacyPrompt.activePreset') }}</p>
|
||||
<p class="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ activePresetId || t('settings.aiPrompt.legacyPrompt.notConfigured') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.customPresetCount') }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm font-medium text-gray-900 dark:text-white">{{ customPromptPresets.length }}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.remotePresetCount') }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm font-medium text-gray-900 dark:text-white">{{ remotePresetIds.length }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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)"
|
||||
v-if="parseError"
|
||||
class="rounded-xl border border-red-200 bg-red-50 p-4 dark:border-red-900/60 dark:bg-red-950/20"
|
||||
>
|
||||
<!-- 预设信息 -->
|
||||
<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)
|
||||
? 'bg-primary-500 text-white'
|
||||
: 'bg-gray-200 text-gray-500 dark:bg-gray-700 dark:text-gray-400',
|
||||
]"
|
||||
>
|
||||
<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>
|
||||
<p class="text-sm font-medium text-red-700 dark:text-red-300">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.parseError') }}
|
||||
</p>
|
||||
<p class="mt-1 break-all text-xs text-red-600 dark:text-red-400">{{ parseError }}</p>
|
||||
</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 v-if="customPromptPresets.length > 0" class="space-y-3">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.customPresetList') }}
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="preset in customPromptPresets"
|
||||
:key="preset.id || preset.name || preset.systemPrompt"
|
||||
class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-900"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name || '-' }}</p>
|
||||
<UBadge color="gray" variant="soft" size="xs">
|
||||
{{ getApplicableLabel(preset.applicableTo) }}
|
||||
</UBadge>
|
||||
</div>
|
||||
<p class="mt-2 line-clamp-3 whitespace-pre-wrap text-xs leading-6 text-gray-500 dark:text-gray-400">
|
||||
{{ preset.systemPrompt || t('settings.aiPrompt.legacyPrompt.noPromptContent') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 说明文字 -->
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.aiPrompt.presets.description') }}
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.rawJsonTitle') }}
|
||||
</h4>
|
||||
<span class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t('settings.aiPrompt.legacyPrompt.rawJsonHint') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/70">
|
||||
<pre
|
||||
class="max-h-[360px] overflow-auto whitespace-pre-wrap break-all text-xs leading-6 text-gray-600 dark:text-gray-300"
|
||||
>{{ formattedPromptStoreJson }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 编辑/添加弹窗 -->
|
||||
<AIPromptEditModal v-model:open="showEditModal" :mode="editMode" :preset="editingPreset" @saved="handleModalSaved" />
|
||||
|
||||
<!-- 导入预设弹窗 -->
|
||||
<ImportPresetModal v-model:open="showImportModal" @preset-added="handleImportPresetAdded" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user