mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-04-29 08:12:41 +08:00
feat: 移除旧版提示词系统
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = ''
|
||||||
|
|||||||
@@ -60,14 +60,6 @@ export interface AgentResult {
|
|||||||
totalUsage?: TokenUsage
|
totalUsage?: TokenUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户自定义提示词配置
|
|
||||||
*/
|
|
||||||
export interface PromptConfig {
|
|
||||||
/** 系统提示词(角色定义 + 回答要求,统一为单一字段) */
|
|
||||||
systemPrompt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 技能上下文(传递给 prompt-builder)
|
* 技能上下文(传递给 prompt-builder)
|
||||||
* 手动选择和 AI 自选两种模式互斥
|
* 手动选择和 AI 自选两种模式互斥
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
12
electron/preload/index.d.ts
vendored
12
electron/preload/index.d.ts
vendored
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user