mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
refactor(ai): remove orphaned AI conversation/message IPC handlers
Migrate conversation/message CRUD and debug queries from IPC to HTTP by having ElectronAIAdapter extend FetchAIAdapter. Remove 18 dead handlers (14 CRUD + 3 debug + 1 compressContext) from ipc/ai.ts and slim down preload AiApi to IPC-only methods (filtering, export, tool testing, desensitize rules, native shell). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
1310b04bc0
commit
fcca22a8dc
4 files changed
+74
-833
No files matched your search
+5
-307
@@ -2,7 +2,6 @@
|
||||
import { ipcMain, shell } from 'electron'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as aiConversations from '../ai/conversations'
|
||||
import * as llm from '../ai/llm'
|
||||
import { aiLogger, setDebugMode } from '../ai/logger'
|
||||
import { serializeError } from '../ai/serialize-error'
|
||||
@@ -19,7 +18,6 @@ import type { AssistantConfig } from '../ai/assistant/types'
|
||||
import * as skillManager from '../ai/skills'
|
||||
import {
|
||||
checkAndCompress,
|
||||
manualCompress,
|
||||
createCompressionLlmAdapter,
|
||||
type CompressionConfig,
|
||||
type CompressionLlmAdapter,
|
||||
@@ -56,7 +54,6 @@ function buildCompressionAdapter(activeAIConfig: AIServiceConfig, onCompressing?
|
||||
}
|
||||
|
||||
function toPiSimpleMessages(messages: Array<{ role: string; content: string }>, timestamp: number): PiMessage[] {
|
||||
// pi-ai 的 simple API 在类型上要求完整 Message 联合,这里沿用现有轻量消息格式并集中做兼容转换。
|
||||
return messages.map((message) => ({
|
||||
role: message.role as 'user' | 'assistant',
|
||||
content: message.content,
|
||||
@@ -65,7 +62,6 @@ function toPiSimpleMessages(messages: Array<{ role: string; content: string }>,
|
||||
}
|
||||
|
||||
// ==================== AI Agent 请求追踪 ====================
|
||||
// 用于跟踪活跃的 Agent 请求,支持中止操作
|
||||
const activeAgentRequests = new Map<string, AbortController>()
|
||||
|
||||
function resolveProviderName(provider?: llm.LLMProvider): string {
|
||||
@@ -76,7 +72,6 @@ function resolveProviderName(provider?: llm.LLMProvider): string {
|
||||
export function registerAIHandlers({ win }: IpcContext): void {
|
||||
console.log('[IPC] Registering AI handlers...')
|
||||
|
||||
// 初始化助手管理器(同步内置助手、加载用户助手)
|
||||
try {
|
||||
assistantManager.initAssistantManager()
|
||||
console.log('[IPC] Assistant manager initialized')
|
||||
@@ -84,7 +79,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
console.error('[IPC] Failed to initialize assistant manager:', error)
|
||||
}
|
||||
|
||||
// 初始化技能管理器(扫描用户技能文件)
|
||||
try {
|
||||
skillManager.initSkillManager()
|
||||
console.log('[IPC] Skill manager initialized')
|
||||
@@ -99,53 +93,10 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
aiLogger.info('Config', `Debug mode ${enabled ? 'enabled' : 'disabled'}`)
|
||||
})
|
||||
|
||||
ipcMain.handle('ai:clearDebugContext', async () => {
|
||||
try {
|
||||
const cleared = aiConversations.clearAllDebugContext()
|
||||
aiLogger.info('Debug', `Cleared debug_context for ${cleared} messages`)
|
||||
return { success: true, cleared }
|
||||
} catch (error) {
|
||||
console.error('Failed to clear debug context:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
// ==================== AI 日志 ====================
|
||||
|
||||
// ==================== AI 对话管理 ====================
|
||||
|
||||
/**
|
||||
* 创建新的 AI 对话
|
||||
* 参数契约与 preload / 数据层保持一致:(sessionId, title?)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'ai:createConversation',
|
||||
async (_, sessionId: string, title: string | undefined, assistantId: string) => {
|
||||
try {
|
||||
return aiConversations.createConversation(sessionId, title, assistantId)
|
||||
} catch (error) {
|
||||
console.error('Failed to create AI conversation:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取所有 AI 对话列表
|
||||
*/
|
||||
ipcMain.handle('ai:getConversations', async (_, sessionId: string) => {
|
||||
try {
|
||||
return aiConversations.getConversations(sessionId)
|
||||
} catch (error) {
|
||||
console.error('Failed to get AI conversations:', error)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开当前 AI 日志文件并定位到文件
|
||||
*/
|
||||
ipcMain.handle('ai:showLogFile', async () => {
|
||||
try {
|
||||
// 优先使用当前已存在的日志文件,避免创建新的空日志
|
||||
const existingLogPath = aiLogger.getExistingLogPath()
|
||||
if (existingLogPath) {
|
||||
shell.showItemInFolder(existingLogPath)
|
||||
@@ -163,7 +114,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
return { success: false, error: 'No AI log files found' }
|
||||
}
|
||||
|
||||
// 选择最近修改的日志文件
|
||||
const latestLog = logFiles
|
||||
.map((name) => {
|
||||
const filePath = path.join(logDir, name)
|
||||
@@ -180,176 +130,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取单个对话详情
|
||||
*/
|
||||
ipcMain.handle('ai:getConversation', async (_, conversationId: string) => {
|
||||
try {
|
||||
return aiConversations.getConversation(conversationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to get AI conversation details:', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 更新 AI 对话标题
|
||||
*/
|
||||
ipcMain.handle('ai:updateConversationTitle', async (_, conversationId: string, title: string) => {
|
||||
try {
|
||||
return aiConversations.updateConversationTitle(conversationId, title)
|
||||
} catch (error) {
|
||||
console.error('Failed to update AI conversation title:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 删除 AI 对话
|
||||
*/
|
||||
ipcMain.handle('ai:deleteConversation', async (_, conversationId: string) => {
|
||||
try {
|
||||
return aiConversations.deleteConversation(conversationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete AI conversation:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 添加 AI 消息
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'ai:addMessage',
|
||||
async (
|
||||
_,
|
||||
conversationId: string,
|
||||
role: aiConversations.AIMessageRole,
|
||||
content: string,
|
||||
dataKeywords?: string[],
|
||||
dataMessageCount?: number,
|
||||
contentBlocks?: aiConversations.ContentBlock[],
|
||||
tokenUsage?: aiConversations.TokenUsageData
|
||||
) => {
|
||||
try {
|
||||
return aiConversations.addMessage(
|
||||
conversationId,
|
||||
role,
|
||||
content,
|
||||
dataKeywords,
|
||||
dataMessageCount,
|
||||
contentBlocks,
|
||||
tokenUsage
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to add AI message:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取 AI 对话的所有消息
|
||||
*/
|
||||
ipcMain.handle('ai:getMessages', async (_, conversationId: string) => {
|
||||
try {
|
||||
return aiConversations.getMessages(conversationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to get AI messages:', error)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取对话的累计 token 使用量
|
||||
*/
|
||||
ipcMain.handle('ai:getConversationTokenUsage', async (_, conversationId: string) => {
|
||||
try {
|
||||
return aiConversations.getConversationTokenUsage(conversationId)
|
||||
} catch (error) {
|
||||
console.error('Failed to get conversation token usage:', error)
|
||||
return { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 删除 AI 消息
|
||||
*/
|
||||
ipcMain.handle('ai:deleteMessage', async (_, messageId: string) => {
|
||||
try {
|
||||
return aiConversations.deleteMessage(messageId)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete AI message:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('ai:deleteMessagesFrom', async (_, conversationId: string, messageId: string) => {
|
||||
try {
|
||||
return aiConversations.deleteMessagesFrom(conversationId, messageId)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete messages:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
'ai:forkConversation',
|
||||
async (_, sourceConversationId: string, upToMessageId: string, title?: string) => {
|
||||
try {
|
||||
return aiConversations.forkConversation(sourceConversationId, upToMessageId, title)
|
||||
} catch (error) {
|
||||
console.error('Failed to fork conversation:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle('ai:updateMessageContent', async (_, messageId: string, newContent: string) => {
|
||||
try {
|
||||
return aiConversations.updateMessageContent(messageId, newContent)
|
||||
} catch (error) {
|
||||
console.error('Failed to update message content:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('ai:deleteAndRelinkMessage', async (_, conversationId: string, messageId: string) => {
|
||||
try {
|
||||
return aiConversations.deleteAndRelinkMessage(conversationId, messageId)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete and relink message:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(
|
||||
'ai:insertMessageAfter',
|
||||
async (
|
||||
_,
|
||||
conversationId: string,
|
||||
afterMessageId: string,
|
||||
role: aiConversations.AIMessageRole,
|
||||
content: string,
|
||||
contentBlocks?: aiConversations.ContentBlock[],
|
||||
tokenUsage?: aiConversations.TokenUsageData
|
||||
) => {
|
||||
try {
|
||||
return aiConversations.insertMessageAfter(
|
||||
conversationId,
|
||||
afterMessageId,
|
||||
role,
|
||||
content,
|
||||
contentBlocks,
|
||||
tokenUsage
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to insert message after:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ==================== 脱敏规则 ====================
|
||||
|
||||
ipcMain.handle('ai:getDefaultDesensitizeRules', (_, locale: string) => {
|
||||
@@ -360,14 +140,8 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
return mergeRulesForLocale(existingRules as any[], locale)
|
||||
})
|
||||
|
||||
// LLM CRUD (config, provider, model, validate, etc.) has been migrated to
|
||||
// HTTP service layer via shared routes. Only chat APIs remain on IPC.
|
||||
// ==================== LLM 直接调用 API(SQLLab 等非 Agent 场景) ====================
|
||||
|
||||
// ==================== LLM 直接调用 API(SQLLab 等非 Agent 场景使用) ====================
|
||||
|
||||
/**
|
||||
* 非流式 LLM 调用
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'llm:chat',
|
||||
async (
|
||||
@@ -411,9 +185,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 流式 LLM 调用(SQLLab AI 生成 / 结果总结等场景使用)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'llm:chatStream',
|
||||
async (
|
||||
@@ -454,9 +225,7 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
)
|
||||
|
||||
// Assistant and Skill CRUD has been migrated to HTTP service layer via shared routes.
|
||||
|
||||
// ==================== 工具测试 API(实验室 - 基础工具) ====================
|
||||
// ==================== 工具测试 API ====================
|
||||
|
||||
const activeToolTests = new Map<string, AbortController>()
|
||||
|
||||
@@ -546,14 +315,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
|
||||
// ==================== AI Agent API ====================
|
||||
|
||||
/**
|
||||
* 执行 Agent 对话(流式)
|
||||
* Agent 会自动调用工具并返回最终结果
|
||||
* Agent 通过 context.conversationId 从 SQLite 读取对话历史(数据流倒置)
|
||||
* @param chatType 聊天类型('group' | 'private')
|
||||
* @param locale 语言设置(可选,默认 'zh-CN')
|
||||
* @param assistantId 助手 ID(可选,传入时从 AssistantManager 获取配置)
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'agent:runStream',
|
||||
async (
|
||||
@@ -589,10 +350,8 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
const piModel = buildPiModel(activeAIConfig)
|
||||
|
||||
// 上下文压缩前置步骤(在 Agent 创建之前执行)
|
||||
if (compressionConfig?.enabled && context.conversationId && context.historyLeafMessageId === undefined) {
|
||||
try {
|
||||
// 获取助手 systemPrompt 用于 token 计算
|
||||
const tempAssistantConfig = assistantId
|
||||
? (assistantManager.getAssistantConfig(assistantId) ?? undefined)
|
||||
: undefined
|
||||
@@ -669,7 +428,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
: '(disabled)',
|
||||
})
|
||||
|
||||
// 提示词系统已退场,主流程统一从助手配置获取 systemPrompt。
|
||||
const defaultAssistantId = getDefaultGeneralAssistantId(locale)
|
||||
let resolvedAssistantId = assistantId || defaultAssistantId
|
||||
let assistantConfig: AssistantConfig | undefined =
|
||||
@@ -683,7 +441,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
assistantConfig = assistantManager.getAssistantConfig(defaultAssistantId) ?? undefined
|
||||
}
|
||||
|
||||
// 构建技能上下文
|
||||
let skillCtx: SkillContext | undefined
|
||||
if (skillId) {
|
||||
const skillDef = skillManager.getSkillConfig(skillId) ?? undefined
|
||||
@@ -701,7 +458,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
}
|
||||
|
||||
// 工具结果 token 预算注入:基于 context window 百分比计算
|
||||
const maxToolResultPercent = compressionConfig?.maxToolResultPercent ?? 50
|
||||
const modelDef = llm.findModelDefinition(activeAIConfig.provider, activeAIConfig.model || '')
|
||||
const resolvedContextWindow = modelDef?.contextWindow || 128000
|
||||
@@ -740,11 +496,9 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
skillCtx
|
||||
)
|
||||
|
||||
// 异步执行,通过事件发送流式数据
|
||||
;(async () => {
|
||||
try {
|
||||
const result = await agent.executeStream(userMessage, (chunk: AgentStreamChunk) => {
|
||||
// 如果已中止,不再发送
|
||||
if (abortController.signal.aborted) {
|
||||
return
|
||||
}
|
||||
@@ -769,7 +523,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
return
|
||||
}
|
||||
|
||||
// 发送完成信息
|
||||
win.webContents.send('agent:complete', {
|
||||
requestId,
|
||||
result: {
|
||||
@@ -804,12 +557,10 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
serializedError.url = activeAIConfig.baseUrl
|
||||
}
|
||||
aiLogger.error('IPC', `Agent execution error: ${requestId}`, serializedError)
|
||||
// 发送错误 chunk
|
||||
win.webContents.send('agent:streamChunk', {
|
||||
requestId,
|
||||
chunk: { type: 'error', error: serializedError, isFinished: true },
|
||||
})
|
||||
// 发送完成事件(带错误信息),确保前端 promise 能 resolve
|
||||
win.webContents.send('agent:complete', {
|
||||
requestId,
|
||||
result: {
|
||||
@@ -821,7 +572,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
// 清理请求追踪
|
||||
activeAgentRequests.delete(requestId)
|
||||
}
|
||||
})()
|
||||
@@ -834,9 +584,6 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 中止 Agent 请求
|
||||
*/
|
||||
ipcMain.handle('agent:abort', async (_, requestId: string) => {
|
||||
aiLogger.info('IPC', `Abort request received: ${requestId}`)
|
||||
|
||||
@@ -852,64 +599,15 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 上下文压缩 ====================
|
||||
|
||||
ipcMain.handle(
|
||||
'ai:compressContext',
|
||||
async (_, conversationId: string, compressionConfig: CompressionConfig, systemPrompt: string) => {
|
||||
try {
|
||||
const activeAIConfig = getDefaultAssistantConfig()
|
||||
if (!activeAIConfig) {
|
||||
return { success: false, error: t('llm.notConfigured') }
|
||||
}
|
||||
|
||||
const result = await manualCompress(
|
||||
conversationId,
|
||||
compressionConfig,
|
||||
systemPrompt,
|
||||
buildCompressionAdapter(activeAIConfig),
|
||||
getConversationManager(),
|
||||
compressionLogger
|
||||
)
|
||||
return { success: true, result }
|
||||
} catch (error) {
|
||||
aiLogger.error('IPC', 'Manual compression failed', { error: String(error) })
|
||||
return { success: false, error: String(error) }
|
||||
}
|
||||
}
|
||||
)
|
||||
// ==================== 上下文 token 估算 ====================
|
||||
|
||||
ipcMain.handle('ai:estimateContextTokens', async (_, conversationId: string) => {
|
||||
try {
|
||||
const history = aiConversations.getHistoryForAgent(conversationId)
|
||||
const history = getConversationManager().getHistoryForAgent(conversationId)
|
||||
const tokens = countMessagesTokens(history.map((m) => ({ role: m.role, content: m.content })))
|
||||
return { success: true, tokens, messageCount: history.length }
|
||||
} catch (error) {
|
||||
return { success: false, tokens: 0, error: String(error) }
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* [Debug] 获取 AI 数据库 Schema
|
||||
*/
|
||||
ipcMain.handle('ai:getAiSchema', async () => {
|
||||
try {
|
||||
return aiConversations.getAiSchema()
|
||||
} catch (error) {
|
||||
console.error('Failed to get AI schema:', error)
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* [Debug] 在 AI 数据库上执行原始 SQL
|
||||
*/
|
||||
ipcMain.handle('ai:executeAiSQL', async (_, sql: string) => {
|
||||
try {
|
||||
return aiConversations.executeAiSQL(sql)
|
||||
} catch (error: any) {
|
||||
console.error('Failed to execute AI SQL:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
+18
-273
@@ -1,59 +1,20 @@
|
||||
/**
|
||||
* AI 相关 API - AI 对话、LLM 服务、Agent、Embedding
|
||||
* AI 相关 API — 仅保留 IPC 必须的能力
|
||||
*
|
||||
* Conversation/message CRUD 和 debug 已迁移到 HTTP 共享路由(FetchAIAdapter),
|
||||
* 此处只保留需要 worker、native shell、工具注册表等 IPC 才能提供的功能。
|
||||
*/
|
||||
import { ipcRenderer } from 'electron'
|
||||
import type { ExportProgress } from '../../../../src/types/base'
|
||||
|
||||
// Agent API 类型 — 从 shared/types 统一导入
|
||||
export type { TokenUsage, AgentRuntimeStatus } from '../../shared/types'
|
||||
import type { TokenUsage, AgentRuntimeStatus, SerializedErrorInfo } from '../../shared/types'
|
||||
|
||||
export type { SerializedErrorInfo } from '../../shared/types'
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
export interface AIConversation {
|
||||
id: string
|
||||
sessionId: string
|
||||
title: string | null
|
||||
assistantId: string
|
||||
activeMessageId?: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
// 内容块类型(用于 AI 消息的混合渲染)
|
||||
export type ContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'think'; tag: string; text: string; durationMs?: number }
|
||||
| {
|
||||
type: 'tool'
|
||||
tool: {
|
||||
name: string
|
||||
displayName: string
|
||||
status: 'running' | 'done' | 'error'
|
||||
params?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
| { type: 'skill'; skillId: string; skillName: string }
|
||||
| { type: 'error'; error: SerializedErrorInfo }
|
||||
| { type: 'summary_meta'; bufferBoundaryTimestamp: number; compressedMessageCount: number }
|
||||
|
||||
export type AIMessageRole = 'user' | 'assistant' | 'summary'
|
||||
|
||||
export interface TokenUsageData {
|
||||
promptTokens: number
|
||||
completionTokens: number
|
||||
totalTokens: number
|
||||
}
|
||||
|
||||
export interface AIMessage {
|
||||
id: string
|
||||
conversationId: string
|
||||
role: AIMessageRole
|
||||
content: string
|
||||
timestamp: number
|
||||
parentId?: string | null
|
||||
dataKeywords?: string[]
|
||||
dataMessageCount?: number
|
||||
contentBlocks?: ContentBlock[]
|
||||
tokenUsage?: TokenUsageData
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'system' | 'user' | 'assistant'
|
||||
content: string
|
||||
@@ -73,12 +34,6 @@ export interface ChatStreamChunk {
|
||||
thinkingDone?: boolean
|
||||
}
|
||||
|
||||
// Agent API 类型 — 从 shared/types 统一导入
|
||||
export type { TokenUsage, AgentRuntimeStatus } from '../../shared/types'
|
||||
import type { TokenUsage, AgentRuntimeStatus, SerializedErrorInfo } from '../../shared/types'
|
||||
|
||||
export type { SerializedErrorInfo } from '../../shared/types'
|
||||
|
||||
export interface AgentStreamChunk {
|
||||
type: 'content' | 'think' | 'tool_start' | 'tool_result' | 'status' | 'compression_done' | 'done' | 'error'
|
||||
content?: string
|
||||
@@ -96,7 +51,6 @@ export interface AgentStreamChunk {
|
||||
tokensAfter: number
|
||||
timestamp: number
|
||||
}
|
||||
/** Token 使用量(type=done 时返回累计值) */
|
||||
usage?: TokenUsage
|
||||
}
|
||||
|
||||
@@ -107,7 +61,6 @@ export interface AgentResult {
|
||||
error?: SerializedErrorInfo
|
||||
}
|
||||
|
||||
/** 单条脱敏规则 */
|
||||
export interface DesensitizeRule {
|
||||
id: string
|
||||
label: string
|
||||
@@ -118,7 +71,6 @@ export interface DesensitizeRule {
|
||||
locales: string[]
|
||||
}
|
||||
|
||||
/** 工具目录条目(实验室 - 基础工具) */
|
||||
export interface ToolCatalogEntry {
|
||||
name: string
|
||||
category: 'core' | 'analysis'
|
||||
@@ -126,7 +78,6 @@ export interface ToolCatalogEntry {
|
||||
parameters: Record<string, unknown>
|
||||
}
|
||||
|
||||
/** 工具执行结果 */
|
||||
export interface ToolExecuteResult {
|
||||
success: boolean
|
||||
elapsed?: number
|
||||
@@ -136,7 +87,6 @@ export interface ToolExecuteResult {
|
||||
truncated?: boolean
|
||||
}
|
||||
|
||||
/** 聊天记录预处理配置 */
|
||||
export interface PreprocessConfig {
|
||||
dataCleaning: boolean
|
||||
mergeConsecutive: boolean
|
||||
@@ -166,10 +116,10 @@ export interface ToolContext {
|
||||
preprocessConfig?: PreprocessConfig
|
||||
}
|
||||
|
||||
// ==================== AI API ====================
|
||||
// ==================== AI API (IPC-only subset) ====================
|
||||
|
||||
export const aiApi = {
|
||||
// ==================== 消息筛选/导出(IPC handler 保留) ====================
|
||||
// ===== 消息筛选/导出(worker-dependent) =====
|
||||
|
||||
filterMessagesWithContext: (
|
||||
sessionId: string,
|
||||
@@ -224,11 +174,6 @@ export const aiApi = {
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取多个会话的完整消息(支持分页)
|
||||
* @param page 页码(从 1 开始,默认 1)
|
||||
* @param pageSize 每页块数(默认 50)
|
||||
*/
|
||||
getMultipleSessionsMessages: (
|
||||
sessionId: string,
|
||||
chatSessionIds: number[],
|
||||
@@ -270,9 +215,6 @@ export const aiApi = {
|
||||
return ipcRenderer.invoke('ai:getMultipleSessionsMessages', sessionId, chatSessionIds, page, pageSize)
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出筛选结果到文件(后端生成,支持大数据量)
|
||||
*/
|
||||
exportFilterResultToFile: (params: {
|
||||
sessionId: string
|
||||
sessionName: string
|
||||
@@ -287,9 +229,6 @@ export const aiApi = {
|
||||
return ipcRenderer.invoke('ai:exportFilterResultToFile', params)
|
||||
},
|
||||
|
||||
/**
|
||||
* 监听导出进度
|
||||
*/
|
||||
onExportProgress: (callback: (progress: ExportProgress) => void) => {
|
||||
const handler = (_event: Electron.IpcRendererEvent, progress: ExportProgress) => {
|
||||
callback(progress)
|
||||
@@ -300,134 +239,13 @@ export const aiApi = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建 AI 对话
|
||||
*/
|
||||
createConversation: (sessionId: string, title: string | undefined, assistantId: string): Promise<AIConversation> => {
|
||||
return ipcRenderer.invoke('ai:createConversation', sessionId, title, assistantId)
|
||||
},
|
||||
// ===== 日志(native shell) =====
|
||||
|
||||
/**
|
||||
* 获取会话的所有 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: AIMessageRole,
|
||||
content: string,
|
||||
dataKeywords?: string[],
|
||||
dataMessageCount?: number,
|
||||
contentBlocks?: ContentBlock[],
|
||||
tokenUsage?: TokenUsageData
|
||||
): Promise<AIMessage> => {
|
||||
return ipcRenderer.invoke(
|
||||
'ai:addMessage',
|
||||
conversationId,
|
||||
role,
|
||||
content,
|
||||
dataKeywords,
|
||||
dataMessageCount,
|
||||
contentBlocks,
|
||||
tokenUsage
|
||||
)
|
||||
},
|
||||
|
||||
deleteMessagesFrom: (conversationId: string, messageId: string): Promise<void> => {
|
||||
return ipcRenderer.invoke('ai:deleteMessagesFrom', conversationId, messageId)
|
||||
},
|
||||
|
||||
forkConversation: (sourceConversationId: string, upToMessageId: string, title?: string): Promise<AIConversation> => {
|
||||
return ipcRenderer.invoke('ai:forkConversation', sourceConversationId, upToMessageId, title)
|
||||
},
|
||||
|
||||
updateMessageContent: (messageId: string, newContent: string): Promise<void> => {
|
||||
return ipcRenderer.invoke('ai:updateMessageContent', messageId, newContent)
|
||||
},
|
||||
|
||||
deleteAndRelinkMessage: (conversationId: string, messageId: string): Promise<void> => {
|
||||
return ipcRenderer.invoke('ai:deleteAndRelinkMessage', conversationId, messageId)
|
||||
},
|
||||
|
||||
insertMessageAfter: (
|
||||
conversationId: string,
|
||||
afterMessageId: string,
|
||||
role: AIMessageRole,
|
||||
content: string,
|
||||
contentBlocks?: ContentBlock[],
|
||||
tokenUsage?: TokenUsageData
|
||||
): Promise<AIMessage> => {
|
||||
return ipcRenderer.invoke(
|
||||
'ai:insertMessageAfter',
|
||||
conversationId,
|
||||
afterMessageId,
|
||||
role,
|
||||
content,
|
||||
contentBlocks,
|
||||
tokenUsage
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取 AI 对话的所有消息
|
||||
*/
|
||||
getMessages: (conversationId: string): Promise<AIMessage[]> => {
|
||||
return ipcRenderer.invoke('ai:getMessages', conversationId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取对话的累计 token 使用量
|
||||
*/
|
||||
getConversationTokenUsage: (conversationId: string): Promise<TokenUsageData> => {
|
||||
return ipcRenderer.invoke('ai:getConversationTokenUsage', conversationId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除 AI 消息
|
||||
*/
|
||||
deleteMessage: (messageId: string): Promise<boolean> => {
|
||||
return ipcRenderer.invoke('ai:deleteMessage', messageId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开当前 AI 日志文件并定位到文件
|
||||
*/
|
||||
showAiLogFile: (): Promise<{ success: boolean; path?: string; error?: string }> => {
|
||||
return ipcRenderer.invoke('ai:showLogFile')
|
||||
},
|
||||
|
||||
/**
|
||||
* 一键清除所有消息的 debug_context 数据
|
||||
*/
|
||||
clearDebugContext: (): Promise<{ success: boolean; cleared: number }> => {
|
||||
return ipcRenderer.invoke('ai:clearDebugContext')
|
||||
},
|
||||
// ===== 脱敏规则(node-runtime) =====
|
||||
|
||||
getDefaultDesensitizeRules: (locale: string): Promise<DesensitizeRule[]> => {
|
||||
return ipcRenderer.invoke('ai:getDefaultDesensitizeRules', locale)
|
||||
@@ -437,6 +255,8 @@ export const aiApi = {
|
||||
return ipcRenderer.invoke('ai:mergeDesensitizeRules', existingRules, locale)
|
||||
},
|
||||
|
||||
// ===== 工具测试(tool registry on main process) =====
|
||||
|
||||
getToolCatalog: (): Promise<ToolCatalogEntry[]> => {
|
||||
return ipcRenderer.invoke('ai:getToolCatalog')
|
||||
},
|
||||
@@ -454,63 +274,18 @@ export const aiApi = {
|
||||
return ipcRenderer.invoke('ai:cancelToolTest', testId)
|
||||
},
|
||||
|
||||
// ===== 上下文估算(needs conversationManager) =====
|
||||
|
||||
estimateContextTokens: (
|
||||
conversationId: string
|
||||
): Promise<{ success: boolean; tokens: number; messageCount?: number; error?: string }> => {
|
||||
return ipcRenderer.invoke('ai:estimateContextTokens', conversationId)
|
||||
},
|
||||
|
||||
compressContext: (
|
||||
conversationId: string,
|
||||
compressionConfig: {
|
||||
enabled: boolean
|
||||
tokenThresholdPercent: number
|
||||
bufferSizePercent: number
|
||||
maxToolResultPercent?: number
|
||||
},
|
||||
systemPrompt: string
|
||||
): Promise<{
|
||||
success: boolean
|
||||
result?: {
|
||||
compressed: boolean
|
||||
reason: string
|
||||
tokensBefore?: number
|
||||
tokensAfter?: number
|
||||
error?: string
|
||||
}
|
||||
error?: string
|
||||
}> => {
|
||||
return ipcRenderer.invoke('ai:compressContext', conversationId, compressionConfig, systemPrompt)
|
||||
},
|
||||
|
||||
/**
|
||||
* [Debug] 获取 AI 数据库 Schema
|
||||
*/
|
||||
getAiSchema: (): Promise<
|
||||
Array<{ name: string; columns: Array<{ name: string; type: string; notnull: boolean; pk: boolean }> }>
|
||||
> => {
|
||||
return ipcRenderer.invoke('ai:getAiSchema')
|
||||
},
|
||||
|
||||
/**
|
||||
* [Debug] 在 AI 数据库上执行原始 SQL
|
||||
*/
|
||||
executeAiSQL: (
|
||||
sql: string
|
||||
): Promise<{ columns: string[]; rows: any[][]; rowCount: number; duration: number; limited: boolean }> => {
|
||||
return ipcRenderer.invoke('ai:executeAiSQL', sql)
|
||||
},
|
||||
}
|
||||
|
||||
// ==================== LLM API ====================
|
||||
|
||||
export const llmApi = {
|
||||
/**
|
||||
* LLM CRUD (config, provider, model, validate, etc.) has been migrated to
|
||||
* HTTP service layer via FetchLLMAdapter. Only streaming / non-streaming
|
||||
* chat APIs remain on IPC because they require real-time event forwarding.
|
||||
*/
|
||||
|
||||
chat: (
|
||||
messages: ChatMessage[],
|
||||
options?: ChatOptions
|
||||
@@ -544,7 +319,6 @@ export const llmApi = {
|
||||
onChunk(data.chunk)
|
||||
}
|
||||
|
||||
// 如果已完成,移除监听器并 resolve
|
||||
if (data.chunk.isFinished) {
|
||||
console.log('[preload] chatStream 完成,requestId:', requestId)
|
||||
ipcRenderer.removeListener('llm:streamChunk', handler)
|
||||
@@ -556,7 +330,6 @@ export const llmApi = {
|
||||
|
||||
ipcRenderer.on('llm:streamChunk', handler)
|
||||
|
||||
// 发起请求
|
||||
ipcRenderer
|
||||
.invoke('llm:chatStream', requestId, messages, options)
|
||||
.then((result) => {
|
||||
@@ -565,7 +338,6 @@ export const llmApi = {
|
||||
ipcRenderer.removeListener('llm:streamChunk', handler)
|
||||
resolve(result)
|
||||
}
|
||||
// 如果 success,等待流完成(由 handler 处理 resolve)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[preload] chatStream invoke 错误:', error)
|
||||
@@ -576,26 +348,9 @@ export const llmApi = {
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Assistant CRUD has been migrated to HTTP service layer via FetchAssistantAdapter.
|
||||
* The preload bridge is no longer needed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Skill CRUD has been migrated to HTTP service layer via FetchSkillAdapter.
|
||||
* The preload bridge is no longer needed.
|
||||
*/
|
||||
|
||||
// ==================== Agent API ====================
|
||||
|
||||
export const agentApi = {
|
||||
/**
|
||||
* 执行 Agent 对话(流式)
|
||||
* Agent 通过 context.conversationId 从后端 SQLite 读取对话历史
|
||||
* @param chatType 聊天类型('group' | 'private')
|
||||
* @param locale 语言设置(可选,默认 'zh-CN')
|
||||
* @returns 返回 { requestId, promise },requestId 可用于中止请求
|
||||
*/
|
||||
runStream: (
|
||||
userMessage: string,
|
||||
context: ToolContext,
|
||||
@@ -616,8 +371,6 @@ export const agentApi = {
|
||||
requestId: string
|
||||
promise: Promise<{ success: boolean; result?: AgentResult; error?: SerializedErrorInfo }>
|
||||
} => {
|
||||
// 防御性处理:确保传给 IPC 的 context 是“可结构化克隆”的纯对象
|
||||
// 避免调用方误传入响应式 Proxy(例如 Pinia/Vue state)导致 invoke 失败
|
||||
const sanitizedContext: ToolContext = {
|
||||
sessionId: context.sessionId,
|
||||
conversationId: context.conversationId,
|
||||
@@ -659,7 +412,6 @@ export const agentApi = {
|
||||
)
|
||||
|
||||
const promise = new Promise<{ success: boolean; result?: AgentResult; error?: SerializedErrorInfo }>((resolve) => {
|
||||
// 监听流式 chunks
|
||||
const chunkHandler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { requestId: string; chunk: AgentStreamChunk }
|
||||
@@ -671,7 +423,6 @@ export const agentApi = {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听完成事件
|
||||
const completeHandler = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
data: { requestId: string; result: AgentResult & { error?: SerializedErrorInfo } }
|
||||
@@ -680,7 +431,6 @@ export const agentApi = {
|
||||
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 {
|
||||
@@ -713,7 +463,6 @@ export const agentApi = {
|
||||
ipcRenderer.removeListener('agent:complete', completeHandler)
|
||||
resolve(result)
|
||||
}
|
||||
// 如果 success,等待完成(由 completeHandler 处理 resolve)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[preload] Agent invoke 错误:', error)
|
||||
@@ -733,10 +482,6 @@ export const agentApi = {
|
||||
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)
|
||||
|
||||
Vendored
+26
-132
@@ -153,124 +153,15 @@ interface FilterResultWithPagination extends FilterResult {
|
||||
pagination: PaginationInfo
|
||||
}
|
||||
|
||||
interface AIConversation {
|
||||
id: string
|
||||
sessionId: string
|
||||
title: string | null
|
||||
assistantId: string
|
||||
activeMessageId?: string | null
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
// 内容块类型(用于 AI 消息的混合渲染)
|
||||
type AIContentBlock =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'think'; tag: string; text: string; durationMs?: number }
|
||||
| {
|
||||
type: 'tool'
|
||||
tool: {
|
||||
name: string
|
||||
displayName: string
|
||||
status: 'running' | 'done' | 'error'
|
||||
params?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
| { type: 'skill'; skillId: string; skillName: string }
|
||||
| { type: 'error'; error: SerializedErrorInfo }
|
||||
| {
|
||||
type: 'summary_meta'
|
||||
bufferBoundaryTimestamp: number
|
||||
compressedMessageCount: number
|
||||
}
|
||||
|
||||
type AIMessageRole = 'user' | 'assistant' | 'summary'
|
||||
|
||||
interface AITokenUsageData {
|
||||
promptTokens: number
|
||||
completionTokens: number
|
||||
totalTokens: number
|
||||
}
|
||||
|
||||
interface AIMessage {
|
||||
id: string
|
||||
conversationId: string
|
||||
role: AIMessageRole
|
||||
content: string
|
||||
timestamp: number
|
||||
parentId?: string | null
|
||||
dataKeywords?: string[]
|
||||
dataMessageCount?: number
|
||||
contentBlocks?: AIContentBlock[]
|
||||
tokenUsage?: AITokenUsageData
|
||||
}
|
||||
|
||||
/**
|
||||
* AiApi — IPC-only subset
|
||||
*
|
||||
* Conversation/message CRUD and debug queries have been migrated to HTTP
|
||||
* shared routes (FetchAIAdapter via Internal Server). Only features that
|
||||
* require worker, native shell, or tool registry remain on IPC.
|
||||
*/
|
||||
interface AiApi {
|
||||
createConversation: (sessionId: string, title: string | undefined, assistantId: 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: AIMessageRole,
|
||||
content: string,
|
||||
dataKeywords?: string[],
|
||||
dataMessageCount?: number,
|
||||
contentBlocks?: AIContentBlock[],
|
||||
tokenUsage?: AITokenUsageData
|
||||
) => Promise<AIMessage>
|
||||
deleteMessagesFrom: (conversationId: string, messageId: string) => Promise<void>
|
||||
forkConversation: (sourceConversationId: string, upToMessageId: string, title?: string) => Promise<AIConversation>
|
||||
updateMessageContent: (messageId: string, newContent: string) => Promise<void>
|
||||
deleteAndRelinkMessage: (conversationId: string, messageId: string) => Promise<void>
|
||||
insertMessageAfter: (
|
||||
conversationId: string,
|
||||
afterMessageId: string,
|
||||
role: AIMessageRole,
|
||||
content: string,
|
||||
contentBlocks?: AIContentBlock[],
|
||||
tokenUsage?: AITokenUsageData
|
||||
) => Promise<AIMessage>
|
||||
getMessages: (conversationId: string) => Promise<AIMessage[]>
|
||||
getConversationTokenUsage: (conversationId: string) => Promise<AITokenUsageData>
|
||||
deleteMessage: (messageId: string) => Promise<boolean>
|
||||
showAiLogFile: () => Promise<{ success: boolean; path?: string; error?: string }>
|
||||
clearDebugContext: () => Promise<{ success: boolean; cleared: number }>
|
||||
getDefaultDesensitizeRules: (locale: string) => Promise<DesensitizeRule[]>
|
||||
mergeDesensitizeRules: (existingRules: DesensitizeRule[], locale: string) => Promise<DesensitizeRule[]>
|
||||
getToolCatalog: () => Promise<ToolCatalogEntry[]>
|
||||
executeTool: (
|
||||
testId: string,
|
||||
toolName: string,
|
||||
params: Record<string, unknown>,
|
||||
sessionId: string
|
||||
) => Promise<ToolExecuteResult>
|
||||
cancelToolTest: (testId: string) => Promise<{ success: boolean }>
|
||||
estimateContextTokens: (
|
||||
conversationId: string
|
||||
) => Promise<{ success: boolean; tokens: number; messageCount?: number; error?: string }>
|
||||
compressContext: (
|
||||
conversationId: string,
|
||||
compressionConfig: {
|
||||
enabled: boolean
|
||||
tokenThresholdPercent: number
|
||||
bufferSizePercent: number
|
||||
maxToolResultPercent?: number
|
||||
},
|
||||
systemPrompt: string
|
||||
) => Promise<{
|
||||
success: boolean
|
||||
result?: {
|
||||
compressed: boolean
|
||||
reason: string
|
||||
tokensBefore?: number
|
||||
tokensAfter?: number
|
||||
error?: string
|
||||
}
|
||||
error?: string
|
||||
}>
|
||||
// 自定义筛选(支持分页)
|
||||
// Message filtering / export (worker-dependent)
|
||||
filterMessagesWithContext: (
|
||||
sessionId: string,
|
||||
keywords?: string[],
|
||||
@@ -286,7 +177,6 @@ interface AiApi {
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
) => Promise<FilterResultWithPagination>
|
||||
// 导出筛选结果到文件
|
||||
exportFilterResultToFile: (params: {
|
||||
sessionId: string
|
||||
sessionName: string
|
||||
@@ -298,19 +188,25 @@ interface AiApi {
|
||||
contextSize?: number
|
||||
chatSessionIds?: number[]
|
||||
}) => Promise<{ success: boolean; filePath?: string; error?: string }>
|
||||
// 监听导出进度
|
||||
onExportProgress: (callback: (progress: ExportProgress) => void) => () => void
|
||||
// [Debug] AI 数据库直接访问
|
||||
getAiSchema: () => Promise<
|
||||
Array<{ name: string; columns: Array<{ name: string; type: string; notnull: boolean; pk: boolean }> }>
|
||||
>
|
||||
executeAiSQL: (sql: string) => Promise<{
|
||||
columns: string[]
|
||||
rows: any[][]
|
||||
rowCount: number
|
||||
duration: number
|
||||
limited: boolean
|
||||
}>
|
||||
// Native shell
|
||||
showAiLogFile: () => Promise<{ success: boolean; path?: string; error?: string }>
|
||||
// Desensitize rules (node-runtime)
|
||||
getDefaultDesensitizeRules: (locale: string) => Promise<DesensitizeRule[]>
|
||||
mergeDesensitizeRules: (existingRules: DesensitizeRule[], locale: string) => Promise<DesensitizeRule[]>
|
||||
// Tool testing (tool registry on main process)
|
||||
getToolCatalog: () => Promise<ToolCatalogEntry[]>
|
||||
executeTool: (
|
||||
testId: string,
|
||||
toolName: string,
|
||||
params: Record<string, unknown>,
|
||||
sessionId: string
|
||||
) => Promise<ToolExecuteResult>
|
||||
cancelToolTest: (testId: string) => Promise<{ success: boolean }>
|
||||
// Context estimation (needs conversationManager)
|
||||
estimateContextTokens: (
|
||||
conversationId: string
|
||||
) => Promise<{ success: boolean; tokens: number; messageCount?: number; error?: string }>
|
||||
}
|
||||
|
||||
// ==================== 新模型系统类型 ====================
|
||||
@@ -780,8 +676,6 @@ export {
|
||||
CacheApi,
|
||||
NetworkApi,
|
||||
ProxyConfig,
|
||||
AIConversation,
|
||||
AIMessage,
|
||||
LLMChatMessage,
|
||||
LLMChatOptions,
|
||||
LLMChatStreamChunk,
|
||||
|
||||
+25
-121
@@ -1,114 +1,31 @@
|
||||
import type {
|
||||
AIAdapter,
|
||||
AIConversation,
|
||||
AIMessage,
|
||||
AIMessageRole,
|
||||
ContentBlock,
|
||||
TokenUsageData,
|
||||
FilterResultWithPagination,
|
||||
ExportFilterParams,
|
||||
ExportProgress,
|
||||
AiSQLResult,
|
||||
AiSchemaTable,
|
||||
ToolCatalogEntry,
|
||||
ToolExecuteResult,
|
||||
DesensitizeRule,
|
||||
} from './types'
|
||||
import type { TimeFilter } from '@/types/base'
|
||||
import { FetchAIAdapter } from './fetch'
|
||||
|
||||
export class ElectronAIAdapter implements AIAdapter {
|
||||
// ===== 对话管理 =====
|
||||
async getConversation(conversationId: string): Promise<AIConversation | null> {
|
||||
return window.aiApi.getConversation(conversationId)
|
||||
}
|
||||
|
||||
async getConversations(sessionId: string): Promise<AIConversation[]> {
|
||||
return window.aiApi.getConversations(sessionId)
|
||||
}
|
||||
|
||||
async createConversation(sessionId: string, title: string | undefined, assistantId: string): Promise<AIConversation> {
|
||||
return window.aiApi.createConversation(sessionId, title, assistantId)
|
||||
}
|
||||
|
||||
async updateConversationTitle(conversationId: string, title: string): Promise<boolean> {
|
||||
return window.aiApi.updateConversationTitle(conversationId, title)
|
||||
}
|
||||
|
||||
async deleteConversation(conversationId: string): Promise<boolean> {
|
||||
return window.aiApi.deleteConversation(conversationId)
|
||||
}
|
||||
|
||||
// ===== 消息 =====
|
||||
async getMessages(conversationId: string): Promise<AIMessage[]> {
|
||||
return window.aiApi.getMessages(conversationId) as Promise<AIMessage[]>
|
||||
}
|
||||
|
||||
async addMessage(
|
||||
conversationId: string,
|
||||
role: AIMessageRole,
|
||||
content: string,
|
||||
dataKeywords?: string[],
|
||||
dataMessageCount?: number,
|
||||
contentBlocks?: ContentBlock[],
|
||||
tokenUsage?: TokenUsageData
|
||||
): Promise<AIMessage> {
|
||||
return window.aiApi.addMessage(
|
||||
conversationId,
|
||||
role as any,
|
||||
content,
|
||||
dataKeywords,
|
||||
dataMessageCount,
|
||||
contentBlocks as any,
|
||||
tokenUsage
|
||||
) as Promise<AIMessage>
|
||||
}
|
||||
|
||||
async deleteMessagesFrom(conversationId: string, messageId: string): Promise<void> {
|
||||
return window.aiApi.deleteMessagesFrom(conversationId, messageId)
|
||||
}
|
||||
|
||||
async forkConversation(sourceConversationId: string, upToMessageId: string, title?: string): Promise<AIConversation> {
|
||||
return window.aiApi.forkConversation(sourceConversationId, upToMessageId, title) as Promise<AIConversation>
|
||||
}
|
||||
|
||||
async updateMessageContent(messageId: string, newContent: string): Promise<void> {
|
||||
return window.aiApi.updateMessageContent(messageId, newContent)
|
||||
}
|
||||
|
||||
async deleteAndRelinkMessage(conversationId: string, messageId: string): Promise<void> {
|
||||
return window.aiApi.deleteAndRelinkMessage(conversationId, messageId)
|
||||
}
|
||||
|
||||
async insertMessageAfter(
|
||||
conversationId: string,
|
||||
afterMessageId: string,
|
||||
role: AIMessageRole,
|
||||
content: string,
|
||||
contentBlocks?: ContentBlock[],
|
||||
tokenUsage?: TokenUsageData
|
||||
): Promise<AIMessage> {
|
||||
return window.aiApi.insertMessageAfter(
|
||||
conversationId,
|
||||
afterMessageId,
|
||||
role,
|
||||
content,
|
||||
contentBlocks as any,
|
||||
tokenUsage
|
||||
) as Promise<AIMessage>
|
||||
}
|
||||
|
||||
async getConversationTokenUsage(conversationId: string): Promise<TokenUsageData> {
|
||||
return window.aiApi.getConversationTokenUsage(conversationId)
|
||||
}
|
||||
|
||||
async estimateContextTokens(
|
||||
/**
|
||||
* Electron AI Adapter
|
||||
*
|
||||
* Extends FetchAIAdapter so conversation/message CRUD and debug queries
|
||||
* go through the Internal HTTP Server (shared routes). Only features
|
||||
* that require IPC (worker, native shell, tool registry) are overridden.
|
||||
*/
|
||||
export class ElectronAIAdapter extends FetchAIAdapter {
|
||||
// ===== Context estimation (needs conversationManager on main process) =====
|
||||
override async estimateContextTokens(
|
||||
conversationId: string
|
||||
): Promise<{ success: boolean; tokens: number; messageCount?: number; error?: string }> {
|
||||
return window.aiApi.estimateContextTokens(conversationId)
|
||||
}
|
||||
|
||||
// ===== 消息筛选/导出 =====
|
||||
async filterMessagesWithContext(
|
||||
// ===== Message filtering / export (worker-dependent) =====
|
||||
override async filterMessagesWithContext(
|
||||
sessionId: string,
|
||||
keywords?: string[],
|
||||
timeFilter?: TimeFilter,
|
||||
@@ -128,7 +45,7 @@ export class ElectronAIAdapter implements AIAdapter {
|
||||
)
|
||||
}
|
||||
|
||||
async getMultipleSessionsMessages(
|
||||
override async getMultipleSessionsMessages(
|
||||
sessionId: string,
|
||||
chatSessionIds: number[],
|
||||
page?: number,
|
||||
@@ -137,35 +54,22 @@ export class ElectronAIAdapter implements AIAdapter {
|
||||
return window.aiApi.getMultipleSessionsMessages(sessionId, chatSessionIds, page, pageSize)
|
||||
}
|
||||
|
||||
async exportFilterResultToFile(
|
||||
override async exportFilterResultToFile(
|
||||
params: ExportFilterParams
|
||||
): Promise<{ success: boolean; filePath?: string; error?: string }> {
|
||||
return window.aiApi.exportFilterResultToFile(params)
|
||||
}
|
||||
|
||||
onExportProgress(callback: (progress: ExportProgress) => void): () => void {
|
||||
override onExportProgress(callback: (progress: ExportProgress) => void): () => void {
|
||||
return window.aiApi.onExportProgress(callback)
|
||||
}
|
||||
|
||||
// ===== 调试 =====
|
||||
async executeAiSQL(sql: string): Promise<AiSQLResult> {
|
||||
return window.aiApi.executeAiSQL(sql)
|
||||
}
|
||||
|
||||
async getAiSchema(): Promise<AiSchemaTable[]> {
|
||||
return window.aiApi.getAiSchema()
|
||||
}
|
||||
|
||||
async clearDebugContext(): Promise<{ success: boolean; cleared: number }> {
|
||||
return window.aiApi.clearDebugContext()
|
||||
}
|
||||
|
||||
// ===== 工具 =====
|
||||
async getToolCatalog(): Promise<ToolCatalogEntry[]> {
|
||||
// ===== Tool testing (localized catalog + worker execution on main process) =====
|
||||
override async getToolCatalog(): Promise<ToolCatalogEntry[]> {
|
||||
return window.aiApi.getToolCatalog()
|
||||
}
|
||||
|
||||
async executeTool(
|
||||
override async executeTool(
|
||||
testId: string,
|
||||
toolName: string,
|
||||
params: Record<string, unknown>,
|
||||
@@ -174,21 +78,21 @@ export class ElectronAIAdapter implements AIAdapter {
|
||||
return window.aiApi.executeTool(testId, toolName, params, sessionId)
|
||||
}
|
||||
|
||||
async cancelToolTest(testId: string): Promise<{ success: boolean }> {
|
||||
override async cancelToolTest(testId: string): Promise<{ success: boolean }> {
|
||||
return window.aiApi.cancelToolTest(testId)
|
||||
}
|
||||
|
||||
// ===== 脱敏 =====
|
||||
async getDefaultDesensitizeRules(locale: string): Promise<DesensitizeRule[]> {
|
||||
// ===== Desensitize rules (needs node-runtime) =====
|
||||
override async getDefaultDesensitizeRules(locale: string): Promise<DesensitizeRule[]> {
|
||||
return window.aiApi.getDefaultDesensitizeRules(locale)
|
||||
}
|
||||
|
||||
async mergeDesensitizeRules(existingRules: DesensitizeRule[], locale: string): Promise<DesensitizeRule[]> {
|
||||
override async mergeDesensitizeRules(existingRules: DesensitizeRule[], locale: string): Promise<DesensitizeRule[]> {
|
||||
return window.aiApi.mergeDesensitizeRules(existingRules, locale)
|
||||
}
|
||||
|
||||
// ===== 日志 =====
|
||||
async showAiLogFile(): Promise<{ success: boolean; path?: string; error?: string }> {
|
||||
// ===== Native shell =====
|
||||
override async showAiLogFile(): Promise<{ success: boolean; path?: string; error?: string }> {
|
||||
return window.aiApi.showAiLogFile()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user