feat: 支持AI分析

This commit is contained in:
digua
2025-12-03 00:42:07 +08:00
parent 3d7cadc123
commit 5a2bab52be
29 changed files with 4022 additions and 4 deletions

View File

@@ -81,13 +81,121 @@ interface MergeApi {
onParseProgress: (callback: (data: { filePath: string; progress: ImportProgress }) => void) => () => void
}
// AI 相关类型
interface SearchMessageResult {
id: number
senderName: string
senderPlatformId: string
content: string
timestamp: number
type: number
}
interface AIConversation {
id: string
sessionId: string
title: string | null
createdAt: number
updatedAt: number
}
interface AIMessage {
id: string
conversationId: string
role: 'user' | 'assistant'
content: string
timestamp: number
dataKeywords?: string[]
dataMessageCount?: number
}
interface AiApi {
searchMessages: (
sessionId: string,
keywords: string[],
filter?: TimeFilter,
limit?: number,
offset?: number
) => Promise<{ messages: SearchMessageResult[]; total: number }>
getMessageContext: (sessionId: string, messageId: number, contextSize?: number) => Promise<SearchMessageResult[]>
createConversation: (sessionId: string, title?: string) => Promise<AIConversation>
getConversations: (sessionId: string) => Promise<AIConversation[]>
getConversation: (conversationId: string) => Promise<AIConversation | null>
updateConversationTitle: (conversationId: string, title: string) => Promise<boolean>
deleteConversation: (conversationId: string) => Promise<boolean>
addMessage: (
conversationId: string,
role: 'user' | 'assistant',
content: string,
dataKeywords?: string[],
dataMessageCount?: number
) => Promise<AIMessage>
getMessages: (conversationId: string) => Promise<AIMessage[]>
deleteMessage: (messageId: string) => Promise<boolean>
}
// LLM 相关类型
interface LLMProviderInfo {
id: string
name: string
description: string
defaultBaseUrl: string
models: Array<{ id: string; name: string; description?: string }>
}
interface LLMConfig {
provider: string
apiKey: string
apiKeySet: boolean
model?: string
maxTokens?: number
}
interface LLMChatMessage {
role: 'system' | 'user' | 'assistant'
content: string
}
interface LLMChatOptions {
temperature?: number
maxTokens?: number
}
interface LLMChatStreamChunk {
content: string
isFinished: boolean
finishReason?: 'stop' | 'length' | 'error'
}
interface LlmApi {
getProviders: () => Promise<LLMProviderInfo[]>
getConfig: () => Promise<LLMConfig | null>
saveConfig: (config: {
provider: string
apiKey: string
model?: string
maxTokens?: number
}) => Promise<{ success: boolean; error?: string }>
deleteConfig: () => Promise<boolean>
validateApiKey: (provider: string, apiKey: string) => Promise<boolean>
hasConfig: () => Promise<boolean>
chat: (messages: LLMChatMessage[], options?: LLMChatOptions) => Promise<{ success: boolean; content?: string; error?: string }>
chatStream: (
messages: LLMChatMessage[],
options?: LLMChatOptions,
onChunk?: (chunk: LLMChatStreamChunk) => void
) => Promise<{ success: boolean; error?: string }>
}
declare global {
interface Window {
electron: ElectronAPI
api: Api
chatApi: ChatApi
mergeApi: MergeApi
aiApi: AiApi
llmApi: LlmApi
}
}
export { ChatApi, Api, MergeApi }
export { ChatApi, Api, MergeApi, AiApi, LlmApi, SearchMessageResult, AIConversation, AIMessage, LLMProviderInfo, LLMConfig, LLMChatMessage, LLMChatOptions, LLMChatStreamChunk }

View File

@@ -42,7 +42,7 @@ const api = {
}
},
receive: (channel: string, func: (...args: unknown[]) => void) => {
const validChannels = ['show-message', 'chat:importProgress', 'merge:parseProgress']
const validChannels = ['show-message', 'chat:importProgress', 'merge:parseProgress', 'llm:streamChunk']
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (_event, ...args) => func(...args))
@@ -332,6 +332,265 @@ const mergeApi = {
},
}
// AI API - AI 功能
interface SearchMessageResult {
id: number
senderName: string
senderPlatformId: string
content: string
timestamp: number
type: number
}
interface AIConversation {
id: string
sessionId: string
title: string | null
createdAt: number
updatedAt: number
}
interface AIMessage {
id: string
conversationId: string
role: 'user' | 'assistant'
content: string
timestamp: number
dataKeywords?: string[]
dataMessageCount?: number
}
const aiApi = {
/**
* 搜索消息(关键词搜索)
*/
searchMessages: (
sessionId: string,
keywords: string[],
filter?: { startTs?: number; endTs?: number },
limit?: number,
offset?: number
): Promise<{ messages: SearchMessageResult[]; total: number }> => {
return ipcRenderer.invoke('ai:searchMessages', sessionId, keywords, filter, limit, offset)
},
/**
* 获取消息上下文
*/
getMessageContext: (sessionId: string, messageId: number, contextSize?: number): Promise<SearchMessageResult[]> => {
return ipcRenderer.invoke('ai:getMessageContext', sessionId, messageId, contextSize)
},
/**
* 创建 AI 对话
*/
createConversation: (sessionId: string, title?: string): Promise<AIConversation> => {
return ipcRenderer.invoke('ai:createConversation', sessionId, title)
},
/**
* 获取会话的所有 AI 对话列表
*/
getConversations: (sessionId: string): Promise<AIConversation[]> => {
return ipcRenderer.invoke('ai:getConversations', sessionId)
},
/**
* 获取单个 AI 对话
*/
getConversation: (conversationId: string): Promise<AIConversation | null> => {
return ipcRenderer.invoke('ai:getConversation', conversationId)
},
/**
* 更新 AI 对话标题
*/
updateConversationTitle: (conversationId: string, title: string): Promise<boolean> => {
return ipcRenderer.invoke('ai:updateConversationTitle', conversationId, title)
},
/**
* 删除 AI 对话
*/
deleteConversation: (conversationId: string): Promise<boolean> => {
return ipcRenderer.invoke('ai:deleteConversation', conversationId)
},
/**
* 添加 AI 消息
*/
addMessage: (
conversationId: string,
role: 'user' | 'assistant',
content: string,
dataKeywords?: string[],
dataMessageCount?: number
): Promise<AIMessage> => {
return ipcRenderer.invoke('ai:addMessage', conversationId, role, content, dataKeywords, dataMessageCount)
},
/**
* 获取 AI 对话的所有消息
*/
getMessages: (conversationId: string): Promise<AIMessage[]> => {
return ipcRenderer.invoke('ai:getMessages', conversationId)
},
/**
* 删除 AI 消息
*/
deleteMessage: (messageId: string): Promise<boolean> => {
return ipcRenderer.invoke('ai:deleteMessage', messageId)
},
}
// LLM API - LLM 服务功能
interface LLMProvider {
id: string
name: string
description: string
defaultBaseUrl: string
models: Array<{ id: string; name: string; description?: string }>
}
interface LLMConfig {
provider: string
apiKey: string
apiKeySet: boolean
model?: string
maxTokens?: number
}
interface ChatMessage {
role: 'system' | 'user' | 'assistant'
content: string
}
interface ChatOptions {
temperature?: number
maxTokens?: number
}
interface ChatStreamChunk {
content: string
isFinished: boolean
finishReason?: 'stop' | 'length' | 'error'
}
const llmApi = {
/**
* 获取所有支持的 LLM 提供商
*/
getProviders: (): Promise<LLMProvider[]> => {
return ipcRenderer.invoke('llm:getProviders')
},
/**
* 获取当前 LLM 配置
*/
getConfig: (): Promise<LLMConfig | null> => {
return ipcRenderer.invoke('llm:getConfig')
},
/**
* 保存 LLM 配置
*/
saveConfig: (config: {
provider: string
apiKey: string
model?: string
maxTokens?: number
}): Promise<{ success: boolean; error?: string }> => {
return ipcRenderer.invoke('llm:saveConfig', config)
},
/**
* 删除 LLM 配置
*/
deleteConfig: (): Promise<boolean> => {
return ipcRenderer.invoke('llm:deleteConfig')
},
/**
* 验证 API Key
*/
validateApiKey: (provider: string, apiKey: string): Promise<boolean> => {
return ipcRenderer.invoke('llm:validateApiKey', provider, apiKey)
},
/**
* 检查是否已配置 LLM
*/
hasConfig: (): Promise<boolean> => {
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) })
})
})
},
}
// 扩展 api添加 dialog 功能
const extendedApi = {
...api,
@@ -351,6 +610,8 @@ if (process.contextIsolated) {
contextBridge.exposeInMainWorld('api', extendedApi)
contextBridge.exposeInMainWorld('chatApi', chatApi)
contextBridge.exposeInMainWorld('mergeApi', mergeApi)
contextBridge.exposeInMainWorld('aiApi', aiApi)
contextBridge.exposeInMainWorld('llmApi', llmApi)
} catch (error) {
console.error(error)
}
@@ -363,4 +624,8 @@ if (process.contextIsolated) {
window.chatApi = chatApi
// @ts-ignore (define in dts)
window.mergeApi = mergeApi
// @ts-ignore (define in dts)
window.aiApi = aiApi
// @ts-ignore (define in dts)
window.llmApi = llmApi
}