1471 lines
48 KiB
JavaScript
1471 lines
48 KiB
JavaScript
const { contextBridge, ipcRenderer } = require('electron');
|
|
|
|
// IPC事件名称常量 - 直接内联避免沙箱环境的模块加载问题
|
|
const IPC_EVENTS = {
|
|
UPDATE_CHECK: 'updater-check-update',
|
|
UPDATE_OPEN_RELEASE_PAGE: 'updater-open-release-page',
|
|
UPDATE_START_DOWNLOAD: 'updater-start-download',
|
|
UPDATE_INSTALL: 'updater-install-update',
|
|
UPDATE_IGNORE_VERSION: 'updater-ignore-version',
|
|
UPDATE_UNIGNORE_VERSION: 'updater-unignore-version',
|
|
UPDATE_GET_IGNORED_VERSIONS: 'updater-get-ignored-versions',
|
|
UPDATE_DOWNLOAD_SPECIFIC_VERSION: 'updater-download-specific-version',
|
|
UPDATE_CHECK_ALL_VERSIONS: 'updater-check-all-versions', // 新增常量
|
|
|
|
// 主进程发送给渲染进程的事件
|
|
UPDATE_AVAILABLE_INFO: 'update-available-info',
|
|
UPDATE_NOT_AVAILABLE: 'update-not-available',
|
|
UPDATE_DOWNLOAD_PROGRESS: 'update-download-progress',
|
|
UPDATE_DOWNLOADED: 'update-downloaded',
|
|
UPDATE_ERROR: 'update-error',
|
|
UPDATE_DOWNLOAD_STARTED: 'updater-download-started'
|
|
};
|
|
|
|
const REMOTE_STORAGE_CHANNEL = 'remote-storage:invoke';
|
|
|
|
// 简单的超时包装器,避免过度设计
|
|
const withTimeout = (promise, timeoutMs = 30000) => {
|
|
return Promise.race([
|
|
promise,
|
|
new Promise((_, reject) => {
|
|
setTimeout(() => {
|
|
const timeoutError = new Error(`Operation timed out after ${timeoutMs}ms`);
|
|
timeoutError.code = 'TIMEOUT';
|
|
timeoutError.detailedMessage = `[${new Date().toISOString()}] Timeout Error:\n\nOperation timed out after ${timeoutMs}ms\nThis usually indicates network connectivity issues or server problems.`;
|
|
reject(timeoutError);
|
|
}, timeoutMs);
|
|
})
|
|
]);
|
|
};
|
|
|
|
// 生成唯一的流式请求ID
|
|
function generateStreamId() {
|
|
return `stream_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
function createIpcError(payload) {
|
|
if (!payload) {
|
|
// Throw a plain object so renderer can still read properties across contextBridge.
|
|
return { message: 'Unknown IPC error' };
|
|
}
|
|
|
|
if (typeof payload === 'string') {
|
|
return new Error(payload);
|
|
}
|
|
|
|
// IMPORTANT:
|
|
// Errors do NOT reliably preserve custom fields (e.g. `code`, `params`) across
|
|
// Electron contextBridge/isolated worlds. If we wrap a structured payload
|
|
// into an Error and throw it, the renderer may only receive `message`.
|
|
//
|
|
// So for structured IPC errors, throw a plain object. UI will translate via
|
|
// `code + params` (see getI18nErrorMessage).
|
|
if (payload && typeof payload === 'object') {
|
|
const hasCode = typeof payload.code === 'string' && payload.code.length > 0
|
|
const hasParams = payload.params && typeof payload.params === 'object'
|
|
|
|
const message = typeof payload.message === 'string' && payload.message
|
|
? payload.message
|
|
: (typeof payload.code === 'string' ? `[${payload.code}]` : 'Unknown IPC error');
|
|
|
|
// For plain message-only errors, throw an Error so callers relying on
|
|
// `instanceof Error` (and `.message`) keep working.
|
|
if (!hasCode && !hasParams) {
|
|
return new Error(message)
|
|
}
|
|
|
|
return { ...payload, message };
|
|
}
|
|
|
|
return new Error(String(payload));
|
|
}
|
|
|
|
async function invokeFavorite(channel, ...args) {
|
|
const result = await ipcRenderer.invoke(channel, ...args);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
|
|
contextBridge.exposeInMainWorld('electronAPI', {
|
|
// IPC event listeners
|
|
on: (channel, callback) => {
|
|
ipcRenderer.on(channel, (event, ...args) => callback(...args));
|
|
},
|
|
off: (channel, callback) => {
|
|
ipcRenderer.removeListener(channel, callback);
|
|
},
|
|
|
|
// High-level LLM service interface
|
|
llm: {
|
|
// Test connection to a provider
|
|
testConnection: async (provider) => {
|
|
const result = await ipcRenderer.invoke('llm-testConnection', provider);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Send a simple message
|
|
sendMessage: async (messages, provider) => {
|
|
const result = await ipcRenderer.invoke('llm-sendMessage', messages, provider);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Send a structured message
|
|
sendMessageStructured: async (messages, provider) => {
|
|
const result = await ipcRenderer.invoke('llm-sendMessageStructured', messages, provider);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Fetch model list
|
|
fetchModelList: async (provider, customConfig) => {
|
|
const result = await ipcRenderer.invoke('llm-fetchModelList', provider, customConfig);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Send streaming message
|
|
sendMessageStream: async (messages, provider, callbacks) => {
|
|
const streamId = generateStreamId();
|
|
|
|
// Set up event listeners for streaming responses
|
|
const contentListener = (event, content) => {
|
|
if (callbacks.onContent) callbacks.onContent(content);
|
|
};
|
|
const thinkingListener = (event, thinking) => {
|
|
if (callbacks.onThinking) callbacks.onThinking(thinking);
|
|
};
|
|
const finishListener = (event) => {
|
|
cleanup();
|
|
if (callbacks.onFinish) callbacks.onFinish();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
// Clean up listeners
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-content-${streamId}`, contentListener);
|
|
ipcRenderer.removeListener(`stream-thinking-${streamId}`, thinkingListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
// Register listeners
|
|
ipcRenderer.on(`stream-content-${streamId}`, contentListener);
|
|
ipcRenderer.on(`stream-thinking-${streamId}`, thinkingListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
// Send the streaming request
|
|
try {
|
|
const result = await ipcRenderer.invoke('llm-sendMessageStream', messages, provider, streamId);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
} catch (error) {
|
|
cleanup();
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
// Send streaming message with tools (supports tool-call events)
|
|
sendMessageStreamWithTools: async (messages, provider, tools, callbacks) => {
|
|
const streamId = generateStreamId();
|
|
|
|
// Set up event listeners for streaming responses
|
|
const contentListener = (event, content) => {
|
|
if (callbacks.onContent) callbacks.onContent(content);
|
|
};
|
|
const thinkingListener = (event, thinking) => {
|
|
if (callbacks.onThinking) callbacks.onThinking(thinking);
|
|
};
|
|
const toolCallListener = (event, toolCall) => {
|
|
if (callbacks.onToolCall) callbacks.onToolCall(toolCall);
|
|
};
|
|
const finishListener = (event) => {
|
|
cleanup();
|
|
if (callbacks.onFinish) callbacks.onFinish();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
// Clean up listeners
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-content-${streamId}`, contentListener);
|
|
ipcRenderer.removeListener(`stream-thinking-${streamId}`, thinkingListener);
|
|
ipcRenderer.removeListener(`stream-tool-call-${streamId}`, toolCallListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
// Register listeners
|
|
ipcRenderer.on(`stream-content-${streamId}`, contentListener);
|
|
ipcRenderer.on(`stream-thinking-${streamId}`, thinkingListener);
|
|
ipcRenderer.on(`stream-tool-call-${streamId}`, toolCallListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
// Send the streaming request
|
|
try {
|
|
const result = await ipcRenderer.invoke(
|
|
'llm-sendMessageStreamWithTools',
|
|
messages,
|
|
provider,
|
|
tools,
|
|
streamId
|
|
);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
} catch (error) {
|
|
cleanup();
|
|
throw error;
|
|
}
|
|
}
|
|
},
|
|
|
|
imageUnderstanding: {
|
|
understand: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-understanding-understand', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
// Model Manager interface
|
|
model: {
|
|
ensureInitialized: async () => {
|
|
const result = await ipcRenderer.invoke('model-ensureInitialized');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
|
|
isInitialized: async () => {
|
|
const result = await ipcRenderer.invoke('model-isInitialized');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
|
|
// Get all models
|
|
getAllModels: async () => {
|
|
const result = await ipcRenderer.invoke('model-getAllModels');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Get all models
|
|
getModels: async () => {
|
|
console.warn('`getModels` is deprecated, please use `getAllModels`');
|
|
const result = await ipcRenderer.invoke('model-getAllModels');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Add a new model
|
|
addModel: async (model) => {
|
|
const result = await ipcRenderer.invoke('model-addModel', model);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Update an existing model
|
|
updateModel: async (id, updates) => {
|
|
const result = await ipcRenderer.invoke('model-updateModel', id, updates);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Delete a model
|
|
deleteModel: async (id) => {
|
|
const result = await ipcRenderer.invoke('model-deleteModel', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
getEnabledModels: async () => {
|
|
const result = await ipcRenderer.invoke('model-getEnabledModels');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Export all model data
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('model-exportData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Import model data
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('model-importData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Get data type identifier
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('model-getDataType');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Validate data format
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('model-validateData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// Image Model Manager interface (Config-centric)
|
|
imageModel: {
|
|
ensureInitialized: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-ensureInitialized');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
isInitialized: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-isInitialized');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
getAllConfigs: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-getAllConfigs');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
getConfig: async (id) => {
|
|
const result = await ipcRenderer.invoke('image-model-getConfig', id);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
addConfig: async (config) => {
|
|
const result = await ipcRenderer.invoke('image-model-addConfig', config);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
updateConfig: async (id, updates) => {
|
|
const result = await ipcRenderer.invoke('image-model-updateConfig', id, updates);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
deleteConfig: async (id) => {
|
|
const result = await ipcRenderer.invoke('image-model-deleteConfig', id);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
getEnabledConfigs: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-getEnabledConfigs');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-exportData');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('image-model-importData', data);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('image-model-getDataType');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('image-model-validateData', data);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// Image Service interface
|
|
image: {
|
|
generate: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-generate', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// 显式模式:避免根据 inputImage 是否存在隐式推断
|
|
generateText2Image: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-generateText2Image', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
generateImage2Image: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-generateImage2Image', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
generateMultiImage: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-generateMultiImage', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
validateRequest: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-validateRequest', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
validateText2ImageRequest: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-validateText2ImageRequest', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
validateImage2ImageRequest: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-validateImage2ImageRequest', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
validateMultiImageRequest: async (request) => {
|
|
const result = await ipcRenderer.invoke('image-validateMultiImageRequest', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// 新增:连接测试在主进程执行
|
|
testConnection: async (config) => {
|
|
const result = await ipcRenderer.invoke('image-testConnection', config);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
// 新增:动态模型列表在主进程获取
|
|
getDynamicModels: async (providerId, connectionConfig) => {
|
|
const result = await ipcRenderer.invoke('image-getDynamicModels', providerId, connectionConfig);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
// Template Manager interface
|
|
template: {
|
|
// Get all templates
|
|
getTemplates: async () => {
|
|
const result = await ipcRenderer.invoke('template-getTemplates');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Get a specific template
|
|
getTemplate: async (id) => {
|
|
const result = await ipcRenderer.invoke('template-getTemplate', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Create a new template
|
|
createTemplate: async (template) => {
|
|
const result = await ipcRenderer.invoke('template-createTemplate', template);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Update an existing template
|
|
updateTemplate: async (id, updates) => {
|
|
const result = await ipcRenderer.invoke('template-updateTemplate', id, updates);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Delete a template
|
|
deleteTemplate: async (id) => {
|
|
const result = await ipcRenderer.invoke('template-deleteTemplate', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Add listTemplatesByType
|
|
listTemplatesByType: async (type) => {
|
|
const result = await ipcRenderer.invoke('template-listTemplatesByType', type);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Template Import/Export
|
|
exportTemplate: async (id) => {
|
|
const result = await ipcRenderer.invoke('template-exportTemplate', id);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
importTemplate: async (jsonString) => {
|
|
const result = await ipcRenderer.invoke('template-importTemplate', jsonString);
|
|
if (!result.success) throw createIpcError(result.error);
|
|
},
|
|
|
|
// Export all user templates data
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('template-exportData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Import user templates data
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('template-importData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Get data type identifier
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('template-getDataType');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Validate data format
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('template-validateData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Template language methods
|
|
changeBuiltinTemplateLanguage: async (language) => {
|
|
const result = await ipcRenderer.invoke('template-changeBuiltinTemplateLanguage', language);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
getCurrentBuiltinTemplateLanguage: async () => {
|
|
const result = await ipcRenderer.invoke('template-getCurrentBuiltinTemplateLanguage');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
|
|
getSupportedBuiltinTemplateLanguages: async () => {
|
|
const result = await ipcRenderer.invoke('template-getSupportedBuiltinTemplateLanguages');
|
|
if (!result.success) throw createIpcError(result.error);
|
|
return result.data;
|
|
},
|
|
|
|
},
|
|
|
|
|
|
// History Manager interface
|
|
history: {
|
|
// Get all history records
|
|
getHistory: async () => {
|
|
const result = await ipcRenderer.invoke('history-getHistory');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Add a new history record
|
|
addRecord: async (record) => {
|
|
const result = await ipcRenderer.invoke('history-addRecord', record);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Delete a history record
|
|
deleteRecord: async (id) => {
|
|
const result = await ipcRenderer.invoke('history-deleteRecord', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Clear all history
|
|
clearHistory: async () => {
|
|
const result = await ipcRenderer.invoke('history-clearHistory');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// 添加缺失的历史记录链功能
|
|
getIterationChain: async (recordId) => {
|
|
const result = await ipcRenderer.invoke('history-getIterationChain', recordId);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
getAllChains: async () => {
|
|
const result = await ipcRenderer.invoke('history-getAllChains');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
getChain: async (chainId) => {
|
|
const result = await ipcRenderer.invoke('history-getChain', chainId);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
createNewChain: async (record) => {
|
|
const result = await ipcRenderer.invoke('history-createNewChain', record);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
addIteration: async (params) => {
|
|
const result = await ipcRenderer.invoke('history-addIteration', params);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
deleteChain: async (chainId) => {
|
|
const result = await ipcRenderer.invoke('history-deleteChain', chainId);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Export all history data
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('history-exportData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Import history data
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('history-importData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Get data type identifier
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('history-getDataType');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Validate data format
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('history-validateData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
// Favorite Manager interface
|
|
favoriteManager: {
|
|
addFavorite: async (favorite) => {
|
|
return await invokeFavorite('favorite-addFavorite', favorite);
|
|
},
|
|
getFavorites: async (options) => {
|
|
return await invokeFavorite('favorite-getFavorites', options);
|
|
},
|
|
getFavorite: async (id) => {
|
|
return await invokeFavorite('favorite-getFavorite', id);
|
|
},
|
|
updateFavorite: async (id, updates) => {
|
|
await invokeFavorite('favorite-updateFavorite', id, updates);
|
|
},
|
|
setFavoritePromptAssetCurrentVersion: async (id, versionId) => {
|
|
await invokeFavorite('favorite-setFavoritePromptAssetCurrentVersion', id, versionId);
|
|
},
|
|
deleteFavoritePromptAssetVersion: async (id, versionId) => {
|
|
await invokeFavorite('favorite-deleteFavoritePromptAssetVersion', id, versionId);
|
|
},
|
|
deleteFavorite: async (id) => {
|
|
await invokeFavorite('favorite-deleteFavorite', id);
|
|
},
|
|
deleteFavorites: async (ids) => {
|
|
await invokeFavorite('favorite-deleteFavorites', ids);
|
|
},
|
|
incrementUseCount: async (id) => {
|
|
await invokeFavorite('favorite-incrementUseCount', id);
|
|
},
|
|
getCategories: async () => {
|
|
return await invokeFavorite('favorite-getCategories');
|
|
},
|
|
addCategory: async (category) => {
|
|
return await invokeFavorite('favorite-addCategory', category);
|
|
},
|
|
updateCategory: async (id, updates) => {
|
|
await invokeFavorite('favorite-updateCategory', id, updates);
|
|
},
|
|
deleteCategory: async (id) => {
|
|
return await invokeFavorite('favorite-deleteCategory', id);
|
|
},
|
|
getStats: async () => {
|
|
return await invokeFavorite('favorite-getStats');
|
|
},
|
|
searchFavorites: async (keyword, options) => {
|
|
return await invokeFavorite('favorite-searchFavorites', keyword, options);
|
|
},
|
|
exportFavorites: async (ids) => {
|
|
return await invokeFavorite('favorite-exportFavorites', ids);
|
|
},
|
|
importFavorites: async (data, options) => {
|
|
return await invokeFavorite('favorite-importFavorites', data, options);
|
|
},
|
|
getAllTags: async () => {
|
|
return await invokeFavorite('favorite-getAllTags');
|
|
},
|
|
addTag: async (tag) => {
|
|
await invokeFavorite('favorite-addTag', tag);
|
|
},
|
|
renameTag: async (oldTag, newTag) => {
|
|
return await invokeFavorite('favorite-renameTag', oldTag, newTag);
|
|
},
|
|
mergeTags: async (sourceTags, targetTag) => {
|
|
return await invokeFavorite('favorite-mergeTags', sourceTags, targetTag);
|
|
},
|
|
deleteTag: async (tag) => {
|
|
return await invokeFavorite('favorite-deleteTag', tag);
|
|
},
|
|
reorderCategories: async (categoryIds) => {
|
|
await invokeFavorite('favorite-reorderCategories', categoryIds);
|
|
},
|
|
getCategoryUsage: async (categoryId) => {
|
|
return await invokeFavorite('favorite-getCategoryUsage', categoryId);
|
|
},
|
|
ensureDefaultCategories: async (defaultCategories) => {
|
|
await invokeFavorite('favorite-ensureDefaultCategories', defaultCategories);
|
|
}
|
|
},
|
|
|
|
// Prompt Service interface
|
|
prompt: {
|
|
optimizePrompt: async (request) => {
|
|
const result = await ipcRenderer.invoke('prompt-optimizePrompt', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
optimizeMessage: async (request) => {
|
|
const result = await ipcRenderer.invoke('prompt-optimizeMessage', request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
// 统一的流式封装(与 llm.sendMessageStream 同模式)
|
|
optimizePromptStream: async (request, callbacks) => {
|
|
const streamId = generateStreamId();
|
|
|
|
const tokenListener = (event, token) => {
|
|
if (callbacks?.onToken) callbacks.onToken(token);
|
|
};
|
|
const reasoningListener = (event, token) => {
|
|
if (callbacks?.onReasoningToken) callbacks.onReasoningToken(token);
|
|
};
|
|
const finishListener = () => {
|
|
cleanup();
|
|
if (callbacks?.onComplete) callbacks.onComplete();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks?.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.removeListener(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
ipcRenderer.on(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.on(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
const result = await ipcRenderer.invoke('prompt-optimizePromptStream', request, streamId);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
optimizeMessageStream: async (request, callbacks) => {
|
|
const streamId = generateStreamId();
|
|
|
|
const tokenListener = (event, token) => {
|
|
if (callbacks?.onToken) callbacks.onToken(token);
|
|
};
|
|
const reasoningListener = (event, token) => {
|
|
if (callbacks?.onReasoningToken) callbacks.onReasoningToken(token);
|
|
};
|
|
const finishListener = () => {
|
|
cleanup();
|
|
if (callbacks?.onComplete) callbacks.onComplete();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks?.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.removeListener(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
ipcRenderer.on(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.on(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
const result = await ipcRenderer.invoke('prompt-optimizeMessageStream', request, streamId);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
iteratePromptStream: async (originalPrompt, lastOptimizedPrompt, iterateInput, modelKey, templateId, callbacks, contextData) => {
|
|
const streamId = generateStreamId();
|
|
|
|
const tokenListener = (event, token) => {
|
|
if (callbacks?.onToken) callbacks.onToken(token);
|
|
};
|
|
const reasoningListener = (event, token) => {
|
|
if (callbacks?.onReasoningToken) callbacks.onReasoningToken(token);
|
|
};
|
|
const finishListener = () => {
|
|
cleanup();
|
|
if (callbacks?.onComplete) callbacks.onComplete();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks?.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.removeListener(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
ipcRenderer.on(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.on(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
const result = await ipcRenderer.invoke('prompt-iteratePromptStream', originalPrompt, lastOptimizedPrompt, iterateInput, modelKey, templateId, streamId, contextData);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
testPromptStream: async (systemPrompt, userPrompt, modelKey, callbacks, inputImages) => {
|
|
const streamId = generateStreamId();
|
|
|
|
const tokenListener = (event, token) => {
|
|
if (callbacks?.onToken) callbacks.onToken(token);
|
|
};
|
|
const reasoningListener = (event, token) => {
|
|
if (callbacks?.onReasoningToken) callbacks.onReasoningToken(token);
|
|
};
|
|
const finishListener = () => {
|
|
cleanup();
|
|
if (callbacks?.onComplete) callbacks.onComplete();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks?.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.removeListener(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
ipcRenderer.on(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.on(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
const result = await ipcRenderer.invoke('prompt-testPromptStream', systemPrompt, userPrompt, modelKey, streamId, inputImages);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
// 自定义会话测试(支持工具调用)
|
|
testCustomConversationStream: async (request, callbacks) => {
|
|
const streamId = generateStreamId();
|
|
|
|
const tokenListener = (event, token) => {
|
|
if (callbacks?.onToken) callbacks.onToken(token);
|
|
};
|
|
const reasoningListener = (event, token) => {
|
|
if (callbacks?.onReasoningToken) callbacks.onReasoningToken(token);
|
|
};
|
|
const toolCallListener = (event, toolCall) => {
|
|
if (callbacks?.onToolCall) callbacks.onToolCall(toolCall);
|
|
};
|
|
const finishListener = () => {
|
|
cleanup();
|
|
if (callbacks?.onComplete) callbacks.onComplete();
|
|
};
|
|
const errorListener = (event, error) => {
|
|
cleanup();
|
|
if (callbacks?.onError) callbacks.onError(new Error(error));
|
|
};
|
|
|
|
const cleanup = () => {
|
|
ipcRenderer.removeListener(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.removeListener(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.removeListener(`stream-tool-call-${streamId}`, toolCallListener);
|
|
ipcRenderer.removeListener(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.removeListener(`stream-error-${streamId}`, errorListener);
|
|
};
|
|
|
|
ipcRenderer.on(`stream-token-${streamId}`, tokenListener);
|
|
ipcRenderer.on(`stream-reasoning-token-${streamId}`, reasoningListener);
|
|
ipcRenderer.on(`stream-tool-call-${streamId}`, toolCallListener);
|
|
ipcRenderer.on(`stream-finish-${streamId}`, finishListener);
|
|
ipcRenderer.on(`stream-error-${streamId}`, errorListener);
|
|
|
|
const result = await ipcRenderer.invoke('prompt-testCustomConversationStream', request, streamId);
|
|
if (!result.success) {
|
|
cleanup();
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
iteratePrompt: async (originalPrompt, lastOptimizedPrompt, iterateInput, modelKey, templateId, contextData) => {
|
|
const result = await ipcRenderer.invoke('prompt-iteratePrompt', originalPrompt, lastOptimizedPrompt, iterateInput, modelKey, templateId, contextData);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
testPrompt: async (systemPrompt, userPrompt, modelKey, inputImages) => {
|
|
const result = await ipcRenderer.invoke('prompt-testPrompt', systemPrompt, userPrompt, modelKey, inputImages);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
getHistory: async () => {
|
|
const result = await ipcRenderer.invoke('prompt-getHistory');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
getIterationChain: async (recordId) => {
|
|
const result = await ipcRenderer.invoke('prompt-getIterationChain', recordId);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// 配置同步接口 - 从主进程获取统一配置
|
|
config: {
|
|
// 获取环境变量(主进程作为唯一源)
|
|
getEnvironmentVariables: async () => {
|
|
const result = await ipcRenderer.invoke('config-getEnvironmentVariables');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
|
|
|
|
// Data Manager interface
|
|
data: {
|
|
// Export all data
|
|
exportAllData: async () => {
|
|
const result = await ipcRenderer.invoke('data-exportAllData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Import all data
|
|
importAllData: async (dataString) => {
|
|
const result = await ipcRenderer.invoke('data-importAllData', dataString);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Get local storage info (desktop only)
|
|
getStorageInfo: async () => {
|
|
const result = await ipcRenderer.invoke('data-getStorageInfo');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Open the userData directory (desktop only)
|
|
openStorageDirectory: async () => {
|
|
const result = await ipcRenderer.invoke('data-openStorageDirectory');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
remoteStorage: {
|
|
invoke: async (request) => {
|
|
const result = await ipcRenderer.invoke(REMOTE_STORAGE_CHANNEL, request);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// Context Repository interface
|
|
context: {
|
|
list: async () => {
|
|
const result = await ipcRenderer.invoke('context-list');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
getCurrentId: async () => {
|
|
const result = await ipcRenderer.invoke('context-getCurrentId');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
setCurrentId: async (id) => {
|
|
const result = await ipcRenderer.invoke('context-setCurrentId', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
get: async (id) => {
|
|
const result = await ipcRenderer.invoke('context-get', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
create: async (meta) => {
|
|
const result = await ipcRenderer.invoke('context-create', meta);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
duplicate: async (id, options) => {
|
|
const result = await ipcRenderer.invoke('context-duplicate', id, options);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
rename: async (id, title) => {
|
|
const result = await ipcRenderer.invoke('context-rename', id, title);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
save: async (ctx) => {
|
|
const result = await ipcRenderer.invoke('context-save', ctx);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
update: async (id, patch) => {
|
|
const result = await ipcRenderer.invoke('context-update', id, patch);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
remove: async (id) => {
|
|
const result = await ipcRenderer.invoke('context-remove', id);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
exportAll: async () => {
|
|
const result = await ipcRenderer.invoke('context-exportAll');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
importAll: async (bundle, mode) => {
|
|
const result = await ipcRenderer.invoke('context-importAll', bundle, mode);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('context-exportData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('context-importData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('context-getDataType');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('context-validateData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
},
|
|
|
|
// Add an identifier so the frontend knows it's running in Electron
|
|
isElectron: true,
|
|
|
|
// Preference Service interface
|
|
preference: {
|
|
get: async (key, defaultValue) => {
|
|
const result = await ipcRenderer.invoke('preference-get', key, defaultValue);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
set: async (key, value) => {
|
|
const result = await ipcRenderer.invoke('preference-set', key, value);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
delete: async (key) => {
|
|
const result = await ipcRenderer.invoke('preference-delete', key);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
keys: async () => {
|
|
const result = await ipcRenderer.invoke('preference-keys');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
clear: async () => {
|
|
const result = await ipcRenderer.invoke('preference-clear');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
getAll: async () => {
|
|
const result = await ipcRenderer.invoke('preference-getAll');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Export all preference data
|
|
exportData: async () => {
|
|
const result = await ipcRenderer.invoke('preference-exportData');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Import preference data
|
|
importData: async (data) => {
|
|
const result = await ipcRenderer.invoke('preference-importData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
|
|
// Get data type identifier
|
|
getDataType: async () => {
|
|
const result = await ipcRenderer.invoke('preference-getDataType');
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Validate data format
|
|
validateData: async (data) => {
|
|
const result = await ipcRenderer.invoke('preference-validateData', data);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// Shell operations
|
|
shell: {
|
|
openExternal: async (url) => {
|
|
const result = await ipcRenderer.invoke('shell-openExternal', url);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
|
|
// App information
|
|
app: {
|
|
getVersion: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke('app-get-version'),
|
|
5000 // 5秒超时,获取版本应该很快
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
// Sync UI locale (renderer -> main) so main-process UI (e.g. context menus)
|
|
// can follow the app's selected language.
|
|
setLocale: async (locale) => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke('app-set-locale', locale),
|
|
5000
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
},
|
|
},
|
|
|
|
// Auto-updater interface with timeout protection
|
|
updater: {
|
|
checkUpdate: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_CHECK),
|
|
30000 // 30秒超时,检查更新可能需要网络请求
|
|
);
|
|
if (!result.success) {
|
|
console.error('[DEBUG] Preload received error result:', result);
|
|
// 保留完整的错误信息,不要创建新的 Error 对象
|
|
const error = new Error(result.error);
|
|
error.originalError = result.error;
|
|
error.detailedMessage = result.error;
|
|
console.error('[DEBUG] Preload throwing enhanced error:', error);
|
|
throw error;
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
checkAllVersions: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_CHECK_ALL_VERSIONS),
|
|
60000 // 60秒超时,需要检查两个版本
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
openReleasePage: async (version) => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_OPEN_RELEASE_PAGE, version),
|
|
10000
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
startDownload: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_START_DOWNLOAD),
|
|
10000 // 10秒超时,启动下载应该很快
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
installUpdate: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_INSTALL),
|
|
10000 // 10秒超时,安装启动应该很快
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error);
|
|
}
|
|
return result.data;
|
|
},
|
|
ignoreVersion: async (version, versionType) => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_IGNORE_VERSION, version, versionType),
|
|
5000 // 5秒超时,设置偏好应该很快
|
|
);
|
|
if (!result.success) {
|
|
// 保留完整的错误信息
|
|
const error = new Error(result.error);
|
|
error.originalError = result.error;
|
|
error.detailedMessage = result.error;
|
|
throw error;
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
getIgnoredVersions: async () => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_GET_IGNORED_VERSIONS),
|
|
5000 // 5秒超时,读取偏好应该很快
|
|
);
|
|
if (!result.success) {
|
|
const error = new Error(result.error);
|
|
error.originalError = result.error;
|
|
error.detailedMessage = result.error;
|
|
throw error;
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
unignoreVersion: async (versionType) => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_UNIGNORE_VERSION, versionType),
|
|
5000 // 5秒超时,设置偏好应该很快
|
|
);
|
|
if (!result.success) {
|
|
const error = new Error(result.error);
|
|
error.originalError = result.error;
|
|
error.detailedMessage = result.error;
|
|
throw error;
|
|
}
|
|
return result.data;
|
|
},
|
|
|
|
downloadSpecificVersion: async (versionType) => {
|
|
const result = await withTimeout(
|
|
ipcRenderer.invoke(IPC_EVENTS.UPDATE_DOWNLOAD_SPECIFIC_VERSION, versionType),
|
|
30000 // 30秒超时,现在只等待下载启动,不等待完成,所以30秒足够
|
|
);
|
|
if (!result.success) {
|
|
throw createIpcError(result.error || 'Failed to download specific version');
|
|
}
|
|
return result.data;
|
|
},
|
|
},
|
|
});
|