/** * AI 相关 API - AI 对话、LLM 服务、Agent、Embedding */ import { ipcRenderer } from 'electron' import type { ExportProgress } from '../../../src/types/base' // ==================== 类型定义 ==================== // AI API 类型 export interface SearchMessageResult { id: number senderName: string senderPlatformId: string senderAliases: string[] senderAvatar: string | null content: string timestamp: number type: number } export interface AIConversation { id: string sessionId: string title: string | null createdAt: number updatedAt: number } // 内容块类型(用于 AI 消息的混合渲染) export type ContentBlock = | { type: 'text'; text: string } | { type: 'tool' tool: { name: string displayName: string status: 'running' | 'done' | 'error' params?: Record } } export interface AIMessage { id: string conversationId: string role: 'user' | 'assistant' content: string timestamp: number dataKeywords?: string[] dataMessageCount?: number contentBlocks?: ContentBlock[] } // LLM API 类型 export interface LLMProvider { id: string name: string description: string defaultBaseUrl: string models: Array<{ id: string; name: string; description?: string }> } export interface ChatMessage { role: 'system' | 'user' | 'assistant' content: string } export interface ChatOptions { temperature?: number maxTokens?: number } export interface ChatStreamChunk { content: string isFinished: boolean finishReason?: 'stop' | 'length' | 'error' } // Agent API 类型 export interface AgentStreamChunk { type: 'content' | 'think' | 'tool_start' | 'tool_result' | 'done' | 'error' content?: string thinkTag?: string thinkDurationMs?: number toolName?: string toolParams?: Record toolResult?: unknown error?: string isFinished?: boolean } export interface AgentResult { content: string toolsUsed: string[] toolRounds: number } export interface ToolContext { sessionId: string timeFilter?: { startTs: number; endTs: number } } // AI 服务配置类型(前端用) export interface AIServiceConfigDisplay { id: string name: string provider: string apiKey: string // 脱敏后的 API Key apiKeySet: boolean model?: string baseUrl?: string maxTokens?: number disableThinking?: boolean isReasoningModel?: boolean createdAt: number updatedAt: number } // Embedding 服务配置(多配置模式) export interface EmbeddingServiceConfig { id: string name: string apiSource: 'reuse_llm' | 'custom' model: string baseUrl?: string apiKey?: string createdAt: number updatedAt: number } // Embedding 配置展示用(隐藏 apiKey) export interface EmbeddingServiceConfigDisplay { id: string name: string apiSource: 'reuse_llm' | 'custom' model: string baseUrl?: string apiKeySet: boolean createdAt: number updatedAt: number } // 用户自定义提示词配置 export interface PromptConfig { roleDefinition: string responseRules: string } // ==================== AI API ==================== export const aiApi = { /** * 搜索消息(关键词搜索) * @param senderId 可选的发送者成员 ID,用于筛选特定成员的消息 */ searchMessages: ( sessionId: string, keywords: string[], filter?: { startTs?: number; endTs?: number }, limit?: number, offset?: number, senderId?: number ): Promise<{ messages: SearchMessageResult[]; total: number }> => { return ipcRenderer.invoke('ai:searchMessages', sessionId, keywords, filter, limit, offset, senderId) }, /** * 获取消息上下文 * @param messageIds 支持单个或批量消息 ID */ getMessageContext: ( sessionId: string, messageIds: number | number[], contextSize?: number ): Promise => { return ipcRenderer.invoke('ai:getMessageContext', sessionId, messageIds, contextSize) }, /** * 获取最近消息(AI Agent 专用) */ getRecentMessages: ( sessionId: string, filter?: { startTs?: number; endTs?: number }, limit?: number ): Promise<{ messages: SearchMessageResult[]; total: number }> => { return ipcRenderer.invoke('ai:getRecentMessages', sessionId, filter, limit) }, /** * 获取所有最近消息(消息查看器专用) */ getAllRecentMessages: ( sessionId: string, filter?: { startTs?: number; endTs?: number }, limit?: number ): Promise<{ messages: SearchMessageResult[]; total: number }> => { return ipcRenderer.invoke('ai:getAllRecentMessages', sessionId, filter, limit) }, /** * 获取两人之间的对话 */ getConversationBetween: ( sessionId: string, memberId1: number, memberId2: number, filter?: { startTs?: number; endTs?: number }, limit?: number ): Promise<{ messages: SearchMessageResult[]; total: number; member1Name: string; member2Name: string }> => { return ipcRenderer.invoke('ai:getConversationBetween', sessionId, memberId1, memberId2, filter, limit) }, /** * 获取指定消息之前的 N 条(用于向上无限滚动) */ getMessagesBefore: ( sessionId: string, beforeId: number, limit?: number, filter?: { startTs?: number; endTs?: number }, senderId?: number, keywords?: string[] ): Promise<{ messages: SearchMessageResult[]; hasMore: boolean }> => { return ipcRenderer.invoke('ai:getMessagesBefore', sessionId, beforeId, limit, filter, senderId, keywords) }, /** * 获取指定消息之后的 N 条(用于向下无限滚动) */ getMessagesAfter: ( sessionId: string, afterId: number, limit?: number, filter?: { startTs?: number; endTs?: number }, senderId?: number, keywords?: string[] ): Promise<{ messages: SearchMessageResult[]; hasMore: boolean }> => { return ipcRenderer.invoke('ai:getMessagesAfter', sessionId, afterId, limit, filter, senderId, keywords) }, // ==================== 自定义筛选(支持分页) ==================== /** * 筛选结果消息类型 */ // FilterMessage 和 FilterResult 类型定义在下方 /** * 按条件筛选消息并扩充上下文(支持分页) * @param page 页码(从 1 开始,默认 1) * @param pageSize 每页块数(默认 50) */ filterMessagesWithContext: ( sessionId: string, keywords?: string[], timeFilter?: { startTs: number; endTs: number }, senderIds?: number[], contextSize?: number, page?: number, pageSize?: number ): Promise<{ blocks: Array<{ startTs: number endTs: number messages: Array<{ id: number senderName: string senderPlatformId: string senderAliases: string[] senderAvatar: string | null content: string timestamp: number type: number replyToMessageId: string | null replyToContent: string | null replyToSenderName: string | null isHit: boolean }> hitCount: number }> stats: { totalMessages: number hitMessages: number totalChars: number } pagination: { page: number pageSize: number totalBlocks: number totalHits: number hasMore: boolean } }> => { return ipcRenderer.invoke( 'ai:filterMessagesWithContext', sessionId, keywords, timeFilter, senderIds, contextSize, page, pageSize ) }, /** * 获取多个会话的完整消息(支持分页) * @param page 页码(从 1 开始,默认 1) * @param pageSize 每页块数(默认 50) */ getMultipleSessionsMessages: ( sessionId: string, chatSessionIds: number[], page?: number, pageSize?: number ): Promise<{ blocks: Array<{ startTs: number endTs: number messages: Array<{ id: number senderName: string senderPlatformId: string senderAliases: string[] senderAvatar: string | null content: string timestamp: number type: number replyToMessageId: string | null replyToContent: string | null replyToSenderName: string | null isHit: boolean }> hitCount: number }> stats: { totalMessages: number hitMessages: number totalChars: number } pagination: { page: number pageSize: number totalBlocks: number totalHits: number hasMore: boolean } }> => { return ipcRenderer.invoke('ai:getMultipleSessionsMessages', sessionId, chatSessionIds, page, pageSize) }, /** * 导出筛选结果到文件(后端生成,支持大数据量) */ exportFilterResultToFile: (params: { sessionId: string sessionName: string outputDir: string filterMode: 'condition' | 'session' keywords?: string[] timeFilter?: { startTs: number; endTs: number } senderIds?: number[] contextSize?: number chatSessionIds?: number[] }): Promise<{ success: boolean; filePath?: string; error?: string }> => { return ipcRenderer.invoke('ai:exportFilterResultToFile', params) }, /** * 监听导出进度 */ onExportProgress: (callback: (progress: ExportProgress) => void) => { const handler = (_event: Electron.IpcRendererEvent, progress: ExportProgress) => { callback(progress) } ipcRenderer.on('ai:exportProgress', handler) return () => { ipcRenderer.removeListener('ai:exportProgress', handler) } }, /** * 创建 AI 对话 */ createConversation: (sessionId: string, title?: string): Promise => { return ipcRenderer.invoke('ai:createConversation', sessionId, title) }, /** * 获取会话的所有 AI 对话列表 */ getConversations: (sessionId: string): Promise => { return ipcRenderer.invoke('ai:getConversations', sessionId) }, /** * 获取单个 AI 对话 */ getConversation: (conversationId: string): Promise => { return ipcRenderer.invoke('ai:getConversation', conversationId) }, /** * 更新 AI 对话标题 */ updateConversationTitle: (conversationId: string, title: string): Promise => { return ipcRenderer.invoke('ai:updateConversationTitle', conversationId, title) }, /** * 删除 AI 对话 */ deleteConversation: (conversationId: string): Promise => { return ipcRenderer.invoke('ai:deleteConversation', conversationId) }, /** * 添加 AI 消息 */ addMessage: ( conversationId: string, role: 'user' | 'assistant', content: string, dataKeywords?: string[], dataMessageCount?: number, contentBlocks?: ContentBlock[] ): Promise => { return ipcRenderer.invoke( 'ai:addMessage', conversationId, role, content, dataKeywords, dataMessageCount, contentBlocks ) }, /** * 获取 AI 对话的所有消息 */ getMessages: (conversationId: string): Promise => { return ipcRenderer.invoke('ai:getMessages', conversationId) }, /** * 删除 AI 消息 */ deleteMessage: (messageId: string): Promise => { return ipcRenderer.invoke('ai:deleteMessage', messageId) }, /** * 打开当前 AI 日志文件并定位到文件 */ showAiLogFile: (): Promise<{ success: boolean; path?: string; error?: string }> => { return ipcRenderer.invoke('ai:showLogFile') }, } // ==================== LLM API ==================== export const llmApi = { /** * 获取所有支持的 LLM 提供商 */ getProviders: (): Promise => { return ipcRenderer.invoke('llm:getProviders') }, // ==================== 多配置管理 API ==================== /** * 获取所有配置列表 */ getAllConfigs: (): Promise => { return ipcRenderer.invoke('llm:getAllConfigs') }, /** * 获取当前激活的配置 ID */ getActiveConfigId: (): Promise => { return ipcRenderer.invoke('llm:getActiveConfigId') }, /** * 添加新配置 */ addConfig: (config: { name: string provider: string apiKey: string model?: string baseUrl?: string maxTokens?: number disableThinking?: boolean isReasoningModel?: boolean }): Promise<{ success: boolean; config?: AIServiceConfigDisplay; error?: string }> => { return ipcRenderer.invoke('llm:addConfig', config) }, /** * 更新配置 */ updateConfig: ( id: string, updates: { name?: string provider?: string apiKey?: string model?: string baseUrl?: string maxTokens?: number disableThinking?: boolean isReasoningModel?: boolean } ): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('llm:updateConfig', id, updates) }, /** * 删除配置 */ deleteConfig: (id?: string): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('llm:deleteConfig', id) }, /** * 设置激活的配置 */ setActiveConfig: (id: string): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('llm:setActiveConfig', id) }, /** * 验证 API Key(支持自定义 baseUrl 和 model) */ validateApiKey: (provider: string, apiKey: string, baseUrl?: string, model?: string): Promise => { return ipcRenderer.invoke('llm:validateApiKey', provider, apiKey, baseUrl, model) }, /** * 检查是否已配置 LLM(是否有激活的配置) */ hasConfig: (): Promise => { return ipcRenderer.invoke('llm:hasConfig') }, /** * 发送 LLM 聊天请求(非流式) */ chat: ( messages: ChatMessage[], options?: ChatOptions ): Promise<{ success: boolean; content?: string; error?: string }> => { return ipcRenderer.invoke('llm:chat', messages, options) }, /** * 发送 LLM 聊天请求(流式) * 返回一个 Promise,该 Promise 在流完成后才 resolve */ chatStream: ( messages: ChatMessage[], options?: ChatOptions, onChunk?: (chunk: ChatStreamChunk) => void ): Promise<{ success: boolean; error?: string }> => { return new Promise((resolve) => { const requestId = `llm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` console.log('[preload] chatStream 开始,requestId:', requestId) const handler = ( _event: Electron.IpcRendererEvent, data: { requestId: string; chunk: ChatStreamChunk; error?: string } ) => { if (data.requestId === requestId) { if (data.error) { console.log('[preload] chatStream 收到错误:', data.error) if (onChunk) { onChunk({ content: '', isFinished: true, finishReason: 'error' }) } ipcRenderer.removeListener('llm:streamChunk', handler) resolve({ success: false, error: data.error }) } else { if (onChunk) { onChunk(data.chunk) } // 如果已完成,移除监听器并 resolve if (data.chunk.isFinished) { console.log('[preload] chatStream 完成,requestId:', requestId) ipcRenderer.removeListener('llm:streamChunk', handler) resolve({ success: true }) } } } } ipcRenderer.on('llm:streamChunk', handler) // 发起请求 ipcRenderer .invoke('llm:chatStream', requestId, messages, options) .then((result) => { console.log('[preload] chatStream invoke 返回:', result) if (!result.success) { ipcRenderer.removeListener('llm:streamChunk', handler) resolve(result) } // 如果 success,等待流完成(由 handler 处理 resolve) }) .catch((error) => { console.error('[preload] chatStream invoke 错误:', error) ipcRenderer.removeListener('llm:streamChunk', handler) resolve({ success: false, error: String(error) }) }) }) }, } // ==================== Agent API ==================== export const agentApi = { /** * 执行 Agent 对话(流式) * Agent 会自动调用工具获取数据并生成回答 * @param historyMessages 对话历史(可选,用于上下文关联) * @param chatType 聊天类型('group' | 'private') * @param promptConfig 用户自定义提示词配置(可选) * @param locale 语言设置(可选,默认 'zh-CN') * @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 ): { 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, 'chatType:', chatType ?? 'group', 'hasPromptConfig:', !!promptConfig ) const promise = new Promise<{ success: boolean; result?: AgentResult; error?: string }>((resolve) => { // 监听流式 chunks const chunkHandler = ( _event: Electron.IpcRendererEvent, data: { requestId: string; chunk: AgentStreamChunk } ) => { if (data.requestId === requestId) { if (onChunk) { onChunk(data.chunk) } } } // 监听完成事件 const completeHandler = ( _event: Electron.IpcRendererEvent, data: { requestId: string; result: AgentResult & { error?: string } } ) => { if (data.requestId === requestId) { console.log('[preload] Agent 完成,requestId:', requestId, 'hasError:', !!data.result?.error) ipcRenderer.removeListener('agent:streamChunk', chunkHandler) ipcRenderer.removeListener('agent:complete', completeHandler) // 如果 result 中包含 error,返回失败状态 if (data.result?.error) { resolve({ success: false, error: data.result.error }) } else { resolve({ success: true, result: data.result }) } } } ipcRenderer.on('agent:streamChunk', chunkHandler) ipcRenderer.on('agent:complete', completeHandler) // 发起请求(传递历史消息、聊天类型、提示词配置和语言设置) ipcRenderer .invoke('agent:runStream', requestId, userMessage, context, historyMessages, chatType, promptConfig, locale) .then((result) => { console.log('[preload] Agent invoke 返回:', result) if (!result.success) { ipcRenderer.removeListener('agent:streamChunk', chunkHandler) ipcRenderer.removeListener('agent:complete', completeHandler) resolve(result) } // 如果 success,等待完成(由 completeHandler 处理 resolve) }) .catch((error) => { console.error('[preload] Agent invoke 错误:', error) ipcRenderer.removeListener('agent:streamChunk', chunkHandler) ipcRenderer.removeListener('agent:complete', completeHandler) resolve({ success: false, error: String(error) }) }) }) return { requestId, promise } }, /** * 中止 Agent 请求 * @param requestId 请求 ID */ abort: (requestId: string): Promise<{ success: boolean; error?: string }> => { console.log('[preload] Agent abort 请求,requestId:', requestId) return ipcRenderer.invoke('agent:abort', requestId) }, } // ==================== Embedding API ==================== export const embeddingApi = { /** * 获取所有 Embedding 配置(展示用) */ getAllConfigs: (): Promise => { return ipcRenderer.invoke('embedding:getAllConfigs') }, /** * 获取单个 Embedding 配置(用于编辑) */ getConfig: (id: string): Promise => { return ipcRenderer.invoke('embedding:getConfig', id) }, /** * 获取激活的配置 ID */ getActiveConfigId: (): Promise => { return ipcRenderer.invoke('embedding:getActiveConfigId') }, /** * 检查语义搜索是否启用 */ isEnabled: (): Promise => { return ipcRenderer.invoke('embedding:isEnabled') }, /** * 添加 Embedding 配置 */ addConfig: ( config: Omit ): Promise<{ success: boolean; config?: EmbeddingServiceConfig; error?: string }> => { return ipcRenderer.invoke('embedding:addConfig', config) }, /** * 更新 Embedding 配置 */ updateConfig: ( id: string, updates: Partial> ): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('embedding:updateConfig', id, updates) }, /** * 删除 Embedding 配置 */ deleteConfig: (id: string): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('embedding:deleteConfig', id) }, /** * 设置激活的配置 */ setActiveConfig: (id: string): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('embedding:setActiveConfig', id) }, /** * 验证 Embedding 配置 */ validateConfig: (config: EmbeddingServiceConfig): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('embedding:validateConfig', config) }, /** * 获取向量存储统计信息 */ getVectorStoreStats: (): Promise<{ enabled: boolean; count?: number; sizeBytes?: number }> => { return ipcRenderer.invoke('rag:getVectorStoreStats') }, /** * 清空向量存储 */ clearVectorStore: (): Promise<{ success: boolean; error?: string }> => { return ipcRenderer.invoke('rag:clearVectorStore') }, }