diff --git a/electron/main/ai/conversations.ts b/electron/main/ai/conversations.ts index 402065f..5b54164 100644 --- a/electron/main/ai/conversations.ts +++ b/electron/main/ai/conversations.ts @@ -363,3 +363,27 @@ export function deleteMessage(messageId: string): boolean { const result = db.prepare('DELETE FROM ai_message WHERE id = ?').run(messageId) return result.changes > 0 } + +// ==================== Agent 专用 ==================== + +/** + * 为 Agent 提供对话历史 + * + * 返回简化的 {role, content} 格式,按时间升序排列。 + * @param conversationId 对话 ID + * @param maxMessages 最大返回条数(取最近 N 条) + */ +export function getHistoryForAgent( + conversationId: string, + maxMessages?: number +): Array<{ role: 'user' | 'assistant'; content: string }> { + const messages = getMessages(conversationId) + const filtered = messages + .filter((m) => (m.role === 'user' || m.role === 'assistant') && m.content?.trim()) + .map((m) => ({ role: m.role, content: m.content })) + + if (maxMessages && filtered.length > maxMessages) { + return filtered.slice(-maxMessages) + } + return filtered +} diff --git a/electron/main/ipc/ai.ts b/electron/main/ipc/ai.ts index 7b2f423..e891d66 100644 --- a/electron/main/ipc/ai.ts +++ b/electron/main/ipc/ai.ts @@ -8,6 +8,8 @@ import * as rag from '../ai/rag' import { aiLogger } from '../ai/logger' import { getLogsDir } from '../paths' import { Agent, type AgentStreamChunk, type PromptConfig } from '../ai/agent' +import { getActiveConfig, buildPiModel } from '../ai/llm' +import { completeSimple, streamSimple, type TextContent as PiTextContent } from '@mariozechner/pi-ai' import { t } from '../i18n' import type { ToolContext } from '../ai/tools/types' import type { IpcContext } from './types' @@ -115,23 +117,16 @@ export function registerAIHandlers({ win }: IpcContext): void { /** * 创建新的 AI 对话 + * 参数契约与 preload / 数据层保持一致:(sessionId, title?) */ - ipcMain.handle( - 'ai:createConversation', - async ( - _, - title: string, - sessionId?: string, - dataSource?: { type: 'chat' | 'member'; id: string; name?: string } - ) => { - try { - return aiConversations.createConversation(title, sessionId, dataSource) - } catch (error) { - console.error('Failed to create AI conversation:', error) - throw error - } + ipcMain.handle('ai:createConversation', async (_, sessionId: string, title?: string) => { + try { + return aiConversations.createConversation(sessionId, title) + } catch (error) { + console.error('Failed to create AI conversation:', error) + throw error } - ) + }) /** * 获取所有 AI 对话列表 @@ -405,13 +400,11 @@ export function registerAIHandlers({ win }: IpcContext): void { async (_, provider: llm.LLMProvider, apiKey: string, baseUrl?: string, model?: string) => { console.log('[LLM:validateApiKey] Validating:', { provider, baseUrl, model, apiKeyLength: apiKey?.length }) try { - const service = llm.createLLMService({ provider, apiKey, baseUrl, model }) - const result = await service.validateApiKey() + const result = await llm.validateApiKey(provider, apiKey, baseUrl, model) console.log('[LLM:validateApiKey] Result:', result) - return { success: result.success, error: result.error } + return result } catch (error) { console.error('[LLM:validateApiKey] Validation failed:', error) - // 提取有意义的错误信息 const errorMessage = error instanceof Error ? error.message : String(error) return { success: false, error: errorMessage } } @@ -425,75 +418,124 @@ export function registerAIHandlers({ win }: IpcContext): void { return llm.hasActiveConfig() }) - /** - * 发送 LLM 聊天请求(非流式) - */ - ipcMain.handle('llm:chat', async (_, messages: llm.ChatMessage[], options?: llm.ChatOptions) => { - aiLogger.info('IPC', 'Non-streaming LLM request received', { - messagesCount: messages.length, - firstMsgRole: messages[0]?.role, - firstMsgContentLen: messages[0]?.content?.length, - options, - }) - try { - const response = await llm.chat(messages, options) - aiLogger.info('IPC', 'Non-streaming LLM request succeeded', { responseLength: response.length }) - return { success: true, content: response } - } catch (error) { - aiLogger.error('IPC', 'Non-streaming LLM request failed', { error: String(error) }) - console.error('LLM chat failed:', error) - return { success: false, error: String(error) } - } - }) + // ==================== LLM 直接调用 API(SQLLab 等非 Agent 场景使用) ==================== /** - * 发送 LLM 聊天请求(流式) - * 使用 IPC 事件发送流式数据 + * 非流式 LLM 调用 + */ + ipcMain.handle( + 'llm:chat', + async ( + _, + messages: Array<{ role: string; content: string }>, + options?: { temperature?: number; maxTokens?: number } + ) => { + try { + const activeConfig = getActiveConfig() + if (!activeConfig) { + return { success: false, error: t('llm.notConfigured') } + } + const piModel = buildPiModel(activeConfig) + const now = Date.now() + const systemMsg = messages.find((m) => m.role === 'system') + const nonSystemMsgs = messages.filter((m) => m.role !== 'system') + + const result = await completeSimple( + piModel, + { + systemPrompt: systemMsg?.content, + messages: nonSystemMsgs.map((m) => ({ + role: m.role as 'user' | 'assistant', + content: m.content, + timestamp: now, + })), + }, + { + apiKey: activeConfig.apiKey, + temperature: options?.temperature, + maxTokens: options?.maxTokens, + } + ) + + const content = result.content + .filter((item): item is PiTextContent => item.type === 'text') + .map((item) => item.text) + .join('') + + return { success: true, content } + } catch (error) { + aiLogger.error('IPC', 'llm:chat error', { error: String(error) }) + return { success: false, error: String(error) } + } + } + ) + + /** + * 流式 LLM 调用(SQLLab AI 生成 / 结果总结等场景使用) */ ipcMain.handle( 'llm:chatStream', - async (_, requestId: string, messages: llm.ChatMessage[], options?: llm.ChatOptions) => { - aiLogger.info('IPC', `Streaming chat request received: ${requestId}`, { - messagesCount: messages.length, - options, - }) - + async ( + _, + requestId: string, + messages: Array<{ role: string; content: string }>, + options?: { temperature?: number; maxTokens?: number } + ) => { try { - const generator = llm.chatStream(messages, options) - aiLogger.info('IPC', `Stream generator created: ${requestId}`) + const activeConfig = getActiveConfig() + if (!activeConfig) { + return { success: false, error: t('llm.notConfigured') } + } + const piModel = buildPiModel(activeConfig) + const now = Date.now() + const systemMsg = messages.find((m) => m.role === 'system') + const nonSystemMsgs = messages.filter((m) => m.role !== 'system') - // 异步处理流式响应 + const eventStream = streamSimple( + piModel, + { + systemPrompt: systemMsg?.content, + messages: nonSystemMsgs.map((m) => ({ + role: m.role as 'user' | 'assistant', + content: m.content, + timestamp: now, + })), + }, + { + apiKey: activeConfig.apiKey, + temperature: options?.temperature, + maxTokens: options?.maxTokens, + } + ) + + // 异步消费流,通过事件发送 chunks ;(async () => { - let chunkIndex = 0 try { - aiLogger.info('IPC', `Iterating stream response: ${requestId}`) - for await (const chunk of generator) { - chunkIndex++ - aiLogger.debug('IPC', `Sending chunk #${chunkIndex}: ${requestId}`, { - contentLength: chunk.content?.length, - isFinished: chunk.isFinished, - finishReason: chunk.finishReason, - }) - win.webContents.send('llm:streamChunk', { requestId, chunk }) + for await (const event of eventStream) { + if (event.type === 'text_delta') { + win.webContents.send('llm:streamChunk', { + requestId, + chunk: { content: event.delta, isFinished: false }, + }) + } } - aiLogger.info('IPC', `Stream response completed: ${requestId}`, { totalChunks: chunkIndex }) - } catch (error) { - aiLogger.error('IPC', `Stream response error: ${requestId}`, { - error: String(error), - chunkIndex, - }) win.webContents.send('llm:streamChunk', { requestId, - chunk: { content: '', isFinished: true, finishReason: 'error' }, + chunk: { content: '', isFinished: true, finishReason: 'stop' }, + }) + } catch (error) { + aiLogger.error('IPC', 'llm:chatStream stream error', { requestId, error: String(error) }) + win.webContents.send('llm:streamChunk', { + requestId, error: String(error), + chunk: { content: '', isFinished: true, finishReason: 'error' }, }) } })() return { success: true } } catch (error) { - aiLogger.error('IPC', `Failed to create stream request: ${requestId}`, { error: String(error) }) - console.error('LLM streaming chat failed:', error) + aiLogger.error('IPC', 'llm:chatStream error', { error: String(error) }) return { success: false, error: String(error) } } } @@ -504,10 +546,11 @@ export function registerAIHandlers({ win }: IpcContext): void { /** * 执行 Agent 对话(流式) * Agent 会自动调用工具并返回最终结果 - * @param historyMessages 对话历史(可选,用于上下文关联) + * Agent 通过 context.conversationId 从 SQLite 读取对话历史(数据流倒置) * @param chatType 聊天类型('group' | 'private') * @param promptConfig 用户自定义提示词配置(可选) * @param locale 语言设置(可选,默认 'zh-CN') + * @param maxHistoryRounds 前端用户配置的最大历史轮数(可选,每轮 = user + assistant = 2 条) */ ipcMain.handle( 'agent:runStream', @@ -516,35 +559,36 @@ export function registerAIHandlers({ win }: IpcContext): void { requestId: string, userMessage: string, context: ToolContext, - historyMessages?: Array<{ role: 'user' | 'assistant'; content: string }>, chatType?: 'group' | 'private', promptConfig?: PromptConfig, - locale?: string + locale?: string, + maxHistoryRounds?: number ) => { aiLogger.info('IPC', `Agent stream request received: ${requestId}`, { userMessage: userMessage.slice(0, 100), sessionId: context.sessionId, - historyLength: historyMessages?.length ?? 0, + conversationId: context.conversationId, chatType: chatType ?? 'group', hasPromptConfig: !!promptConfig, }) try { - // 创建 AbortController 并存储 const abortController = new AbortController() activeAgentRequests.set(requestId, abortController) - // 转换历史消息格式 - const formattedHistory = - historyMessages?.map((msg) => ({ - role: msg.role as 'user' | 'assistant', - content: msg.content, - })) ?? [] + const activeAIConfig = getActiveConfig() + if (!activeAIConfig) { + return { success: false, error: t('llm.notConfigured') } + } + const piModel = buildPiModel(activeAIConfig) + + const contextHistoryLimit = maxHistoryRounds ? maxHistoryRounds * 2 : undefined const agent = new Agent( context, - { abortSignal: abortController.signal }, - formattedHistory, + piModel, + activeAIConfig.apiKey, + { abortSignal: abortController.signal, contextHistoryLimit }, chatType ?? 'group', promptConfig, locale ?? 'zh-CN' @@ -568,9 +612,18 @@ export function registerAIHandlers({ win }: IpcContext): void { win.webContents.send('agent:streamChunk', { requestId, chunk }) }) - // 如果已中止,不发送完成信息 if (abortController.signal.aborted) { - aiLogger.info('IPC', `Agent aborted, skipping completion: ${requestId}`) + aiLogger.info('IPC', `Agent aborted: ${requestId}`) + win.webContents.send('agent:complete', { + requestId, + result: { + content: result.content, + toolsUsed: result.toolsUsed, + toolRounds: result.toolRounds, + totalUsage: result.totalUsage, + aborted: true, + }, + }) return } @@ -592,9 +645,12 @@ export function registerAIHandlers({ win }: IpcContext): void { totalUsage: result.totalUsage, }) } catch (error) { - // 如果是中止错误,不报告为错误 if (error instanceof Error && error.name === 'AbortError') { - aiLogger.info('IPC', `Agent request aborted: ${requestId}`) + aiLogger.info('IPC', `Agent request aborted (error): ${requestId}`) + win.webContents.send('agent:complete', { + requestId, + result: { content: '', toolsUsed: [], toolRounds: 0, aborted: true }, + }) return } const friendlyError = formatAIError(error) diff --git a/electron/main/ipc/chat.ts b/electron/main/ipc/chat.ts index 8021a46..757437d 100644 --- a/electron/main/ipc/chat.ts +++ b/electron/main/ipc/chat.ts @@ -295,9 +295,7 @@ export function registerChatHandlers(ctx: IpcContext): void { */ ipcMain.handle('chat:deleteSession', async (_, sessionId: string) => { try { - // 先关闭 Worker 中的数据库连接 await worker.closeDatabase(sessionId) - // 然后删除文件(使用核心模块) const result = databaseCore.deleteSession(sessionId) return result } catch (error) { diff --git a/electron/preload/apis/ai.ts b/electron/preload/apis/ai.ts index 8731b901..d6c37fd 100644 --- a/electron/preload/apis/ai.ts +++ b/electron/preload/apis/ai.ts @@ -75,29 +75,9 @@ export interface ChatStreamChunk { finishReason?: 'stop' | 'length' | 'error' } -// Agent API 类型 -export interface TokenUsage { - promptTokens: number - completionTokens: number - totalTokens: number -} - -export interface AgentRuntimeStatus { - phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error' - round: number - toolsUsed: number - currentTool?: string - contextTokens: number - contextWindow: number - contextUsage: number - totalUsage: TokenUsage - nodeCount?: number - tagCount?: number - segmentSize?: number - checkoutCount?: number - activeAnchorNodeId?: string | null - updatedAt: number -} +// Agent API 类型 — 从 shared/types 统一导入 +export type { TokenUsage, AgentRuntimeStatus } from '../../shared/types' +import type { TokenUsage, AgentRuntimeStatus } from '../../shared/types' export interface AgentStreamChunk { type: 'content' | 'think' | 'tool_start' | 'tool_result' | 'status' | 'done' | 'error' @@ -651,28 +631,28 @@ export const llmApi = { export const agentApi = { /** * 执行 Agent 对话(流式) - * Agent 会自动调用工具获取数据并生成回答 - * @param historyMessages 对话历史(可选,用于上下文关联) + * Agent 通过 context.conversationId 从后端 SQLite 读取对话历史 * @param chatType 聊天类型('group' | 'private') * @param promptConfig 用户自定义提示词配置(可选) * @param locale 语言设置(可选,默认 'zh-CN') + * @param maxHistoryRounds 最大历史轮数(可选,每轮 = user + assistant = 2 条) * @returns 返回 { requestId, promise },requestId 可用于中止请求 */ runStream: ( userMessage: string, context: ToolContext, onChunk?: (chunk: AgentStreamChunk) => void, - historyMessages?: Array<{ role: 'user' | 'assistant'; content: string }>, chatType?: 'group' | 'private', promptConfig?: PromptConfig, - locale?: string + locale?: string, + maxHistoryRounds?: number ): { requestId: string; promise: Promise<{ success: boolean; result?: AgentResult; error?: string }> } => { const requestId = `agent_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` console.log( '[preload] Agent runStream 开始,requestId:', requestId, - 'historyLength:', - historyMessages?.length ?? 0, + 'conversationId:', + context.conversationId ?? 'none', 'chatType:', chatType ?? 'group', 'hasPromptConfig:', @@ -713,9 +693,8 @@ export const agentApi = { ipcRenderer.on('agent:streamChunk', chunkHandler) ipcRenderer.on('agent:complete', completeHandler) - // 发起请求(传递历史消息、聊天类型、提示词配置和语言设置) ipcRenderer - .invoke('agent:runStream', requestId, userMessage, context, historyMessages, chatType, promptConfig, locale) + .invoke('agent:runStream', requestId, userMessage, context, chatType, promptConfig, locale, maxHistoryRounds) .then((result) => { console.log('[preload] Agent invoke 返回:', result) if (!result.success) { diff --git a/electron/preload/index.d.ts b/electron/preload/index.d.ts index 11d1065..2f8fb03 100644 --- a/electron/preload/index.d.ts +++ b/electron/preload/index.d.ts @@ -1,5 +1,6 @@ import { ElectronAPI } from '@electron-toolkit/preload' import type { AnalysisSession, MessageType, ImportProgress, ExportProgress } from '../../src/types/base' +import type { TokenUsage, AgentRuntimeStatus } from '../shared/types' import type { MemberActivity, MemberNameHistory, @@ -582,29 +583,7 @@ interface RAGConfig { topK?: number } -// Token 使用量类型 -interface TokenUsage { - promptTokens: number - completionTokens: number - totalTokens: number -} - -interface AgentRuntimeStatus { - phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error' - round: number - toolsUsed: number - currentTool?: string - contextTokens: number - contextWindow: number - contextUsage: number - totalUsage: TokenUsage - nodeCount?: number - tagCount?: number - segmentSize?: number - checkoutCount?: number - activeAnchorNodeId?: string | null - updatedAt: number -} +// TokenUsage & AgentRuntimeStatus — imported from electron/shared/types.ts // Agent 相关类型 interface AgentStreamChunk { @@ -659,10 +638,10 @@ interface AgentApi { userMessage: string, context: ToolContext, onChunk?: (chunk: AgentStreamChunk) => void, - historyMessages?: Array<{ role: 'user' | 'assistant'; content: string }>, chatType?: 'group' | 'private', promptConfig?: PromptConfig, - locale?: string + locale?: string, + maxHistoryRounds?: number ) => { requestId: string; promise: Promise<{ success: boolean; result?: AgentResult; error?: string }> } abort: (requestId: string) => Promise<{ success: boolean; error?: string }> } diff --git a/src/composables/useAIChat.ts b/src/composables/useAIChat.ts index cfc2329..3b3e56e 100644 --- a/src/composables/useAIChat.ts +++ b/src/composables/useAIChat.ts @@ -7,6 +7,7 @@ import { ref, computed } from 'vue' import { storeToRefs } from 'pinia' import { usePromptStore } from '@/stores/prompt' import { useSessionStore } from '@/stores/session' +import type { TokenUsage, AgentRuntimeStatus } from '@electron/shared/types' // 工具调用记录 export interface ToolCallRecord { @@ -69,30 +70,8 @@ export interface ToolStatus { result?: unknown } -// Token 使用量类型 -export interface TokenUsage { - promptTokens: number - completionTokens: number - totalTokens: number -} - -// Agent 运行状态(由主进程流式推送) -export interface AgentRuntimeStatus { - phase: 'preparing' | 'thinking' | 'tool_running' | 'responding' | 'completed' | 'aborted' | 'error' - round: number - toolsUsed: number - currentTool?: string - contextTokens: number - contextWindow: number - contextUsage: number - totalUsage: TokenUsage - nodeCount?: number - tagCount?: number - segmentSize?: number - checkoutCount?: number - activeAnchorNodeId?: string | null - updatedAt: number -} +// TokenUsage & AgentRuntimeStatus — re-export from shared/types +export type { TokenUsage, AgentRuntimeStatus } // 工具显示名称通过 vue-i18n 管理: ai.chat.message.tools.* // 渲染层 (ChatMessage.vue, AIThinkingIndicator.vue) 使用 t() 动态获取 @@ -129,6 +108,9 @@ export function useAIChat( const isLoadingSource = ref(false) const isAIThinking = ref(false) const currentConversationId = ref(null) + // Agent 上下文会话 ID(与数据库 conversationId 解耦): + // 用于保证“新建对话首轮”也能拿到独立上下文键,避免共享 draft 时间线 + const contextConversationId = ref('') // Owner 信息(用于告诉 AI 当前用户是谁) const ownerInfo = ref(undefined) @@ -182,14 +164,19 @@ export function useAIChat( return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` } + function generateDraftContextConversationId(): string { + return `draft_ctx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` + } + + // 初始化时先分配一个草稿上下文键,确保首轮请求也有隔离 ID + contextConversationId.value = generateDraftContextConversationId() + function buildFallbackAgentStatus(): AgentRuntimeStatus { return { phase: 'preparing', round: 0, toolsUsed: toolsUsedInCurrentRound.value.length, contextTokens: 0, - contextWindow: 0, - contextUsage: 0, totalUsage: { ...sessionTokenUsage.value }, updatedAt: Date.now(), } @@ -373,11 +360,20 @@ export function useAIChat( } try { - // 调用 Agent API - // 注意:ownerInfo 需要深拷贝为普通对象,否则 IPC 克隆会失败 + // 确保对话 ID 存在(数据流倒置:Agent 从 SQLite 读取历史,需要有效的 conversationId) + if (!currentConversationId.value) { + const title = content.slice(0, 50) + (content.length > 50 ? '...' : '') + const conversation = await window.aiApi.createConversation(sessionId, title) + currentConversationId.value = conversation.id + contextConversationId.value = conversation.id + console.log('[AI] 提前创建对话:', conversation.id) + } + + const maxHistoryRounds = aiGlobalSettings.value.maxHistoryRounds ?? 5 + const context = { sessionId, - conversationId: currentConversationId.value || undefined, + conversationId: currentConversationId.value, timeFilter: timeFilter ? { startTs: timeFilter.startTs, endTs: timeFilter.endTs } : undefined, maxMessagesLimit: aiGlobalSettings.value.maxMessagesPerRequest, ownerInfo: ownerInfo.value @@ -385,31 +381,9 @@ export function useAIChat( : undefined, } - console.log('[AI] 构建 context:', { - sessionId, - maxMessagesLimit: context.maxMessagesLimit, - ownerInfo: context.ownerInfo, - aiGlobalSettings: aiGlobalSettings.value, - }) - - // 收集历史消息(排除当前用户消息和 AI 占位消息) - // 应用历史轮数限制:每轮 = 用户提问 + AI 回复 = 2 条消息 - const maxHistoryRounds = aiGlobalSettings.value.maxHistoryRounds ?? 5 - const maxHistoryMessages = maxHistoryRounds * 2 - - const historyMessages = messages.value - .slice(0, -2) // 排除刚添加的用户消息和 AI 占位消息 - .filter((msg) => msg.role === 'user' || msg.role === 'assistant') - .filter((msg) => msg.content && msg.content.trim() !== '') // 排除空消息 - .slice(-maxHistoryMessages) // 只保留最近 N 轮对话 - .map((msg) => ({ - role: msg.role as 'user' | 'assistant', - content: msg.content, - })) - console.log('[AI] 调用 Agent API...', { context, - historyLength: historyMessages.length, + maxHistoryRounds, chatType, promptConfig: currentPromptConfig.value, }) @@ -487,7 +461,9 @@ export function useAIChat( case 'status': if (chunk.status) { - agentStatus.value = chunk.status + if (!agentStatus.value || chunk.status.updatedAt >= agentStatus.value.updatedAt) { + agentStatus.value = chunk.status + } } break @@ -529,14 +505,13 @@ export function useAIChat( break } }, - historyMessages, chatType, - // 确保传递纯对象,避免 IPC 克隆失败 { roleDefinition: currentPromptConfig.value.roleDefinition, responseRules: currentPromptConfig.value.responseRules, }, - locale + locale, + maxHistoryRounds ) // 存储 Agent 请求 ID(用于中止) @@ -610,15 +585,12 @@ export function useAIChat( console.log('[AI] saveConversation 调用') try { - // 如果没有当前对话,创建新对话 if (!currentConversationId.value) { - const title = userMsg.content.slice(0, 50) + (userMsg.content.length > 50 ? '...' : '') - const conversation = await window.aiApi.createConversation(sessionId, title) - currentConversationId.value = conversation.id - console.log('[AI] 创建了新对话:', conversation.id) + console.warn('[AI] saveConversation: conversationId 未设置,跳过保存') + return } - // 保存用户消息 + // 保存用户消息(保存后 Agent 下次执行时可从 DB 读到) await window.aiApi.addMessage(currentConversationId.value, 'user', userMsg.content) // 保存 AI 消息(包含 contentBlocks) @@ -653,6 +625,8 @@ export function useAIChat( try { const history = await window.aiApi.getMessages(conversationId) currentConversationId.value = conversationId + // 加载历史对话时,绑定到真实 conversationId,确保同一历史会话复用上下文时间线 + contextConversationId.value = conversationId console.log( '[AI] 从数据库加载的原始消息:', @@ -684,6 +658,8 @@ export function useAIChat( */ function startNewConversation(welcomeMessage?: string): void { currentConversationId.value = null + // 每次新建对话都生成新的草稿上下文键,避免多次“新对话首轮”共享同一 draft 轨迹 + contextConversationId.value = generateDraftContextConversationId() messages.value = [] sourceMessages.value = [] currentKeywords.value = [] @@ -726,9 +702,7 @@ export function useAIChat( isAIThinking.value = false isLoadingSource.value = false currentToolStatus.value = null - if (agentStatus.value) { - setAgentPhase('aborted') - } + setAgentPhase('aborted') // 调用主进程中止 Agent 请求 if (currentAgentRequestId) {