feat: 移除旧版提示词系统

This commit is contained in:
digua
2026-03-16 00:24:39 +08:00
committed by digua
parent c26594d8af
commit 4b10cf21dd
22 changed files with 308 additions and 1013 deletions

View File

@@ -17,7 +17,7 @@ import {
type Usage as PiUsage, type Usage as PiUsage,
} from '@mariozechner/pi-ai' } from '@mariozechner/pi-ai'
import type { AgentConfig, AgentStreamChunk, AgentResult, PromptConfig, TokenUsage, SkillContext } from './types' import type { AgentConfig, AgentStreamChunk, AgentResult, TokenUsage, SkillContext } from './types'
import type { AssistantConfig } from '../assistant/types' import type { AssistantConfig } from '../assistant/types'
import { buildSystemPrompt } from './prompt-builder' import { buildSystemPrompt } from './prompt-builder'
import { extractThinkingContent, stripToolCallTags } from './content-parser' import { extractThinkingContent, stripToolCallTags } from './content-parser'
@@ -26,7 +26,7 @@ import { AgentEventHandler } from './event-handler'
type SimpleHistoryMessage = { role: 'user' | 'assistant'; content: string } type SimpleHistoryMessage = { role: 'user' | 'assistant'; content: string }
// Re-export types for external consumers // Re-export types for external consumers
export type { AgentConfig, AgentStreamChunk, AgentResult, PromptConfig, TokenUsage, AgentRuntimeStatus } from './types' export type { AgentConfig, AgentStreamChunk, AgentResult, TokenUsage, AgentRuntimeStatus } from './types'
/** /**
* Agent 执行器类 * Agent 执行器类
@@ -39,7 +39,6 @@ export class Agent {
private apiKey: string private apiKey: string
private abortSignal?: AbortSignal private abortSignal?: AbortSignal
private chatType: 'group' | 'private' = 'group' private chatType: 'group' | 'private' = 'group'
private promptConfig?: PromptConfig
private assistantConfig?: AssistantConfig private assistantConfig?: AssistantConfig
private skillCtx?: SkillContext private skillCtx?: SkillContext
private locale: string = 'zh-CN' private locale: string = 'zh-CN'
@@ -50,7 +49,6 @@ export class Agent {
apiKey: string, apiKey: string,
config: AgentConfig = {}, config: AgentConfig = {},
chatType: 'group' | 'private' = 'group', chatType: 'group' | 'private' = 'group',
promptConfig?: PromptConfig,
locale: string = 'zh-CN', locale: string = 'zh-CN',
assistantConfig?: AssistantConfig, assistantConfig?: AssistantConfig,
skillCtx?: SkillContext skillCtx?: SkillContext
@@ -60,7 +58,6 @@ export class Agent {
this.apiKey = apiKey this.apiKey = apiKey
this.abortSignal = config.abortSignal this.abortSignal = config.abortSignal
this.chatType = chatType this.chatType = chatType
this.promptConfig = promptConfig
this.assistantConfig = assistantConfig this.assistantConfig = assistantConfig
this.skillCtx = skillCtx this.skillCtx = skillCtx
this.locale = locale this.locale = locale
@@ -83,13 +80,9 @@ export class Agent {
const maxToolRounds = Math.max(0, this.config.maxToolRounds ?? 0) const maxToolRounds = Math.max(0, this.config.maxToolRounds ?? 0)
const effectivePromptConfig: PromptConfig | undefined = this.assistantConfig
? { systemPrompt: this.assistantConfig.systemPrompt }
: this.promptConfig
const systemPrompt = buildSystemPrompt( const systemPrompt = buildSystemPrompt(
this.chatType, this.chatType,
effectivePromptConfig, this.assistantConfig?.systemPrompt,
this.context.ownerInfo, this.context.ownerInfo,
this.locale, this.locale,
this.skillCtx, this.skillCtx,
@@ -312,7 +305,6 @@ export async function runAgent(
context: ToolContext, context: ToolContext,
config?: AgentConfig, config?: AgentConfig,
chatType?: 'group' | 'private', chatType?: 'group' | 'private',
promptConfig?: PromptConfig,
locale?: string, locale?: string,
assistantConfig?: AssistantConfig, assistantConfig?: AssistantConfig,
skillCtx?: SkillContext skillCtx?: SkillContext
@@ -320,17 +312,7 @@ export async function runAgent(
const activeConfig = getActiveConfig() const activeConfig = getActiveConfig()
if (!activeConfig) throw new Error('LLM service not configured') if (!activeConfig) throw new Error('LLM service not configured')
const piModel = buildPiModel(activeConfig) const piModel = buildPiModel(activeConfig)
const agent = new Agent( const agent = new Agent(context, piModel, activeConfig.apiKey, config, chatType, locale, assistantConfig, skillCtx)
context,
piModel,
activeConfig.apiKey,
config,
chatType,
promptConfig,
locale,
assistantConfig,
skillCtx
)
return agent.execute(userMessage) return agent.execute(userMessage)
} }
@@ -343,7 +325,6 @@ export async function runAgentStream(
onChunk: (chunk: AgentStreamChunk) => void, onChunk: (chunk: AgentStreamChunk) => void,
config?: AgentConfig, config?: AgentConfig,
chatType?: 'group' | 'private', chatType?: 'group' | 'private',
promptConfig?: PromptConfig,
locale?: string, locale?: string,
assistantConfig?: AssistantConfig, assistantConfig?: AssistantConfig,
skillCtx?: SkillContext skillCtx?: SkillContext
@@ -351,16 +332,6 @@ export async function runAgentStream(
const activeConfig = getActiveConfig() const activeConfig = getActiveConfig()
if (!activeConfig) throw new Error('LLM service not configured') if (!activeConfig) throw new Error('LLM service not configured')
const piModel = buildPiModel(activeConfig) const piModel = buildPiModel(activeConfig)
const agent = new Agent( const agent = new Agent(context, piModel, activeConfig.apiKey, config, chatType, locale, assistantConfig, skillCtx)
context,
piModel,
activeConfig.apiKey,
config,
chatType,
promptConfig,
locale,
assistantConfig,
skillCtx
)
return agent.executeStream(userMessage, onChunk) return agent.executeStream(userMessage, onChunk)
} }

View File

@@ -4,7 +4,7 @@
import { t as i18nT } from '../../i18n' import { t as i18nT } from '../../i18n'
import type { OwnerInfo } from '../tools/types' import type { OwnerInfo } from '../tools/types'
import type { PromptConfig, SkillContext } from './types' import type { SkillContext } from './types'
import type { ToolContext } from '../tools/types' import type { ToolContext } from '../tools/types'
function agentT(key: string, locale: string, options?: Record<string, unknown>): string { function agentT(key: string, locale: string, options?: Record<string, unknown>): string {
@@ -80,20 +80,19 @@ function getFallbackRoleDefinition(chatType: 'group' | 'private', locale: string
/** /**
* 构建完整的系统提示词 * 构建完整的系统提示词
* *
* 提示词配置主要来自前端 src/config/prompts.ts通过 promptConfig 参数传递 * 系统提示词优先使用当前助手配置;若助手不存在,再退回内置默认角色定义
* Fallback 仅在前端未传递配置时使用。
* *
* 最终格式:{用户系统提示词}\n\n{系统锁定段(日期/owner/时间参数/通用指引)} * 最终格式:{助手系统提示词}\n\n{系统锁定段(日期/owner/时间参数/通用指引)}
*/ */
export function buildSystemPrompt( export function buildSystemPrompt(
chatType: 'group' | 'private' = 'group', chatType: 'group' | 'private' = 'group',
promptConfig?: PromptConfig, assistantSystemPrompt?: string,
ownerInfo?: OwnerInfo, ownerInfo?: OwnerInfo,
locale: string = 'zh-CN', locale: string = 'zh-CN',
skillCtx?: SkillContext, skillCtx?: SkillContext,
mentionedMembers?: ToolContext['mentionedMembers'] mentionedMembers?: ToolContext['mentionedMembers']
): string { ): string {
const systemPrompt = promptConfig?.systemPrompt || getFallbackRoleDefinition(chatType, locale) const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(chatType, locale)
const lockedSection = getLockedPromptSection(chatType, ownerInfo, locale, mentionedMembers) const lockedSection = getLockedPromptSection(chatType, ownerInfo, locale, mentionedMembers)
let skillSection = '' let skillSection = ''

View File

@@ -60,14 +60,6 @@ export interface AgentResult {
totalUsage?: TokenUsage totalUsage?: TokenUsage
} }
/**
* 用户自定义提示词配置
*/
export interface PromptConfig {
/** 系统提示词(角色定义 + 回答要求,统一为单一字段) */
systemPrompt: string
}
/** /**
* 技能上下文(传递给 prompt-builder * 技能上下文(传递给 prompt-builder
* 手动选择和 AI 自选两种模式互斥 * 手动选择和 AI 自选两种模式互斥

View File

@@ -15,6 +15,5 @@ export {
getBuiltinCatalog, getBuiltinCatalog,
importAssistant, importAssistant,
reimportAssistant, reimportAssistant,
backupOldPromptPresets,
} from './manager' } from './manager'
export { getBuiltinSqlToolCatalog, getBuiltinTsToolNames } from './builtinSqlTools' export { getBuiltinSqlToolCatalog, getBuiltinTsToolNames } from './builtinSqlTools'

View File

@@ -341,39 +341,6 @@ export function resetAssistant(id: string): AssistantSaveResult {
return saveAssistantToDisk(resetConfig) return saveAssistantToDisk(resetConfig)
} }
// ==================== 提示词预设迁移 ====================
/**
* 备份旧的提示词预设数据到 data/backup 目录
*/
export function backupOldPromptPresets(data: {
customPresets?: unknown[]
builtinOverrides?: Record<string, unknown>
remotePresetIds?: string[]
}): { success: boolean; filePath?: string; error?: string } {
try {
const backupDir = path.join(getAiDataDir(), '..', 'backup')
ensureDir(backupDir)
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
const filePath = path.join(backupDir, `prompt-presets-${timestamp}.json`)
const backupContent = {
backupTime: new Date().toISOString(),
description: 'ChatLab 旧提示词预设系统备份(已被多助手系统替代)',
...data,
}
writeJsonFile(filePath, backupContent)
aiLogger.info('AssistantManager', 'Old prompt presets backed up', { filePath })
return { success: true, filePath }
} catch (error) {
aiLogger.error('AssistantManager', 'Failed to backup prompt presets', { error: String(error) })
return { success: false, error: String(error) }
}
}
// ==================== 内部工具函数 ==================== // ==================== 内部工具函数 ====================
function ensureInitialized(): void { function ensureInitialized(): void {

View File

@@ -7,7 +7,7 @@ import * as llm from '../ai/llm'
import * as rag from '../ai/rag' import * as rag from '../ai/rag'
import { aiLogger, setDebugMode } from '../ai/logger' import { aiLogger, setDebugMode } from '../ai/logger'
import { getLogsDir } from '../paths' import { getLogsDir } from '../paths'
import { Agent, type AgentStreamChunk, type PromptConfig, type SkillContext } from '../ai/agent' import { Agent, type AgentStreamChunk, type SkillContext } from '../ai/agent'
import { getActiveConfig, buildPiModel } from '../ai/llm' import { getActiveConfig, buildPiModel } from '../ai/llm'
import * as assistantManager from '../ai/assistant' import * as assistantManager from '../ai/assistant'
import type { AssistantConfig } from '../ai/assistant/types' import type { AssistantConfig } from '../ai/assistant/types'
@@ -679,21 +679,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
} }
}) })
ipcMain.handle(
'assistant:backupOldPresets',
async (
_,
data: { customPresets?: unknown[]; builtinOverrides?: Record<string, unknown>; remotePresetIds?: string[] }
) => {
try {
return assistantManager.backupOldPromptPresets(data)
} catch (error) {
console.error('Failed to backup old presets:', error)
return { success: false, error: String(error) }
}
}
)
// ==================== 技能管理 API ==================== // ==================== 技能管理 API ====================
ipcMain.handle('skill:getAll', async () => { ipcMain.handle('skill:getAll', async () => {
@@ -775,7 +760,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
* Agent 会自动调用工具并返回最终结果 * Agent 会自动调用工具并返回最终结果
* Agent 通过 context.conversationId 从 SQLite 读取对话历史(数据流倒置) * Agent 通过 context.conversationId 从 SQLite 读取对话历史(数据流倒置)
* @param chatType 聊天类型('group' | 'private' * @param chatType 聊天类型('group' | 'private'
* @param promptConfig 用户自定义提示词配置(可选)
* @param locale 语言设置(可选,默认 'zh-CN' * @param locale 语言设置(可选,默认 'zh-CN'
* @param maxHistoryRounds 前端用户配置的最大历史轮数(可选,每轮 = user + assistant = 2 条) * @param maxHistoryRounds 前端用户配置的最大历史轮数(可选,每轮 = user + assistant = 2 条)
* @param assistantId 助手 ID可选传入时从 AssistantManager 获取配置) * @param assistantId 助手 ID可选传入时从 AssistantManager 获取配置)
@@ -788,7 +772,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
userMessage: string, userMessage: string,
context: ToolContext, context: ToolContext,
chatType?: 'group' | 'private', chatType?: 'group' | 'private',
promptConfig?: PromptConfig,
locale?: string, locale?: string,
maxHistoryRounds?: number, maxHistoryRounds?: number,
assistantId?: string, assistantId?: string,
@@ -800,7 +783,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
sessionId: context.sessionId, sessionId: context.sessionId,
conversationId: context.conversationId, conversationId: context.conversationId,
chatType: chatType ?? 'group', chatType: chatType ?? 'group',
hasPromptConfig: !!promptConfig,
assistantId: assistantId ?? '(none)', assistantId: assistantId ?? '(none)',
skillId: skillId ?? '(none)', skillId: skillId ?? '(none)',
enableAutoSkill: enableAutoSkill ?? false, enableAutoSkill: enableAutoSkill ?? false,
@@ -826,7 +808,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
maxHistoryRounds: maxHistoryRounds ?? '(default)', maxHistoryRounds: maxHistoryRounds ?? '(default)',
maxMessagesLimit: context.maxMessagesLimit, maxMessagesLimit: context.maxMessagesLimit,
hasTimeFilter: !!context.timeFilter, hasTimeFilter: !!context.timeFilter,
hasCustomPrompt: !!promptConfig,
mentionedMembersCount: context.mentionedMembers?.length ?? 0, mentionedMembersCount: context.mentionedMembers?.length ?? 0,
preprocess: pp preprocess: pp
? { ? {
@@ -839,13 +820,15 @@ export function registerAIHandlers({ win }: IpcContext): void {
: '(disabled)', : '(disabled)',
}) })
// 如果指定了 assistantId从 AssistantManager 加载助手配置 // 提示词系统已退场,主流程统一从助手配置获取 systemPrompt。
let assistantConfig: AssistantConfig | undefined // 若前端没有显式传 assistantId则退回 general 助手兜底。
if (assistantId) { let resolvedAssistantId = assistantId || 'general'
assistantConfig = assistantManager.getAssistantConfig(assistantId) ?? undefined let assistantConfig: AssistantConfig | undefined =
if (!assistantConfig) { assistantManager.getAssistantConfig(resolvedAssistantId) ?? undefined
aiLogger.warn('IPC', `Assistant not found: ${assistantId}, falling back to default`) if (!assistantConfig && resolvedAssistantId !== 'general') {
} aiLogger.warn('IPC', `Assistant not found: ${resolvedAssistantId}, falling back to general`)
resolvedAssistantId = 'general'
assistantConfig = assistantManager.getAssistantConfig('general') ?? undefined
} }
// 构建技能上下文 // 构建技能上下文
@@ -872,7 +855,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
activeAIConfig.apiKey, activeAIConfig.apiKey,
{ abortSignal: abortController.signal, contextHistoryLimit }, { abortSignal: abortController.signal, contextHistoryLimit },
chatType ?? 'group', chatType ?? 'group',
promptConfig,
locale ?? 'zh-CN', locale ?? 'zh-CN',
assistantConfig, assistantConfig,
skillCtx skillCtx

View File

@@ -182,11 +182,6 @@ export interface EmbeddingServiceConfigDisplay {
updatedAt: number updatedAt: number
} }
// 用户自定义提示词配置
export interface PromptConfig {
systemPrompt: string
}
// ==================== AI API ==================== // ==================== AI API ====================
export const aiApi = { export const aiApi = {
@@ -759,14 +754,6 @@ export const assistantApi = {
reimportAssistant: (id: string): Promise<{ success: boolean; error?: string }> => { reimportAssistant: (id: string): Promise<{ success: boolean; error?: string }> => {
return ipcRenderer.invoke('assistant:reimport', id) return ipcRenderer.invoke('assistant:reimport', id)
}, },
backupOldPresets: (data: {
customPresets?: unknown[]
builtinOverrides?: Record<string, unknown>
remotePresetIds?: string[]
}): Promise<{ success: boolean; filePath?: string; error?: string }> => {
return ipcRenderer.invoke('assistant:backupOldPresets', data)
},
} }
// ==================== Skill API ==================== // ==================== Skill API ====================
@@ -838,7 +825,6 @@ export const agentApi = {
* 执行 Agent 对话(流式) * 执行 Agent 对话(流式)
* Agent 通过 context.conversationId 从后端 SQLite 读取对话历史 * Agent 通过 context.conversationId 从后端 SQLite 读取对话历史
* @param chatType 聊天类型('group' | 'private' * @param chatType 聊天类型('group' | 'private'
* @param promptConfig 用户自定义提示词配置(可选)
* @param locale 语言设置(可选,默认 'zh-CN' * @param locale 语言设置(可选,默认 'zh-CN'
* @param maxHistoryRounds 最大历史轮数(可选,每轮 = user + assistant = 2 条) * @param maxHistoryRounds 最大历史轮数(可选,每轮 = user + assistant = 2 条)
* @returns 返回 { requestId, promise }requestId 可用于中止请求 * @returns 返回 { requestId, promise }requestId 可用于中止请求
@@ -848,7 +834,6 @@ export const agentApi = {
context: ToolContext, context: ToolContext,
onChunk?: (chunk: AgentStreamChunk) => void, onChunk?: (chunk: AgentStreamChunk) => void,
chatType?: 'group' | 'private', chatType?: 'group' | 'private',
promptConfig?: PromptConfig,
locale?: string, locale?: string,
maxHistoryRounds?: number, maxHistoryRounds?: number,
assistantId?: string, assistantId?: string,
@@ -893,9 +878,7 @@ export const agentApi = {
'conversationId:', 'conversationId:',
sanitizedContext.conversationId ?? 'none', sanitizedContext.conversationId ?? 'none',
'chatType:', 'chatType:',
chatType ?? 'group', chatType ?? 'group'
'hasPromptConfig:',
!!promptConfig
) )
const promise = new Promise<{ success: boolean; result?: AgentResult; error?: string }>((resolve) => { const promise = new Promise<{ success: boolean; result?: AgentResult; error?: string }>((resolve) => {
@@ -939,7 +922,6 @@ export const agentApi = {
userMessage, userMessage,
sanitizedContext, sanitizedContext,
chatType, chatType,
promptConfig,
locale, locale,
maxHistoryRounds, maxHistoryRounds,
assistantId, assistantId,

View File

@@ -666,18 +666,12 @@ interface ToolContext {
preprocessConfig?: PreprocessConfig preprocessConfig?: PreprocessConfig
} }
// 用户自定义提示词配置
interface PromptConfig {
systemPrompt: string
}
interface AgentApi { interface AgentApi {
runStream: ( runStream: (
userMessage: string, userMessage: string,
context: ToolContext, context: ToolContext,
onChunk?: (chunk: AgentStreamChunk) => void, onChunk?: (chunk: AgentStreamChunk) => void,
chatType?: 'group' | 'private', chatType?: 'group' | 'private',
promptConfig?: PromptConfig,
locale?: string, locale?: string,
maxHistoryRounds?: number, maxHistoryRounds?: number,
assistantId?: string, assistantId?: string,
@@ -745,11 +739,6 @@ interface AssistantApi {
getBuiltinTsToolNames: () => Promise<string[]> getBuiltinTsToolNames: () => Promise<string[]>
importAssistant: (builtinId: string) => Promise<{ success: boolean; error?: string }> importAssistant: (builtinId: string) => Promise<{ success: boolean; error?: string }>
reimportAssistant: (id: string) => Promise<{ success: boolean; error?: string }> reimportAssistant: (id: string) => Promise<{ success: boolean; error?: string }>
backupOldPresets: (data: {
customPresets?: unknown[]
builtinOverrides?: Record<string, unknown>
remotePresetIds?: string[]
}) => Promise<{ success: boolean; filePath?: string; error?: string }>
} }
// ==================== 技能管理 ==================== // ==================== 技能管理 ====================
@@ -1029,7 +1018,6 @@ export {
ToolContext, ToolContext,
DesensitizeRule, DesensitizeRule,
PreprocessConfig, PreprocessConfig,
PromptConfig,
TokenUsage, TokenUsage,
CacheDirectoryInfo, CacheDirectoryInfo,
CacheInfo, CacheInfo,

View File

@@ -601,11 +601,8 @@ watch(
<!-- 底部状态栏 --> <!-- 底部状态栏 -->
<ChatStatusBar <ChatStatusBar
:chat-type="currentChatType"
:session-token-usage="sessionTokenUsage" :session-token-usage="sessionTokenUsage"
:agent-status="agentStatus" :agent-status="agentStatus"
:has-l-l-m-config="hasLLMConfig"
:is-checking-config="isCheckingConfig"
:current-conversation-id="currentConversationId" :current-conversation-id="currentConversationId"
/> />
</div> </div>

View File

@@ -33,7 +33,6 @@ onMounted(async () => {
if (!isLoaded.value) { if (!isLoaded.value) {
await assistantStore.loadAssistants() await assistantStore.loadAssistants()
} }
assistantStore.migrateOldPromptPresets()
}) })
function handleSelect(id: string) { function handleSelect(id: string) {

View File

@@ -9,40 +9,24 @@ import { useLLMStore } from '@/stores/llm'
import { exportConversation, type ExportFormat } from '@/utils/conversationExport' import { exportConversation, type ExportFormat } from '@/utils/conversationExport'
import type { AgentRuntimeStatus } from '@electron/shared/types' import type { AgentRuntimeStatus } from '@electron/shared/types'
const { t, locale } = useI18n() const { t } = useI18n()
const toast = useToast() const toast = useToast()
const router = useRouter() const router = useRouter()
// Props // Props
const props = defineProps<{ const props = defineProps<{
chatType: 'group' | 'private'
sessionTokenUsage: { totalTokens: number } sessionTokenUsage: { totalTokens: number }
agentStatus?: AgentRuntimeStatus | null agentStatus?: AgentRuntimeStatus | null
hasLLMConfig: boolean
isCheckingConfig: boolean
currentConversationId?: string | null currentConversationId?: string | null
}>() }>()
// Store // Store
const promptStore = usePromptStore() const promptStore = usePromptStore()
const llmStore = useLLMStore() const llmStore = useLLMStore()
const { aiPromptSettings, activePreset, aiGlobalSettings } = storeToRefs(promptStore) const { aiGlobalSettings } = storeToRefs(promptStore)
const { configs, activeConfig, isLoading: isLoadingLLM } = storeToRefs(llmStore) const { configs, activeConfig, isLoading: isLoadingLLM } = storeToRefs(llmStore)
// 当前类型对应的预设列表(根据 applicableTo 过滤)
const currentPresets = computed(() => promptStore.getPresetsForChatType(props.chatType))
// 当前激活的预设 ID
const currentActivePresetId = computed(() => aiPromptSettings.value.activePresetId)
// 当前激活的预设(如果当前激活的预设不适用于当前类型,使用第一个可用预设)
const currentActivePreset = computed(() => {
const activeInList = currentPresets.value.find((p) => p.id === currentActivePresetId.value)
return activeInList || activePreset.value
})
// 下拉菜单状态 // 下拉菜单状态
const isPresetPopoverOpen = ref(false)
const isModelPopoverOpen = ref(false) const isModelPopoverOpen = ref(false)
const isOpeningLog = ref(false) const isOpeningLog = ref(false)
@@ -82,11 +66,6 @@ function formatNumber(value: number): string {
return new Intl.NumberFormat().format(Math.max(0, Math.round(value))) return new Intl.NumberFormat().format(Math.max(0, Math.round(value)))
} }
const contextTokensText = computed(() => {
if (!props.agentStatus) return '--'
return formatCompactNumber(props.agentStatus.contextTokens)
})
function formatCompactNumber(value: number): string { function formatCompactNumber(value: number): string {
const num = Math.max(0, Math.round(value)) const num = Math.max(0, Math.round(value))
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1).replace(/\.0$/, '')}M` if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
@@ -106,17 +85,6 @@ const agentCompactTitle = computed(() => {
].join('\n') ].join('\n')
}) })
// 设置激活预设
function setActivePreset(presetId: string) {
promptStore.setActivePreset(presetId)
isPresetPopoverOpen.value = false
}
function openPresetSettings() {
isPresetPopoverOpen.value = false
router.push({ name: 'settings', query: { tab: 'ai', subTab: 'preset' } })
}
function openChatSettings() { function openChatSettings() {
router.push({ name: 'settings', query: { tab: 'ai', subTab: 'chat' } }) router.push({ name: 'settings', query: { tab: 'ai', subTab: 'chat' } })
} }
@@ -250,66 +218,11 @@ async function openAiLogFile() {
</script> </script>
<template> <template>
<div class="flex items-center justify-between"> <!-- 抬高状态栏与模型下拉层级避免被输入框上方的快捷提示遮住 -->
<!-- 左侧预设选择器 + 模型切换器 --> <div class="relative z-20 flex items-center justify-between">
<!-- 左侧模型切换器 -->
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<UPopover v-model:open="isPresetPopoverOpen" :ui="{ content: 'p-0' }"> <UPopover v-model:open="isModelPopoverOpen" :ui="{ content: 'z-[80] p-0' }">
<button
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-300"
>
<UIcon name="i-heroicons-chat-bubble-bottom-center-text" class="h-3.5 w-3.5" />
<span class="max-w-[120px] truncate">
{{ currentActivePreset?.name || t('ai.chat.statusBar.preset.default') }}
</span>
<UIcon name="i-heroicons-chevron-down" class="h-3 w-3" />
</button>
<template #content>
<div class="w-48 py-1">
<div class="px-3 py-1.5 text-xs font-medium text-gray-400 dark:text-gray-500">
{{
chatType === 'group'
? t('ai.chat.statusBar.preset.groupTitle')
: t('ai.chat.statusBar.preset.privateTitle')
}}
</div>
<button
v-for="preset in currentPresets"
:key="preset.id"
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
:class="[
preset.id === currentActivePresetId
? 'text-pink-600 dark:text-pink-400'
: 'text-gray-700 dark:text-gray-300',
]"
@click="setActivePreset(preset.id)"
>
<UIcon
:name="
preset.id === currentActivePresetId ? 'i-heroicons-check-circle-solid' : 'i-heroicons-document-text'
"
class="h-4 w-4 shrink-0"
:class="[preset.id === currentActivePresetId ? 'text-pink-500' : 'text-gray-400']"
/>
<span class="truncate">{{ preset.name }}</span>
</button>
<!-- 分隔线 -->
<div class="my-1 border-t border-gray-200 dark:border-gray-700" />
<!-- 管理预设按钮 -->
<button
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-300"
@click="openPresetSettings"
>
<UIcon name="i-heroicons-cog-6-tooth" class="h-4 w-4 shrink-0" />
<span>{{ t('ai.chat.statusBar.preset.manage') }}</span>
</button>
</div>
</template>
</UPopover>
<!-- 模型切换器 -->
<UPopover v-model:open="isModelPopoverOpen" :ui="{ content: 'p-0' }">
<button <button
class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-300" class="flex items-center gap-1.5 rounded-md px-2 py-1 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-300"
:disabled="isLoadingLLM" :disabled="isLoadingLLM"
@@ -373,14 +286,13 @@ async function openAiLogFile() {
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<div <div
v-if="agentStatus" v-if="agentStatus"
class="hidden shrink-0 items-center gap-1 rounded-md border border-gray-200 bg-white px-1.5 py-1 text-xs dark:border-gray-700 dark:bg-gray-900 lg:flex" class="hidden shrink-0 items-center gap-1 rounded-lg bg-gray-50/90 px-1.5 py-1 text-xs shadow-[inset_0_0_0_1px_rgba(255,255,255,0.35)] dark:bg-gray-800/70 dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.04)] lg:flex"
:title="agentCompactTitle" :title="agentCompactTitle"
> >
<!-- 主栏只展示阶段context token 放进 tooltip避免和累计 token 混淆 -->
<span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass"> <span class="rounded px-1 py-0.5 text-[10px] font-medium" :class="agentPhaseClass">
{{ agentPhaseShortText }} {{ agentPhaseShortText }}
</span> </span>
<UIcon name="i-heroicons-document-text" class="h-3 w-3 text-gray-400 dark:text-gray-500" />
<span class="text-[10px] text-gray-500 dark:text-gray-400">{{ contextTokensText }}</span>
</div> </div>
<div <div

View File

@@ -6,7 +6,7 @@
"aiConfig": "Chat Model", "aiConfig": "Chat Model",
"aiRAG": "Vector Model", "aiRAG": "Vector Model",
"aiPrompt": "Chat Config", "aiPrompt": "Chat Config",
"aiPreset": "Prompts", "aiPreset": "Legacy Prompts",
"aiPreprocess": "Preprocess", "aiPreprocess": "Preprocess",
"dataManage": "Data Management", "dataManage": "Data Management",
"storage": "Storage", "storage": "Storage",
@@ -257,6 +257,27 @@
"noDescription": "No description", "noDescription": "No description",
"fetchingContent": "Loading content...", "fetchingContent": "Loading content...",
"fetchError": "Failed to load content" "fetchError": "Failed to load content"
},
"legacyPrompt": {
"title": "Legacy Prompts",
"description": "The old prompt system no longer affects AI conversations. This section is kept only for viewing and copying legacy configs during the transition.",
"copyJson": "Copy JSON",
"emptyTitle": "No legacy prompt data found",
"emptyDescription": "There is no legacy prompt config available to view on this device.",
"activePreset": "Previously Active Preset",
"notConfigured": "Not recorded",
"customPresetCount": "Custom Presets",
"remotePresetCount": "Imported Remote Presets",
"parseError": "Failed to parse legacy prompt data, but you can still copy the raw JSON first.",
"customPresetList": "Custom Presets",
"noPromptContent": "No prompt content recorded",
"rawJsonTitle": "Raw Config JSON",
"rawJsonHint": "View and copy only",
"copySuccess": "JSON copied to clipboard",
"copyFailed": "Copy failed",
"groupOnly": "Group Only",
"privateOnly": "Private Only",
"common": "Common"
} }
}, },
"about": { "about": {

View File

@@ -6,7 +6,7 @@
"aiConfig": "チャットモデル", "aiConfig": "チャットモデル",
"aiRAG": "埋め込みモデル", "aiRAG": "埋め込みモデル",
"aiPrompt": "チャット設定", "aiPrompt": "チャット設定",
"aiPreset": "プロンプト設定", "aiPreset": "旧版プロンプト",
"aiPreprocess": "前処理", "aiPreprocess": "前処理",
"dataManage": "データ管理", "dataManage": "データ管理",
"storage": "ストレージ管理", "storage": "ストレージ管理",
@@ -257,6 +257,27 @@
"noDescription": "説明がありません", "noDescription": "説明がありません",
"fetchingContent": "コンテンツを読み込み中...", "fetchingContent": "コンテンツを読み込み中...",
"fetchError": "コンテンツの読み込みに失敗しました" "fetchError": "コンテンツの読み込みに失敗しました"
},
"legacyPrompt": {
"title": "旧版プロンプト",
"description": "旧プロンプトシステムはすでに AI 会話の実行には使われません。移行期間中に旧設定を確認・コピーするためだけの入口です。",
"copyJson": "JSON をコピー",
"emptyTitle": "旧版プロンプトデータが見つかりません",
"emptyDescription": "この端末には確認できる旧版プロンプト設定がありません。",
"activePreset": "以前の有効プリセット",
"notConfigured": "記録なし",
"customPresetCount": "カスタムプリセット数",
"remotePresetCount": "リモート導入数",
"parseError": "旧版プロンプトデータの解析に失敗しましたが、先に生の JSON をコピーできます。",
"customPresetList": "カスタムプリセット",
"noPromptContent": "プロンプト内容の記録がありません",
"rawJsonTitle": "元の設定 JSON",
"rawJsonHint": "閲覧とコピー専用",
"copySuccess": "JSON をクリップボードにコピーしました",
"copyFailed": "コピーに失敗しました",
"groupOnly": "グループのみ",
"privateOnly": "個人のみ",
"common": "共通"
} }
}, },
"about": { "about": {

View File

@@ -6,7 +6,7 @@
"aiConfig": "对话模型", "aiConfig": "对话模型",
"aiRAG": "向量模型", "aiRAG": "向量模型",
"aiPrompt": "对话配置", "aiPrompt": "对话配置",
"aiPreset": "提示词配置", "aiPreset": "旧版提示词",
"aiPreprocess": "预处理", "aiPreprocess": "预处理",
"dataManage": "数据管理", "dataManage": "数据管理",
"storage": "存储管理", "storage": "存储管理",
@@ -257,6 +257,27 @@
"noDescription": "暂无描述", "noDescription": "暂无描述",
"fetchingContent": "正在加载内容...", "fetchingContent": "正在加载内容...",
"fetchError": "加载内容失败" "fetchError": "加载内容失败"
},
"legacyPrompt": {
"title": "旧版提示词",
"description": "提示词系统已不再参与 AI 对话运行。这里仅保留旧配置的查看与复制入口,方便你在过渡期手动迁移。",
"copyJson": "复制 JSON",
"emptyTitle": "未发现旧版提示词数据",
"emptyDescription": "当前设备上没有可查看的旧版提示词配置。",
"activePreset": "历史激活预设",
"notConfigured": "未记录",
"customPresetCount": "自定义预设数",
"remotePresetCount": "远程导入数",
"parseError": "旧版提示词数据解析失败,但你仍然可以先复制原始 JSON。",
"customPresetList": "自定义预设",
"noPromptContent": "未记录提示词内容",
"rawJsonTitle": "原始配置 JSON",
"rawJsonHint": "仅供查看与复制",
"copySuccess": "JSON 已复制到剪贴板",
"copyFailed": "复制失败",
"groupOnly": "仅群聊",
"privateOnly": "仅私聊",
"common": "通用"
} }
}, },
"about": { "about": {

View File

@@ -6,7 +6,7 @@
"aiConfig": "對話模型", "aiConfig": "對話模型",
"aiRAG": "向量模型", "aiRAG": "向量模型",
"aiPrompt": "聊天設定", "aiPrompt": "聊天設定",
"aiPreset": "提示詞設定", "aiPreset": "舊版提示詞",
"aiPreprocess": "前處理", "aiPreprocess": "前處理",
"dataManage": "資料管理", "dataManage": "資料管理",
"storage": "儲存管理", "storage": "儲存管理",
@@ -257,6 +257,27 @@
"noDescription": "暫無描述", "noDescription": "暫無描述",
"fetchingContent": "正在載入內容...", "fetchingContent": "正在載入內容...",
"fetchError": "載入內容失敗" "fetchError": "載入內容失敗"
},
"legacyPrompt": {
"title": "舊版提示詞",
"description": "提示詞系統已不再參與 AI 對話執行。這裡僅保留舊設定的查看與複製入口,方便你在過渡期手動遷移。",
"copyJson": "複製 JSON",
"emptyTitle": "未發現舊版提示詞資料",
"emptyDescription": "目前這台裝置上沒有可查看的舊版提示詞設定。",
"activePreset": "歷史啟用預設",
"notConfigured": "未記錄",
"customPresetCount": "自訂預設數",
"remotePresetCount": "遠端匯入數",
"parseError": "舊版提示詞資料解析失敗,但你仍可先複製原始 JSON。",
"customPresetList": "自訂預設",
"noPromptContent": "未記錄提示詞內容",
"rawJsonTitle": "原始設定 JSON",
"rawJsonHint": "僅供查看與複製",
"copySuccess": "JSON 已複製到剪貼簿",
"copyFailed": "複製失敗",
"groupOnly": "僅群聊",
"privateOnly": "僅私聊",
"common": "通用"
} }
}, },
"about": { "about": {

View File

@@ -1,243 +0,0 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import type { PromptPreset, PresetApplicableType } from '@/types/ai'
import {
getDefaultSystemPrompt,
getLockedPromptSectionPreview,
getOriginalBuiltinPreset,
type LocaleType,
} from '@/config/prompts'
import { usePromptStore } from '@/stores/prompt'
const { t, locale } = useI18n()
const props = defineProps<{
open: boolean
mode: 'add' | 'edit'
preset: PromptPreset | null
}>()
const emit = defineEmits<{
'update:open': [value: boolean]
saved: []
}>()
const promptStore = usePromptStore()
const formData = ref({
name: '',
systemPrompt: '',
supportGroup: true,
supportPrivate: true,
})
const isBuiltIn = computed(() => props.preset?.isBuiltIn ?? false)
const isEditMode = computed(() => props.mode === 'edit')
const isModified = computed(() => {
if (!isBuiltIn.value || !props.preset) return false
return promptStore.isBuiltinPresetModified(props.preset.id)
})
const modalTitle = computed(() => {
if (isBuiltIn.value) return t('settings.aiPrompt.modal.editBuiltin')
return isEditMode.value ? t('settings.aiPrompt.modal.editCustom') : t('settings.aiPrompt.modal.addCustom')
})
const canSave = computed(() => {
return formData.value.name.trim() && formData.value.systemPrompt.trim()
})
function applicableToCheckboxes(applicableTo?: PresetApplicableType): { group: boolean; private: boolean } {
if (!applicableTo || applicableTo === 'common') {
return { group: true, private: true }
}
return {
group: applicableTo === 'group',
private: applicableTo === 'private',
}
}
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,
(newVal) => {
if (newVal) {
if (props.preset) {
const checkboxes = applicableToCheckboxes(props.preset.applicableTo)
formData.value = {
name: props.preset.name,
systemPrompt: props.preset.systemPrompt,
supportGroup: checkboxes.group,
supportPrivate: checkboxes.private,
}
} else {
formData.value = {
name: '',
systemPrompt: getDefaultSystemPrompt(locale.value as LocaleType),
supportGroup: true,
supportPrivate: true,
}
}
}
}
)
function closeModal() {
emit('update:open', false)
}
function handleSave() {
if (!canSave.value) return
const applicableTo = checkboxesToApplicableTo(formData.value.supportGroup, formData.value.supportPrivate)
if (isEditMode.value && props.preset) {
const updates: {
name: string
systemPrompt: string
applicableTo?: PresetApplicableType
} = {
name: formData.value.name.trim(),
systemPrompt: formData.value.systemPrompt.trim(),
}
if (!isBuiltIn.value) {
updates.applicableTo = applicableTo
}
promptStore.updatePromptPreset(props.preset.id, updates)
} else {
promptStore.addPromptPreset({
name: formData.value.name.trim(),
systemPrompt: formData.value.systemPrompt.trim(),
applicableTo,
})
}
emit('saved')
closeModal()
}
function handleReset() {
if (!props.preset || !isBuiltIn.value) return
const original = getOriginalBuiltinPreset(props.preset.id, locale.value as LocaleType)
if (original) {
formData.value = {
name: original.name,
systemPrompt: original.systemPrompt,
supportGroup: true,
supportPrivate: true,
}
promptStore.resetBuiltinPreset(props.preset.id)
}
}
const previewContent = computed(() => {
const lockedSection = getLockedPromptSectionPreview('group', undefined, locale.value as LocaleType)
return `${formData.value.systemPrompt}
${lockedSection}`
})
</script>
<template>
<UModal :open="open" :ui="{ content: 'md:w-full max-w-2xl' }" @update:open="emit('update:open', $event)">
<template #content>
<div class="p-6">
<!-- Header -->
<div class="mb-4 flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ modalTitle }}</h2>
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="closeModal" />
</div>
<!-- 表单 -->
<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('settings.aiPrompt.modal.presetName') }}
</label>
<UInput
v-model="formData.name"
:placeholder="t('settings.aiPrompt.modal.presetNamePlaceholder')"
class="w-60"
/>
</div>
<!-- 适用场景仅自定义预设显示 -->
<div v-if="!isBuiltIn">
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('settings.aiPrompt.modal.applicableTo') }}
<span class="font-normal text-gray-500">{{ t('settings.aiPrompt.modal.applicableToHint') }}</span>
</label>
<div class="flex items-center gap-4">
<label class="flex cursor-pointer items-center gap-2">
<input
v-model="formData.supportGroup"
type="checkbox"
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('settings.aiPrompt.modal.groupChat') }}
</span>
</label>
<label class="flex cursor-pointer items-center gap-2">
<input
v-model="formData.supportPrivate"
type="checkbox"
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('settings.aiPrompt.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('settings.aiPrompt.modal.systemPrompt') }}
</label>
<UTextarea
v-model="formData.systemPrompt"
:rows="12"
:placeholder="t('settings.aiPrompt.modal.systemPromptPlaceholder')"
class="w-120 font-mono text-sm"
/>
</div>
<!-- 完整提示词预览 -->
<div>
<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('settings.aiPrompt.modal.preview') }}
<span class="font-normal text-gray-500">{{ t('settings.aiPrompt.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>
</div>
</div>
</div>
<!-- Footer -->
<div class="mt-6 flex justify-end gap-2">
<UButton v-if="isBuiltIn && isModified" variant="outline" color="warning" @click="handleReset">
<UIcon name="i-heroicons-arrow-path" class="mr-1 h-4 w-4" />
{{ t('settings.aiPrompt.modal.resetToDefault') }}
</UButton>
<UButton variant="ghost" @click="closeModal">{{ t('common.cancel') }}</UButton>
<UButton color="primary" :disabled="!canSave" @click="handleSave">
{{ isEditMode ? t('settings.aiPrompt.modal.saveChanges') : t('settings.aiPrompt.modal.addPreset') }}
</UButton>
</div>
</div>
</template>
</UModal>
</template>

View File

@@ -1,183 +1,217 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import type { PromptPreset } from '@/types/ai' import { useToast } from '@nuxt/ui/runtime/composables/useToast.js'
import AIPromptEditModal from './AIPromptEditModal.vue'
import ImportPresetModal from './ImportPresetModal.vue' interface LegacyPromptStoreData {
import { usePromptStore } from '@/stores/prompt' 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 { t } = useI18n()
const toast = useToast()
// Store const rawPromptStore = ref<LegacyPromptStoreData | null>(null)
const promptStore = usePromptStore() const rawPromptText = ref('')
const { allPromptPresets, aiPromptSettings } = storeToRefs(promptStore) const parseError = ref('')
// Emits /**
const emit = defineEmits<{ * 旧版提示词已经不再参与运行,这里只保留原始数据查看与复制能力。
'config-changed': [] */
}>() function loadLegacyPromptStore() {
const raw = localStorage.getItem('prompt')
rawPromptText.value = raw || ''
parseError.value = ''
rawPromptStore.value = null
// 弹窗状态 if (!raw) return
const showEditModal = ref(false)
const showImportModal = ref(false)
const editMode = ref<'add' | 'edit'>('add')
const editingPreset = ref<PromptPreset | null>(null)
/** 打开新增预设弹窗 */ try {
function openAddModal() { rawPromptStore.value = JSON.parse(raw) as LegacyPromptStoreData
editMode.value = 'add' } catch (error) {
editingPreset.value = null parseError.value = String(error)
showEditModal.value = true }
} }
/** 打开编辑预设弹窗 */ const hasLegacyPromptStore = computed(() => rawPromptText.value.trim().length > 0)
function openEditModal(preset: PromptPreset) {
editMode.value = 'edit' const customPromptPresets = computed(() => {
editingPreset.value = preset return Array.isArray(rawPromptStore.value?.customPromptPresets) ? rawPromptStore.value!.customPromptPresets : []
showEditModal.value = true })
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')
} }
/** 处理子弹窗保存后的回调 */ async function handleCopyJson() {
function handleModalSaved() { if (!formattedPromptStoreJson.value) return
emit('config-changed')
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,
})
}
} }
/** 设置当前激活的预设 */ onMounted(() => {
function setActivePreset(presetId: string) { loadLegacyPromptStore()
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')
}
</script> </script>
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<!-- 系统提示词标题和操作按钮 --> <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-center justify-between"> <div class="flex items-start gap-3">
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white"> <div
<UIcon name="i-heroicons-document-text" class="h-4 w-4 text-amber-500" /> 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"
{{ t('settings.aiPrompt.presets.title') }} >
</h4> <UIcon name="i-heroicons-archive-box" class="h-4.5 w-4.5" />
<div class="flex items-center gap-2"> </div>
<UButton variant="ghost" color="gray" size="xs" @click="openAddModal"> <div class="min-w-0">
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" /> <h4 class="text-sm font-semibold text-amber-900 dark:text-amber-100">
{{ t('settings.aiPrompt.presets.add') }} {{ t('settings.aiPrompt.legacyPrompt.title') }}
</UButton> </h4>
<UButton variant="soft" color="primary" size="xs" @click="showImportModal = true"> <p class="mt-1 text-sm leading-6 text-amber-800 dark:text-amber-200">
<UIcon name="i-heroicons-cloud-arrow-down" class="mr-1 h-3.5 w-3.5" /> {{ t('settings.aiPrompt.legacyPrompt.description') }}
{{ t('settings.aiPrompt.presets.import') }} </p>
</UButton> </div>
</div> </div>
</div> </div>
<!-- 预设列表 --> <div class="flex flex-wrap items-center gap-2">
<div class="space-y-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 <div
v-for="preset in allPromptPresets" v-if="parseError"
:key="preset.id" class="rounded-xl border border-red-200 bg-red-50 p-4 dark:border-red-900/60 dark:bg-red-950/20"
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)"
> >
<!-- 预设信息 --> <p class="text-sm font-medium text-red-700 dark:text-red-300">
<div class="flex items-center gap-2"> {{ t('settings.aiPrompt.legacyPrompt.parseError') }}
<div </p>
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full" <p class="mt-1 break-all text-xs text-red-600 dark:text-red-400">{{ parseError }}</p>
:class="[ </div>
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>
<!-- 操作按钮 --> <div v-if="customPromptPresets.length > 0" class="space-y-3">
<div class="flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100" @click.stop> <h4 class="text-sm font-semibold text-gray-900 dark:text-white">
<UButton {{ t('settings.aiPrompt.legacyPrompt.customPresetList') }}
color="neutral" </h4>
variant="ghost" <div class="space-y-2">
size="xs" <div
:icon="preset.isBuiltIn ? 'i-heroicons-eye' : 'i-heroicons-pencil-square'" v-for="preset in customPromptPresets"
@click="openEditModal(preset)" :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"
<UButton >
color="neutral" <div class="flex items-center gap-2">
variant="ghost" <p class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name || '-' }}</p>
size="xs" <UBadge color="gray" variant="soft" size="xs">
icon="i-heroicons-document-duplicate" {{ getApplicableLabel(preset.applicableTo) }}
@click="duplicatePreset(preset.id)" </UBadge>
/> </div>
<UButton <p class="mt-2 line-clamp-3 whitespace-pre-wrap text-xs leading-6 text-gray-500 dark:text-gray-400">
v-if="!preset.isBuiltIn" {{ preset.systemPrompt || t('settings.aiPrompt.legacyPrompt.noPromptContent') }}
color="error" </p>
variant="ghost" </div>
size="xs"
icon="i-heroicons-trash"
@click="deletePreset(preset.id)"
/>
</div> </div>
</div> </div>
</div>
<!-- 说明文字 --> <div class="space-y-3">
<p class="text-xs text-gray-500 dark:text-gray-400"> <div class="flex items-center justify-between">
{{ t('settings.aiPrompt.presets.description') }} <h4 class="text-sm font-semibold text-gray-900 dark:text-white">
</p> {{ 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> </div>
<!-- 编辑/添加弹窗 -->
<AIPromptEditModal v-model:open="showEditModal" :mode="editMode" :preset="editingPreset" @saved="handleModalSaved" />
<!-- 导入预设弹窗 -->
<ImportPresetModal v-model:open="showImportModal" @preset-added="handleImportPresetAdded" />
</template> </template>

View File

@@ -1,321 +0,0 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePromptStore, type RemotePresetData } from '@/stores/prompt'
const { t, locale } = useI18n()
const promptStore = usePromptStore()
// Props
const props = defineProps<{
open: boolean
}>()
// Emits
const emit = defineEmits<{
'update:open': [value: boolean]
'preset-added': []
}>()
// 状态
const isLoading = ref(false)
const error = ref('')
const remotePresets = ref<RemotePresetData[]>([])
// 预览状态
const previewPreset = ref<RemotePresetData | null>(null)
const isPreviewLoading = ref(false)
const previewError = ref('')
// 添加状态(跟踪正在添加的预设 ID
const addingPresetId = ref<string | null>(null)
// 预设分组配置
const presetGroups = computed(() => [
{
key: 'common',
icon: 'i-heroicons-squares-2x2',
iconColor: 'text-emerald-500',
titleKey: 'settings.aiPrompt.importPreset.commonPresets',
presets: remotePresets.value.filter((p) => p.chatType === 'common' || !p.chatType),
},
{
key: 'group',
icon: 'i-heroicons-chat-bubble-left-right',
iconColor: 'text-violet-500',
titleKey: 'settings.aiPrompt.importPreset.groupPresets',
presets: remotePresets.value.filter((p) => p.chatType === 'group'),
},
{
key: 'private',
icon: 'i-heroicons-user',
iconColor: 'text-blue-500',
titleKey: 'settings.aiPrompt.importPreset.privatePresets',
presets: remotePresets.value.filter((p) => p.chatType === 'private'),
},
])
// 加载远程预设索引(不下载内容)
async function loadRemotePresets() {
isLoading.value = true
error.value = ''
try {
const presets = await promptStore.fetchRemotePresets(locale.value)
remotePresets.value = presets
if (presets.length === 0) {
error.value = t('settings.aiPrompt.importPreset.noPresets')
}
} catch (e) {
error.value = t('settings.aiPrompt.importPreset.loadError')
} finally {
isLoading.value = false
}
}
// 预览预设(按需下载内容)
async function handlePreview(preset: RemotePresetData) {
previewError.value = ''
if (preset.systemPrompt) {
previewPreset.value = preset
return
}
isPreviewLoading.value = true
previewPreset.value = preset // 先显示基本信息
const fullPreset = await promptStore.fetchPresetContent(preset)
if (fullPreset) {
previewPreset.value = fullPreset
// 更新列表中的缓存
const index = remotePresets.value.findIndex((p) => p.id === preset.id)
if (index !== -1) {
remotePresets.value[index] = fullPreset
}
} else {
previewError.value = t('settings.aiPrompt.importPreset.fetchError')
}
isPreviewLoading.value = false
}
// 关闭预览
function closePreview() {
previewPreset.value = null
previewError.value = ''
}
// 添加预设(按需下载后再添加)
async function handleAddPreset(preset: RemotePresetData) {
addingPresetId.value = preset.id
let fullPreset = preset
if (!preset.systemPrompt) {
const fetched = await promptStore.fetchPresetContent(preset)
if (!fetched) {
addingPresetId.value = null
return
}
fullPreset = fetched
// 更新列表中的缓存
const index = remotePresets.value.findIndex((p) => p.id === preset.id)
if (index !== -1) {
remotePresets.value[index] = fullPreset
}
}
const success = promptStore.addRemotePreset(fullPreset)
if (success) {
emit('preset-added')
}
addingPresetId.value = null
}
// 从预览弹窗添加预设(添加后自动关闭预览)
async function handleAddPresetFromPreview() {
if (!previewPreset.value) return
await handleAddPreset(previewPreset.value)
closePreview()
}
// 关闭弹窗
function closeModal() {
emit('update:open', false)
closePreview()
}
// 监听打开状态,打开时加载数据
watch(
() => props.open,
(newVal) => {
if (newVal) {
loadRemotePresets()
} else {
closePreview()
}
}
)
</script>
<template>
<UModal :open="open" :ui="{ content: 'md:w-full max-w-lg' }" @update:open="emit('update:open', $event)">
<template #content>
<div class="p-6">
<!-- Header -->
<div class="mb-4 flex items-center justify-between">
<div class="flex items-center gap-2">
<UIcon name="i-heroicons-cloud-arrow-down" class="h-5 w-5 text-primary-500" />
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ t('settings.aiPrompt.importPreset.title') }}
</h2>
</div>
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="closeModal" />
</div>
<!-- 描述 -->
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.importPreset.description') }}
</p>
<!-- 内容区域 -->
<div class="max-h-[400px] overflow-y-auto">
<!-- 加载中 -->
<div v-if="isLoading" class="flex flex-col items-center justify-center py-12">
<UIcon name="i-heroicons-arrow-path" class="h-8 w-8 animate-spin text-primary-500" />
<p class="mt-2 text-sm text-gray-500">{{ t('settings.aiPrompt.importPreset.loading') }}</p>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="flex flex-col items-center justify-center py-12">
<UIcon name="i-heroicons-exclamation-circle" class="h-8 w-8 text-red-500" />
<p class="mt-2 text-sm text-gray-500">{{ error }}</p>
<UButton variant="soft" size="sm" class="mt-4" @click="loadRemotePresets">
{{ t('common.retry') }}
</UButton>
</div>
<!-- 预设列表 -->
<div v-else class="space-y-4">
<!-- 预设分组 -->
<div v-for="group in presetGroups" :key="group.key">
<div v-if="group.presets.length > 0">
<h4 class="mb-2 flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
<UIcon :name="group.icon" class="h-4 w-4" :class="group.iconColor" />
{{ t(group.titleKey) }}
</h4>
<div class="space-y-2">
<div
v-for="preset in group.presets"
: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 min-w-0">
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name }}</p>
<p class="mt-0.5 line-clamp-2 text-xs text-gray-500 dark:text-gray-400">
{{ preset.description || t('settings.aiPrompt.importPreset.noDescription') }}
</p>
</div>
<div class="flex items-center gap-1.5 ml-2 shrink-0">
<!-- 预览按钮 -->
<UButton color="gray" size="xs" @click="handlePreview(preset)">
<UIcon name="i-heroicons-eye" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.importPreset.preview') }}
</UButton>
<!-- 添加按钮 -->
<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('settings.aiPrompt.importPreset.added') }}
</UButton>
<UButton
v-else
variant="soft"
color="primary"
size="xs"
:loading="addingPresetId === preset.id"
@click="handleAddPreset(preset)"
>
<UIcon v-if="addingPresetId !== preset.id" name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
{{ t('settings.aiPrompt.importPreset.add') }}
</UButton>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</UModal>
<!-- 预览弹窗 -->
<UModal :open="!!previewPreset" :ui="{ content: 'md:w-full max-w-2xl' }" @update:open="closePreview">
<template #content>
<div class="p-6">
<div class="mb-4 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ previewPreset?.name }}
</h3>
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="closePreview" />
</div>
<!-- 加载中 -->
<div v-if="isPreviewLoading" class="flex items-center justify-center py-8">
<UIcon name="i-heroicons-arrow-path" class="h-6 w-6 animate-spin text-primary-500" />
<span class="ml-2 text-sm text-gray-500">{{ t('settings.aiPrompt.importPreset.fetchingContent') }}</span>
</div>
<!-- 错误 -->
<div v-else-if="previewError" class="text-center py-8">
<UIcon name="i-heroicons-exclamation-circle" class="h-8 w-8 text-red-500 mx-auto" />
<p class="mt-2 text-sm text-gray-500">{{ previewError }}</p>
</div>
<!-- 内容 -->
<div v-else-if="previewPreset?.systemPrompt" class="max-h-[60vh] overflow-y-auto space-y-4">
<div>
<p class="mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
{{ t('settings.aiPrompt.importPreset.systemPrompt') }}
</p>
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
<p class="whitespace-pre-wrap text-sm text-gray-600 dark:text-gray-400">
{{ previewPreset.systemPrompt }}
</p>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div
v-if="previewPreset && !isPreviewLoading && !previewError"
class="mt-4 flex justify-end gap-2 border-t border-gray-200 pt-4 dark:border-gray-700"
>
<UButton variant="ghost" color="gray" @click="closePreview">
{{ t('common.close') }}
</UButton>
<UButton
v-if="!promptStore.isRemotePresetAdded(previewPreset.id)"
color="primary"
:loading="addingPresetId === previewPreset.id"
@click="handleAddPresetFromPreview"
>
<UIcon v-if="addingPresetId !== previewPreset.id" name="i-heroicons-plus" class="mr-1 h-4 w-4" />
{{ t('settings.aiPrompt.importPreset.add') }}
</UButton>
<UButton v-else variant="soft" color="gray" disabled>
<UIcon name="i-heroicons-check" class="mr-1 h-4 w-4" />
{{ t('settings.aiPrompt.importPreset.added') }}
</UButton>
</div>
</div>
</template>
</UModal>
</template>

View File

@@ -95,9 +95,9 @@ void aiModelConfigRef.value
<!-- 分隔线 --> <!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-700" /> <div class="border-t border-gray-200 dark:border-gray-700" />
<!-- 提示词配置 --> <!-- 旧版提示词查看 -->
<div :ref="(el) => setSectionRef('preset', el as HTMLElement)"> <div :ref="(el) => setSectionRef('preset', el as HTMLElement)">
<AIPromptPresetTab @config-changed="handleAIConfigChanged" /> <AIPromptPresetTab />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,5 +2,4 @@ export { default as AISettingsTab } from './AISettingsTab.vue'
export { default as AIModelConfigTab } from './AI/AIModelConfigTab.vue' export { default as AIModelConfigTab } from './AI/AIModelConfigTab.vue'
export { default as AIModelEditModal } from './AI/AIModelEditModal.vue' export { default as AIModelEditModal } from './AI/AIModelEditModal.vue'
export { default as AIPromptConfigTab } from './AI/AIPromptConfigTab.vue' export { default as AIPromptConfigTab } from './AI/AIPromptConfigTab.vue'
export { default as AIPromptEditModal } from './AI/AIPromptEditModal.vue'
export { default as CacheManageTab } from './CacheManageTab.vue' export { default as CacheManageTab } from './CacheManageTab.vue'

View File

@@ -221,7 +221,7 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const assistantStore = useAssistantStore() const assistantStore = useAssistantStore()
const skillStore = useSkillStore() const skillStore = useSkillStore()
const { activePreset, aiGlobalSettings } = storeToRefs(promptStore) const { aiGlobalSettings } = storeToRefs(promptStore)
function generateId(prefix: string): string { function generateId(prefix: string): string {
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
@@ -759,7 +759,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
preprocessConfig: serializablePreprocessConfig, preprocessConfig: serializablePreprocessConfig,
} }
const currentPromptConfig = { systemPrompt: activePreset.value.systemPrompt }
const { requestId: agentReqId, promise: agentPromise } = window.agentApi.runStream( const { requestId: agentReqId, promise: agentPromise } = window.agentApi.runStream(
content, content,
context, context,
@@ -846,7 +845,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
} }
}, },
state.chatType, state.chatType,
currentAssistantId ? undefined : { systemPrompt: currentPromptConfig.systemPrompt },
state.locale, state.locale,
maxHistoryRounds, maxHistoryRounds,
currentAssistantId, currentAssistantId,

View File

@@ -246,48 +246,6 @@ export const useAssistantStore = defineStore('assistant', () => {
} }
} }
const promptMigrationDone = ref(false)
async function migrateOldPromptPresets(): Promise<void> {
if (promptMigrationDone.value) return
try {
const raw = localStorage.getItem('prompt')
if (!raw) {
promptMigrationDone.value = true
return
}
const data = JSON.parse(raw)
const hasCustomPresets = Array.isArray(data.customPromptPresets) && data.customPromptPresets.length > 0
const hasOverrides = data.builtinPresetOverrides && Object.keys(data.builtinPresetOverrides).length > 0
const hasRemoteIds = Array.isArray(data.fetchedRemotePresetIds) && data.fetchedRemotePresetIds.length > 0
if (!hasCustomPresets && !hasOverrides && !hasRemoteIds) {
promptMigrationDone.value = true
return
}
console.log('[AssistantStore] Backing up old prompt presets...')
const result = await window.assistantApi.backupOldPresets({
customPresets: data.customPromptPresets,
builtinOverrides: data.builtinPresetOverrides,
remotePresetIds: data.fetchedRemotePresetIds,
})
if (result.success) {
console.log('[AssistantStore] Backup saved to:', result.filePath)
} else {
console.warn('[AssistantStore] Backup failed:', result.error)
}
promptMigrationDone.value = true
} catch (error) {
console.error('[AssistantStore] Migration check failed:', error)
promptMigrationDone.value = true
}
}
return { return {
assistants, assistants,
selectedAssistantId, selectedAssistantId,
@@ -302,7 +260,6 @@ export const useAssistantStore = defineStore('assistant', () => {
defaultAssistants, defaultAssistants,
moreAssistants, moreAssistants,
hasMoreAssistants, hasMoreAssistants,
promptMigrationDone,
loadAssistants, loadAssistants,
loadBuiltinCatalog, loadBuiltinCatalog,
loadBuiltinSqlTools, loadBuiltinSqlTools,
@@ -318,6 +275,5 @@ export const useAssistantStore = defineStore('assistant', () => {
importAssistant, importAssistant,
reimportAssistant, reimportAssistant,
deleteAssistant, deleteAssistant,
migrateOldPromptPresets,
} }
}) })