"use strict"; const electron = require("electron"); const OpenAI = require("openai"); const util = require("util"); const log = require("electron-log"); const path = require("path"); const fs = require("fs"); const jsBase64 = require("js-base64"); const path$1 = require("node:path"); const crypto = require("crypto"); const started = require("electron-squirrel-startup"); const net = require("net"); const http = require("http"); const child_process = require("child_process"); const events = require("events"); require("bytenode"); const electronUpdater = require("electron-updater"); function _interopNamespaceDefault(e) { const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } }); if (e) { for (const k in e) { if (k !== "default") { const d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: () => e[k] }); } } } n.default = e; return Object.freeze(n); } const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path); const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs); var IPC_EVENTS = /* @__PURE__ */ ((IPC_EVENTS2) => { IPC_EVENTS2["EXTERNAL_OPEN"] = "external-open"; IPC_EVENTS2["WINDOW_MINIMIZE"] = "window-minimize"; IPC_EVENTS2["WINDOW_MAXIMIZE"] = "window-maximize"; IPC_EVENTS2["WINDOW_CLOSE"] = "window-close"; IPC_EVENTS2["IS_WINDOW_MAXIMIZED"] = "is-window-maximized"; IPC_EVENTS2["APP_SET_FRAMELESS"] = "app:set-frameless"; IPC_EVENTS2["APP_LOAD_PAGE"] = "app:load-page"; IPC_EVENTS2["TAB_CREATE"] = "tab:create"; IPC_EVENTS2["TAB_LIST"] = "tab:list"; IPC_EVENTS2["TAB_NAVIGATE"] = "tab:navigate"; IPC_EVENTS2["TAB_RELOAD"] = "tab:reload"; IPC_EVENTS2["TAB_BACK"] = "tab:back"; IPC_EVENTS2["TAB_FORWARD"] = "tab:forward"; IPC_EVENTS2["TAB_SWITCH"] = "tab:switch"; IPC_EVENTS2["TAB_CLOSE"] = "tab:close"; IPC_EVENTS2["LOG_TO_MAIN"] = "log-to-main"; IPC_EVENTS2["READ_FILE"] = "read-file"; IPC_EVENTS2["INVOKE"] = "ipc:invoke"; IPC_EVENTS2["INVOKE_ASYNC"] = "ipc:invokeAsync"; IPC_EVENTS2["APP_MINIMIZE"] = "app:minimize"; IPC_EVENTS2["APP_MAXIMIZE"] = "app:maximize"; IPC_EVENTS2["APP_QUIT"] = "app:quit"; IPC_EVENTS2["FILE_READ"] = "file:read"; IPC_EVENTS2["FILE_WRITE"] = "file:write"; IPC_EVENTS2["GET_WINDOW_ID"] = "get-window-id"; IPC_EVENTS2["CUSTOM_EVENT"] = "custom:event"; IPC_EVENTS2["TIME_UPDATE"] = "time:update"; IPC_EVENTS2["RENDERER_IS_READY"] = "renderer-ready"; IPC_EVENTS2["SHOW_CONTEXT_MENU"] = "show-context-menu"; IPC_EVENTS2["START_A_DIALOGUE"] = "start-a-dialogue"; IPC_EVENTS2["OPEN_WINDOW"] = "open-window"; IPC_EVENTS2["LOG_DEBUG"] = "log-debug"; IPC_EVENTS2["LOG_INFO"] = "log-info"; IPC_EVENTS2["LOG_WARN"] = "log-warn"; IPC_EVENTS2["LOG_ERROR"] = "log-error"; IPC_EVENTS2["CONFIG_UPDATED"] = "config-updated"; IPC_EVENTS2["SET_CONFIG"] = "set-config"; IPC_EVENTS2["GET_CONFIG"] = "get-config"; IPC_EVENTS2["UPDATE_CONFIG"] = "update-config"; IPC_EVENTS2["SET_THEME_MODE"] = "set-theme-mode"; IPC_EVENTS2["GET_THEME_MODE"] = "get-theme-mode"; IPC_EVENTS2["IS_DARK_THEME"] = "is-dark-theme"; IPC_EVENTS2["THEME_MODE_UPDATED"] = "theme-mode-updated"; IPC_EVENTS2["EXECUTE_SCRIPT"] = "execute-script"; IPC_EVENTS2["OPEN_CHANNEL"] = "open-channel"; IPC_EVENTS2["UPDATE_CHECK"] = "update:check"; IPC_EVENTS2["UPDATE_DOWNLOAD"] = "update:download"; IPC_EVENTS2["UPDATE_INSTALL"] = "update:install"; IPC_EVENTS2["UPDATE_VERSION"] = "update:version"; IPC_EVENTS2["UPDATE_STATUS_CHANGED"] = "update:status-changed"; return IPC_EVENTS2; })(IPC_EVENTS || {}); const MAIN_WIN_SIZE = { width: 1440, height: 900, minWidth: 1440, minHeight: 900 }; var WINDOW_NAMES = /* @__PURE__ */ ((WINDOW_NAMES2) => { WINDOW_NAMES2["MAIN"] = "main"; WINDOW_NAMES2["SETTING"] = "setting"; WINDOW_NAMES2["DIALOG"] = "dialog"; WINDOW_NAMES2["LOADING"] = "loading"; return WINDOW_NAMES2; })(WINDOW_NAMES || {}); var CONFIG_KEYS = /* @__PURE__ */ ((CONFIG_KEYS2) => { CONFIG_KEYS2["THEME_MODE"] = "themeMode"; CONFIG_KEYS2["PRIMARY_COLOR"] = "primaryColor"; CONFIG_KEYS2["LANGUAGE"] = "language"; CONFIG_KEYS2["FONT_SIZE"] = "fontSize"; CONFIG_KEYS2["MINIMIZE_TO_TRAY"] = "minimizeToTray"; CONFIG_KEYS2["PROVIDER"] = "provider"; CONFIG_KEYS2["DEFAULT_MODEL"] = "defaultModel"; CONFIG_KEYS2["AUTO_CHECK_UPDATE"] = "autoCheckUpdate"; CONFIG_KEYS2["AUTO_DOWNLOAD_UPDATE"] = "autoDownloadUpdate"; return CONFIG_KEYS2; })(CONFIG_KEYS || {}); var MENU_IDS = /* @__PURE__ */ ((MENU_IDS2) => { MENU_IDS2["CONVERSATION_ITEM"] = "conversation-item"; MENU_IDS2["CONVERSATION_LIST"] = "conversation-list"; MENU_IDS2["MESSAGE_ITEM"] = "message-item"; return MENU_IDS2; })(MENU_IDS || {}); var CONVERSATION_ITEM_MENU_IDS = /* @__PURE__ */ ((CONVERSATION_ITEM_MENU_IDS2) => { CONVERSATION_ITEM_MENU_IDS2["PIN"] = "pin"; CONVERSATION_ITEM_MENU_IDS2["RENAME"] = "rename"; CONVERSATION_ITEM_MENU_IDS2["DEL"] = "del"; return CONVERSATION_ITEM_MENU_IDS2; })(CONVERSATION_ITEM_MENU_IDS || {}); var CONVERSATION_LIST_MENU_IDS = /* @__PURE__ */ ((CONVERSATION_LIST_MENU_IDS2) => { CONVERSATION_LIST_MENU_IDS2["NEW_CONVERSATION"] = "newConversation"; CONVERSATION_LIST_MENU_IDS2["SORT_BY"] = "sortBy"; CONVERSATION_LIST_MENU_IDS2["SORT_BY_CREATE_TIME"] = "sortByCreateTime"; CONVERSATION_LIST_MENU_IDS2["SORT_BY_UPDATE_TIME"] = "sortByUpdateTime"; CONVERSATION_LIST_MENU_IDS2["SORT_BY_NAME"] = "sortByName"; CONVERSATION_LIST_MENU_IDS2["SORT_BY_MODEL"] = "sortByModel"; CONVERSATION_LIST_MENU_IDS2["SORT_ASCENDING"] = "sortAscending"; CONVERSATION_LIST_MENU_IDS2["SORT_DESCENDING"] = "sortDescending"; CONVERSATION_LIST_MENU_IDS2["BATCH_OPERATIONS"] = "batchOperations"; return CONVERSATION_LIST_MENU_IDS2; })(CONVERSATION_LIST_MENU_IDS || {}); var MESSAGE_ITEM_MENU_IDS = /* @__PURE__ */ ((MESSAGE_ITEM_MENU_IDS2) => { MESSAGE_ITEM_MENU_IDS2["COPY"] = "copy"; MESSAGE_ITEM_MENU_IDS2["DELETE"] = "delete"; MESSAGE_ITEM_MENU_IDS2["SELECT"] = "select"; return MESSAGE_ITEM_MENU_IDS2; })(MESSAGE_ITEM_MENU_IDS || {}); class BaseProvider { } const readdirAsync = util.promisify(fs__namespace.readdir); const statAsync = util.promisify(fs__namespace.stat); const unlinkAsync = util.promisify(fs__namespace.unlink); class LogService { static _instance; // 日志保留天数,默认7天 LOG_RETENTION_DAYS = 7; // 清理间隔,默认24小时(毫秒) CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3; constructor() { const logPath = path__namespace.join(electron.app.getPath("userData"), "logs"); try { if (!fs__namespace.existsSync(logPath)) { fs__namespace.mkdirSync(logPath, { recursive: true }); } } catch (err) { this.error("Failed to create log directory:", err); } log.transports.file.resolvePathFn = () => { const today = /* @__PURE__ */ new Date(); const formattedDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`; return path__namespace.join(logPath, `${formattedDate}.log`); }; log.transports.file.format = "[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}"; log.transports.file.maxSize = 10 * 1024 * 1024; log.transports.console.level = process.env.NODE_ENV === "development" ? "debug" : "info"; log.transports.file.level = "debug"; this._setupIpcEvents(); this._rewriteConsole(); this.info("LogService initialized successfully."); this._cleanupOldLogs(); setInterval(() => this._cleanupOldLogs(), this.CLEANUP_INTERVAL_MS); } _setupIpcEvents() { electron.ipcMain.on(IPC_EVENTS.LOG_DEBUG, (_e, message, ...meta) => this.debug(message, ...meta)); electron.ipcMain.on(IPC_EVENTS.LOG_INFO, (_e, message, ...meta) => this.info(message, ...meta)); electron.ipcMain.on(IPC_EVENTS.LOG_WARN, (_e, message, ...meta) => this.warn(message, ...meta)); electron.ipcMain.on(IPC_EVENTS.LOG_ERROR, (_e, message, ...meta) => this.error(message, ...meta)); } _rewriteConsole() { console.debug = log.debug; console.log = log.info; console.info = log.info; console.warn = log.warn; console.error = log.error; } async _cleanupOldLogs() { try { const logPath = path__namespace.join(electron.app.getPath("userData"), "logs"); if (!fs__namespace.existsSync(logPath)) return; const now = /* @__PURE__ */ new Date(); const expirationDate = new Date(now.getTime() - this.LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3); const files = await readdirAsync(logPath); let deletedCount = 0; for (const file of files) { if (!file.endsWith(".log")) continue; const filePath = path__namespace.join(logPath, file); try { const stats = await statAsync(filePath); if (stats.isFile() && stats.birthtime < expirationDate) { await unlinkAsync(filePath); deletedCount++; } } catch (error) { this.error(`Failed to delete old log file ${filePath}:`, error); } } if (deletedCount > 0) { this.info(`Successfully cleaned up ${deletedCount} old log files.`); } } catch (err) { this.error("Failed to cleanup old logs:", err); } } static getInstance() { if (!this._instance) { this._instance = new LogService(); } return this._instance; } /** * 记录调试信息 * @param {string} message - 日志消息 * @param {any[]} meta - 附加的元数据 */ debug(message, ...meta) { log.debug(message, ...meta); } /** * 记录一般信息 * @param {string} message - 日志消息 * @param {any[]} meta - 附加的元数据 */ info(message, ...meta) { log.info(message, ...meta); } /** * 记录警告信息 * @param {string} message - 日志消息 * @param {any[]} meta - 附加的元数据 */ warn(message, ...meta) { log.warn(message, ...meta); } /** * 记录错误信息 * @param {string} message - 日志消息 * @param {any[]} meta - 附加的元数据,通常是错误对象 */ error(message, ...meta) { log.error(message, ...meta); } logApiRequest(endpoint, data = {}, method = "POST") { this.info(`API Request: ${endpoint}, Method: ${method}, Request: ${JSON.stringify(data)}`); } logApiResponse(endpoint, response = {}, statusCode = 200, responseTime = 0) { if (statusCode >= 400) { this.error(`API Error Response: ${endpoint}, Status: ${statusCode}, Response Time: ${responseTime}ms, Response: ${JSON.stringify(response)}`); } else { this.debug(`API Response: ${endpoint}, Status: ${statusCode}, Response Time: ${responseTime}ms, Response: ${JSON.stringify(response)}`); } } logUserOperation(operation, userId = "unknown", details = {}) { this.info(`User Operation: ${operation} by ${userId}, Details: ${JSON.stringify(details)}`); } } const logManager = LogService.getInstance(); function _transformChunk(chunk) { const choice = chunk.choices[0]; return { isEnd: choice?.finish_reason === "stop", result: choice?.delta?.content ?? "" }; } class OpenAIProvider extends BaseProvider { client; constructor(apiKey, baseURL) { super(); this.client = new OpenAI({ apiKey, baseURL }); } async chat(messages2, model) { const startTime = Date.now(); const lastMessage = messages2[messages2.length - 1]; logManager.logApiRequest("chat.completions.create", { model, lastMessage: lastMessage?.content?.substring(0, 100) + (lastMessage?.content?.length > 100 ? "..." : ""), messageCount: messages2.length }, "POST"); try { const chunks = await this.client.chat.completions.create({ model, messages: messages2, stream: true }); const responseTime = Date.now() - startTime; logManager.logApiResponse("chat.completions.create", { success: true }, 200, responseTime); return { async *[Symbol.asyncIterator]() { for await (const chunk of chunks) { yield _transformChunk(chunk); } } }; } catch (error) { const responseTime = Date.now() - startTime; logManager.logApiResponse("chat.completions.create", { error: error instanceof Error ? error.message : String(error) }, 500, responseTime); throw error; } } } function debounce(fn, delay) { let timer = null; return function(...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args); }, delay); }; } function cloneDeep(obj) { if (obj === null || typeof obj !== "object") { return obj; } if (Array.isArray(obj)) { return obj.map((item) => cloneDeep(item)); } const clone = Object.assign({}, obj); for (const key in clone) { if (Object.prototype.hasOwnProperty.call(clone, key)) { clone[key] = cloneDeep(clone[key]); } } return clone; } function simpleCloneDeep(obj) { try { return JSON.parse(JSON.stringify(obj)); } catch (error) { console.error("simpleCloneDeep failed:", error); return obj; } } function parseOpenAISetting(setting) { try { return JSON.parse(jsBase64.decode(setting)); } catch (error) { console.error("parseOpenAISetting failed:", error); return {}; } } const DEFAULT_CONFIG = { [CONFIG_KEYS.THEME_MODE]: "system", [CONFIG_KEYS.PRIMARY_COLOR]: "#BB5BE7", [CONFIG_KEYS.LANGUAGE]: "zh", [CONFIG_KEYS.FONT_SIZE]: 14, [CONFIG_KEYS.MINIMIZE_TO_TRAY]: false, [CONFIG_KEYS.PROVIDER]: "", [CONFIG_KEYS.DEFAULT_MODEL]: null }; class ConfigService { static _instance; _config; _configPath; _defaultConfig = DEFAULT_CONFIG; _listeners = []; constructor() { this._configPath = path__namespace.join(electron.app.getPath("userData"), "config.json"); this._config = this._loadConfig(); this._setupIpcEvents(); logManager.info("ConfigService initialized successfully."); } _setupIpcEvents() { const duration = 200; const handelUpdate = debounce((val) => this.update(val), duration); electron.ipcMain.handle(IPC_EVENTS.GET_CONFIG, (_, key) => this.get(key)); electron.ipcMain.on(IPC_EVENTS.SET_CONFIG, (_, key, val) => this.set(key, val)); electron.ipcMain.on(IPC_EVENTS.UPDATE_CONFIG, (_, updates) => handelUpdate(updates)); } static getInstance() { if (!this._instance) { this._instance = new ConfigService(); } return this._instance; } _loadConfig() { try { if (fs__namespace.existsSync(this._configPath)) { const configContent = fs__namespace.readFileSync(this._configPath, "utf-8"); const config = { ...this._defaultConfig, ...JSON.parse(configContent) }; logManager.info("Config loaded successfully from:", this._configPath); return config; } } catch (error) { logManager.error("Failed to load config:", error); } return { ...this._defaultConfig }; } _saveConfig() { try { fs__namespace.mkdirSync(path__namespace.dirname(this._configPath), { recursive: true }); fs__namespace.writeFileSync(this._configPath, JSON.stringify(this._config, null, 2), "utf-8"); this._notifyListeners(); logManager.info("Config saved successfully to:", this._configPath); } catch (error) { logManager.error("Failed to save config:", error); } } _notifyListeners() { electron.BrowserWindow.getAllWindows().forEach((win) => win.webContents.send(IPC_EVENTS.CONFIG_UPDATED, this._config)); this._listeners.forEach((listener) => listener({ ...this._config })); } getConfig() { return simpleCloneDeep(this._config); } get(key) { return this._config[key]; } set(key, value, autoSave = true) { if (!(key in this._config)) return; const oldValue = this._config[key]; if (oldValue === value) return; this._config[key] = value; logManager.debug(`Config set: ${key} = ${value}`); autoSave && this._saveConfig(); } update(updates, autoSave = true) { this._config = { ...this._config, ...updates }; autoSave && this._saveConfig(); } resetToDefault() { this._config = { ...this._defaultConfig }; logManager.info("Config reset to default."); this._saveConfig(); } onConfigChange(listener) { this._listeners.push(listener); return () => this._listeners = this._listeners.filter((l) => l !== listener); } } const configManager = ConfigService.getInstance(); [ { id: 1, name: "bigmodel", title: "智谱AI", models: ["glm-4.5-flash"], openAISetting: { baseURL: "https://open.bigmodel.cn/api/paas/v4", apiKey: process.env.BIGMODEL_API_KEY || "" }, createdAt: (/* @__PURE__ */ new Date()).getTime(), updatedAt: (/* @__PURE__ */ new Date()).getTime() }, { id: 2, name: "deepseek", title: "深度求索 (DeepSeek)", models: ["deepseek-chat"], openAISetting: { baseURL: "https://api.deepseek.com/v1", apiKey: process.env.DEEPSEEK_API_KEY || "" }, createdAt: (/* @__PURE__ */ new Date()).getTime(), updatedAt: (/* @__PURE__ */ new Date()).getTime() }, { id: 3, name: "siliconflow", title: "硅基流动", models: ["Qwen/Qwen3-8B", "deepseek-ai/DeepSeek-R1-0528-Qwen3-8B"], openAISetting: { baseURL: "https://api.siliconflow.cn/v1", apiKey: process.env.SILICONFLOW_API_KEY || "" }, createdAt: (/* @__PURE__ */ new Date()).getTime(), updatedAt: (/* @__PURE__ */ new Date()).getTime() }, { id: 4, name: "qianfan", title: "百度千帆", models: ["ernie-speed-128k", "ernie-4.0-8k", "ernie-3.5-8k"], openAISetting: { baseURL: "https://qianfan.baidubce.com/v2", apiKey: process.env.QIANFAN_API_KEY || "" }, createdAt: (/* @__PURE__ */ new Date()).getTime(), updatedAt: (/* @__PURE__ */ new Date()).getTime() } ]; const _parseProvider = () => { let result = []; let isBase64Parsed = false; const providerConfig = configManager.get(CONFIG_KEYS.PROVIDER); const mapCallback = (provider) => ({ ...provider, openAISetting: typeof provider.openAISetting === "string" ? parseOpenAISetting(provider.openAISetting ?? "") : provider.openAISetting }); try { result = JSON.parse(jsBase64.decode(providerConfig)); isBase64Parsed = true; } catch (error) { logManager.error(`parse base64 provider failed: ${error}`); } if (!isBase64Parsed) try { result = JSON.parse(providerConfig); } catch (error) { logManager.error(`parse provider failed: ${error}`); } if (!result.length) return; return result.map(mapCallback); }; const getProviderConfig = () => { try { return _parseProvider(); } catch (error) { logManager.error(`get provider config failed: ${error}`); return null; } }; function createProvider(name) { const providers2 = getProviderConfig(); if (!providers2) { throw new Error("provider config not found"); } for (const provider of providers2) { if (provider.name === name) { if (!provider.openAISetting?.apiKey || !provider.openAISetting?.baseURL) { throw new Error("apiKey or baseURL not found"); } return new OpenAIProvider(provider.openAISetting.apiKey, provider.openAISetting.baseURL); } } } const window$1 = { "minimize": "Minimize", "maximize": "Maximize", "restore": "Restore", "close": "Close" }; const main$1 = { "welcome": { "helloMessage": "Hello, I'm Diona" }, "conversation": { "placeholder": "Type a message...", "newConversation": "New Conversation", "selectModel": "Please select model", "createConversation": "Create Conversation", "searchPlaceholder": "Search conversations...", "goSettings": "Go to", "settings": "Settings Window", "addModel": "to add a model", "dialog": { "title": "Confirm Deletion", "content": "Are you sure you want to delete this conversation?", "content_1": "Are you sure you want to delete the selected conversations? This action cannot be undone." }, "operations": { "pin": "Pin Selected", "del": "Delete Selected", "selectAll": "Select All", "cancel": "Cancel" } }, "sidebar": { "conversations": "Conversations", "settings": "Settings", "help": "Help" }, "message": { "dialog": { "title": "Confirm Deletion", "messageDelete": "Are you sure you want to delete this message?", "batchDelete": "Are you sure you want to delete the selected messages?", "copySuccess": "Copied successfully" }, "batchActions": { "deleteSelected": "Delete Selected" }, "rendering": "Thinking...", "stoppedGeneration": "(Stopped generating)", "sending": "Sending", "stopGeneration": "Stop generating", "send": "Send" } }; const dialog$1 = { "cancel": "Cancel", "confirm": "Confirm" }; const settings$1 = { "title": "Settings", "base": "Basic Settings", "provider": { "modelConfig": "Model Configuration" }, "theme": { "label": "Theme Settings", "dark": "Dark Theme", "light": "Light Theme", "system": "System Theme", "primaryColor": "Primary Color" }, "appearance": { "fontSize": "Font Size", "fontSizeOptions": { "10": "Tiny (10px)", "12": "Small (12px)", "14": "Normal (14px)", "16": "Medium (16px)", "18": "Large (18px)", "20": "Larger (20px)", "24": "Extra Large (24px)" } }, "behavior": { "minimizeToTray": "Minimize to tray when closed" }, "language": { "label": "Language" }, "providers": { "defaultModel": "Default Model", "apiKey": "API Key", "apiUrl": "API URL" } }; const menu$1 = { "conversation": { "newConversation": "New Conversation", "sortBy": "Sort By", "sortByCreateTime": "Sort by Creation Time", "sortByUpdateTime": "Sort by Update Time", "sortByName": "Sort by Name", "sortByModel": "Sort by Model", "sortAscending": "Ascending", "sortDescending": "Descending", "pinConversation": "Pin Conversation", "unpinConversation": "Unpin Conversation", "renameConversation": "Rename Conversation", "delConversation": "Delete Conversation", "batchOperations": "Batch Operations" }, "message": { "copyMessage": "Copy Message", "deleteMessage": "Delete Message", "selectMessage": "Select Message" } }; const tray$1 = { "tooltip": "Diona Application", "showWindow": "Show Window", "exit": "Exit" }; const timeAgo$1 = { "justNow": "Just now", "minutes": "{count} minutes ago", "hours": "{count} hours ago", "days": "{count} days ago", "months": "{count} months ago", "years": "{count} years ago", "weekday": { "sun": "Sunday", "mon": "Monday", "tue": "Tuesday", "wed": "Wednesday", "thu": "Thursday", "fri": "Friday", "sat": "Saturday" } }; const app$1 = { "title": "Diona Application" }; const en = { window: window$1, main: main$1, dialog: dialog$1, settings: settings$1, menu: menu$1, tray: tray$1, timeAgo: timeAgo$1, app: app$1 }; const window = { "minimize": "最小化", "maximize": "最大化", "restore": "还原", "close": "关闭" }; const main = { "welcome": { "helloMessage": "你好,我是迪奥娜" }, "conversation": { "placeholder": "输入消息...", "newConversation": "新对话", "selectModel": "请选择模型", "createConversation": "创建对话", "searchPlaceholder": "搜索对话...", "goSettings": "快去", "settings": "设置窗口", "addModel": "添加模型", "dialog": { "title": "确认删除", "content": "确定要删除这个对话吗?", "content_1": "确定要删除选中的对话吗?此操作不可撤销。" }, "operations": { "pin": "置顶所选", "del": "删除所选", "selectAll": "全选", "cancel": "取消" } }, "sidebar": { "conversations": "对话", "settings": "设置", "help": "帮助" }, "message": { "dialog": { "title": "确认删除", "messageDelete": "确认删除该条消息?", "batchDelete": "确认删除选中的消息?", "copySuccess": "复制成功" }, "batchActions": { "deleteSelected": "删除选中项" }, "rendering": "思考中...", "stoppedGeneration": "(已停止生成)", "sending": "发送中", "stopGeneration": "停止生成", "send": "发送" } }; const dialog = { "cancel": "取消", "confirm": "确认" }; const settings = { "title": "设置", "base": "基础设置", "provider": { "modelConfig": "模型配置" }, "providers": { "defaultModel": "默认模型", "apiKey": "API密钥", "apiUrl": "API地址" }, "theme": { "label": "主题设置", "dark": "深色主题", "light": "浅色主题", "system": "跟随系统", "primaryColor": "主题颜色" }, "appearance": { "fontSize": "字体大小", "fontSizeOptions": { "10": "极小 (10px)", "12": "小 (12px)", "14": "正常 (14px)", "16": "中 (16px)", "18": "大 (18px)", "20": "较大 (20px)", "24": "超大 (24px)" } }, "behavior": { "minimizeToTray": "关闭时最小化到托盘" }, "language": { "label": "语言设置" } }; const menu = { "conversation": { "newConversation": "新建对话", "sortBy": "排序方式", "sortByCreateTime": "按创建时间排序", "sortByUpdateTime": "按更新时间排序", "sortByName": "按名称排序", "sortByModel": "按模型排序", "sortAscending": "递增", "sortDescending": "递减", "pinConversation": "置顶对话", "unpinConversation": "取消置顶", "renameConversation": "重命名对话", "delConversation": "删除对话", "batchOperations": "批量操作" }, "message": { "copyMessage": "复制消息", "deleteMessage": "删除消息", "selectMessage": "选择消息" } }; const tray = { "tooltip": "迪奥娜", "showWindow": "显示窗口", "exit": "退出" }; const timeAgo = { "justNow": "刚刚", "minutes": "{count}分钟前", "hours": "{count}小时前", "days": "{count}天前", "months": "{count}个月前", "years": "{count}年前", "weekday": { "sun": "星期日", "mon": "星期一", "tue": "星期二", "wed": "星期三", "thu": "星期四", "fri": "星期五", "sat": "星期六" } }; const app = { "title": "迪奥娜" }; const zh = { window, main, dialog, settings, menu, tray, timeAgo, app }; const messages = { en, zh }; function createTranslator() { return (key) => { if (!key) return void 0; try { const keys = key?.split("."); let result = messages[configManager.get(CONFIG_KEYS.LANGUAGE)]; for (const _key of keys) { result = result[_key]; } return result; } catch (e) { logManager.error("failed to translate key:", key, e); return key; } }; } let logo = void 0; function createLogo() { if (logo != null) { return logo; } const appPath = electron.app.getAppPath(); const iconPath = path$1.join(appPath, "resources", "icons", "icon.ico"); logo = iconPath; return logo; } class ThemeService { static _instance; _isDark = electron.nativeTheme.shouldUseDarkColors; constructor() { const themeMode = configManager.get(CONFIG_KEYS.THEME_MODE); if (themeMode) { electron.nativeTheme.themeSource = themeMode; this._isDark = electron.nativeTheme.shouldUseDarkColors; } this._setupIpcEvent(); logManager.info("ThemeService initialized successfully."); } _setupIpcEvent() { electron.ipcMain.handle(IPC_EVENTS.SET_THEME_MODE, (_e, mode) => { electron.nativeTheme.themeSource = mode; configManager.set(CONFIG_KEYS.THEME_MODE, mode); return electron.nativeTheme.shouldUseDarkColors; }); electron.ipcMain.handle(IPC_EVENTS.GET_THEME_MODE, () => { return electron.nativeTheme.themeSource; }); electron.ipcMain.handle(IPC_EVENTS.IS_DARK_THEME, () => { return electron.nativeTheme.shouldUseDarkColors; }); electron.nativeTheme.on("updated", () => { this._isDark = electron.nativeTheme.shouldUseDarkColors; electron.BrowserWindow.getAllWindows().forEach( (win) => win.webContents.send(IPC_EVENTS.THEME_MODE_UPDATED, this._isDark) ); }); } static getInstance() { if (!this._instance) { this._instance = new ThemeService(); } return this._instance; } get isDark() { return this._isDark; } get themeMode() { return electron.nativeTheme.themeSource; } } const themeManager = ThemeService.getInstance(); const SHARED_WINDOW_OPTIONS = { frame: false, titleBarStyle: "hidden", trafficLightPosition: { x: -100, y: -100 }, show: false, title: "NIANXX", darkTheme: themeManager.isDark, backgroundColor: themeManager.isDark ? "#2C2C2C" : "#FFFFFF", webPreferences: { nodeIntegration: false, // 禁用 Node.js 集成,提高安全性 contextIsolation: true, // 启用上下文隔离,防止渲染进程访问主进程 API sandbox: true, // 启用沙箱模式,进一步增强安全性 backgroundThrottling: false, preload: path$1.join(process.cwd(), "dist-electron/preload/preload.js") } }; class WindowService { static _instance; _logo = createLogo(); isDev = true; _winStates = { main: { instance: void 0, isHidden: false, onCreate: [], onClosed: [] }, setting: { instance: void 0, isHidden: false, onCreate: [], onClosed: [] }, dialog: { instance: void 0, isHidden: false, onCreate: [], onClosed: [] }, login: { instance: void 0, isHidden: false, onCreate: [], onClosed: [] }, loading: { instance: void 0, isHidden: false, onCreate: [], onClosed: [] } }; constructor() { this._setupIpcEvents(); logManager.info("WindowService initialized successfully."); } _isReallyClose(windowName) { if (windowName === WINDOW_NAMES.MAIN) return configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY) === false; if (windowName === WINDOW_NAMES.SETTING) return false; return true; } _setupIpcEvents() { const handleCloseWindow = (e) => { const target = electron.BrowserWindow.fromWebContents(e.sender); const winName = this.getName(target); this.close(target, this._isReallyClose(winName)); }; const handleMinimizeWindow = (e) => { electron.BrowserWindow.fromWebContents(e.sender)?.minimize(); }; const handleMaximizeWindow = (e) => { this.toggleMax(electron.BrowserWindow.fromWebContents(e.sender)); }; const handleIsWindowMaximized = (e) => { return electron.BrowserWindow.fromWebContents(e.sender)?.isMaximized() ?? false; }; electron.ipcMain.on(IPC_EVENTS.WINDOW_CLOSE, handleCloseWindow); electron.ipcMain.on(IPC_EVENTS.WINDOW_MINIMIZE, handleMinimizeWindow); electron.ipcMain.on(IPC_EVENTS.WINDOW_MAXIMIZE, handleMaximizeWindow); electron.ipcMain.handle(IPC_EVENTS.IS_WINDOW_MAXIMIZED, handleIsWindowMaximized); electron.ipcMain.handle(IPC_EVENTS.APP_LOAD_PAGE, (e, page) => { const win = electron.BrowserWindow.fromWebContents(e.sender); if (win) this._loadPage(win, page); }); } static getInstance() { if (!this._instance) { this._instance = new WindowService(); } return this._instance; } create(name, size, moreOpts) { if (this.get(name)) return; const isHiddenWin = this._isHiddenWin(name); let window2 = this._createWinInstance(name, { ...size, ...moreOpts }); if (this.isDev) window2.webContents.openDevTools(); !isHiddenWin && this._setupWinLifecycle(window2, name)._loadWindowTemplate(window2, name); this._listenWinReady({ win: window2, isHiddenWin, size }); if (!isHiddenWin) { this._winStates[name].instance = window2; this._winStates[name].onCreate.forEach((callback) => callback(window2)); } if (isHiddenWin) { this._winStates[name].isHidden = false; logManager.info(`Hidden window show: ${name}`); } return window2; } _setupWinLifecycle(window2, name) { const updateWinStatus = debounce(() => !window2?.isDestroyed() && window2?.webContents?.send(IPC_EVENTS.WINDOW_MAXIMIZE + "back", window2?.isMaximized()), 80); window2.once("closed", () => { this._winStates[name].onClosed.forEach((callback) => callback(window2)); window2?.destroy(); window2?.removeListener("resize", updateWinStatus); this._winStates[name].instance = void 0; this._winStates[name].isHidden = false; logManager.info(`Window closed: ${name}`); }); window2.on("resize", updateWinStatus); return this; } _listenWinReady(params) { const onReady = () => { params.win?.once("show", () => setTimeout(() => this._applySizeConstraints(params.win, params.size), 2)); params.win?.show(); }; if (!params.isHiddenWin) { const loadingHandler = this._addLoadingView(params.win, params.size); loadingHandler?.(onReady); } else { onReady(); } } _addLoadingView(window2, size) { let rendererIsReady = false; const onRendererIsReady = (e) => { if (e.sender !== window2?.webContents || rendererIsReady) return; rendererIsReady = true; electron.ipcMain.removeListener(IPC_EVENTS.RENDERER_IS_READY, onRendererIsReady); }; electron.ipcMain.on(IPC_EVENTS.RENDERER_IS_READY, onRendererIsReady); return (cb) => { cb(); }; } _applySizeConstraints(win, size) { if (size.maxHeight && size.maxWidth) { win.setMaximumSize(size.maxWidth, size.maxHeight); } if (size.minHeight && size.minWidth) { win.setMinimumSize(size.minWidth, size.minHeight); } } _loadPage(window2, pageName) { { return window2.loadURL(`${"http://localhost:5173"}/${pageName}.html`); } } _loadWindowTemplate(window2, name) { const page = "index"; this._loadPage(window2, page); } _handleCloseWindowState(target, really) { const name = this.getName(target); if (name) { if (!really) this._winStates[name].isHidden = true; else this._winStates[name].instance = void 0; } setTimeout(() => { target[really ? "close" : "hide"]?.(); this._checkAndCloseAllWinodws(); }, 210); } _checkAndCloseAllWinodws() { if (!this._winStates[WINDOW_NAMES.MAIN].instance || this._winStates[WINDOW_NAMES.MAIN].instance?.isDestroyed()) return Object.values(this._winStates).forEach((win) => win?.instance?.close()); const minimizeToTray = configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY); if (!minimizeToTray && !this.get(WINDOW_NAMES.MAIN)?.isVisible()) return Object.values(this._winStates).forEach((win) => !win?.instance?.isVisible() && win?.instance?.close()); } _isHiddenWin(name) { return this._winStates[name] && this._winStates[name].isHidden; } _createWinInstance(name, opts) { return this._isHiddenWin(name) ? this._winStates[name].instance : new electron.BrowserWindow({ ...SHARED_WINDOW_OPTIONS, icon: this._logo, ...opts }); } focus(target) { if (!target) return; const name = this.getName(target); if (target?.isMaximized()) { target?.restore(); logManager.debug(`Window ${name} restored and focused`); } else { logManager.debug(`Window ${name} focused`); } target?.focus(); } close(target, really = true) { if (!target) return; const name = this.getName(target); logManager.info(`Close window: ${name}, really: ${really}`); this._handleCloseWindowState(target, really); } toggleMax(target) { if (!target) return; target.isMaximized() ? target.unmaximize() : target.maximize(); } getName(target) { if (!target) return; for (const [name, win] of Object.entries(this._winStates)) { if (win?.instance === target) return name; } } get(name) { if (this._winStates[name].isHidden) return void 0; return this._winStates[name].instance; } onWindowCreate(name, callback) { this._winStates[name].onCreate.push(callback); } onWindowClosed(name, callback) { this._winStates[name].onClosed.push(callback); } } const windowManager = WindowService.getInstance(); let t$1 = createTranslator(); class MenuService { static _instance; _menuTemplates = /* @__PURE__ */ new Map(); _currentMenu = void 0; constructor() { this._setupIpcListener(); this._setupLanguageChangeListener(); logManager.info("MenuService initialized successfully."); } _setupIpcListener() { electron.ipcMain.handle(IPC_EVENTS.SHOW_CONTEXT_MENU, (_, menuId, dynamicOptions) => new Promise((resolve) => this.showMenu(menuId, () => resolve(true), dynamicOptions))); } _setupLanguageChangeListener() { configManager.onConfigChange((config) => { if (!config[CONFIG_KEYS.LANGUAGE]) return; t$1 = createTranslator(); }); } static getInstance() { if (!this._instance) this._instance = new MenuService(); return this._instance; } register(menuId, template) { this._menuTemplates.set(menuId, template); return menuId; } showMenu(menuId, onClose, dynamicOptions) { if (this._currentMenu) return; const template = cloneDeep(this._menuTemplates.get(menuId)); if (!template) { logManager.warn(`Menu ${menuId} not found.`); onClose?.(); return; } let _dynamicOptions = []; try { _dynamicOptions = Array.isArray(dynamicOptions) ? dynamicOptions : JSON.parse(dynamicOptions ?? "[]"); } catch (error) { logManager.error(`Failed to parse dynamicOptions for menu ${menuId}: ${error}`); } const translationItem = (item) => { if (item.submenu) { return { ...item, label: t$1(item?.label) ?? void 0, submenu: item.submenu?.map((item2) => translationItem(item2)) }; } return { ...item, label: t$1(item?.label) ?? void 0 }; }; const localizedTemplate = template.map((item) => { if (!Array.isArray(_dynamicOptions) || !_dynamicOptions.length) { return translationItem(item); } const dynamicItem = _dynamicOptions.find((_item) => _item.id === item.id); if (dynamicItem) { const mergedItem = { ...item, ...dynamicItem }; return translationItem(mergedItem); } if (item.submenu) { return translationItem({ ...item, submenu: item.submenu?.map((__item) => { const dynamicItem2 = _dynamicOptions.find((_item) => _item.id === __item.id); return { ...__item, ...dynamicItem2 }; }) }); } return translationItem(item); }); const menu2 = electron.Menu.buildFromTemplate(localizedTemplate); this._currentMenu = menu2; menu2.popup({ callback: () => { this._currentMenu = void 0; onClose?.(); } }); } destroyMenu(menuId) { this._menuTemplates.delete(menuId); } destroyed() { this._menuTemplates.clear(); this._currentMenu = void 0; } } const menuManager = MenuService.getInstance(); let t = createTranslator(); class TrayService { static _instance; _tray = null; _removeLanguageListener; _setupLanguageChangeListener() { this._removeLanguageListener = configManager.onConfigChange((config) => { if (!config[CONFIG_KEYS.LANGUAGE]) return; t = createTranslator(); if (this._tray) { this._updateTray(); } }); } _updateTray() { if (!this._tray) { this._tray = new electron.Tray(createLogo()); } const showWindow = () => { const mainWindow = windowManager.get(WINDOW_NAMES.MAIN); if (mainWindow && !mainWindow?.isDestroyed() && mainWindow?.isVisible() && !mainWindow?.isFocused()) { return mainWindow.focus(); } if (mainWindow?.isMinimized()) { return mainWindow?.restore(); } if (mainWindow?.isVisible() && mainWindow?.isFocused()) return; windowManager.create(WINDOW_NAMES.MAIN, MAIN_WIN_SIZE); }; this._tray.setToolTip(t("tray.tooltip") ?? "Diona Application"); this._tray.setContextMenu(electron.Menu.buildFromTemplate([ { label: t("tray.showWindow"), accelerator: "CmdOrCtrl+N", click: showWindow }, { type: "separator" }, { label: t("settings.title"), click: () => electron.ipcMain.emit(`${IPC_EVENTS.OPEN_WINDOW}:${WINDOW_NAMES.SETTING}`) }, { role: "quit", label: t("tray.exit") } ])); this._tray.removeAllListeners("click"); this._tray.on("click", showWindow); } constructor() { this._setupLanguageChangeListener(); logManager.info("TrayService initialized successfully."); } static getInstance() { if (!this._instance) { this._instance = new TrayService(); } return this._instance; } create() { if (this._tray) return; this._updateTray(); electron.app.on("quit", () => { this.destroy(); }); } destroy() { this._tray?.destroy(); this._tray = null; if (this._removeLanguageListener) { this._removeLanguageListener(); this._removeLanguageListener = void 0; } } } const trayManager = TrayService.getInstance(); class TabManager { win; views = /* @__PURE__ */ new Map(); activeId = null; skipNextNavigate = /* @__PURE__ */ new Map(); enabled = false; constructor(win) { this.win = win; this.win.on("resize", () => this.updateActiveBounds()); this._setupIpcEvents(); } _setupIpcEvents() { electron.ipcMain.handle(IPC_EVENTS.TAB_CREATE, (_e, url) => { const info = this.create(url); return info; }); electron.ipcMain.handle(IPC_EVENTS.TAB_LIST, () => { return this.list(); }); electron.ipcMain.handle(IPC_EVENTS.TAB_NAVIGATE, (_e, { tabId, url }) => { this.navigate(tabId, url); }); electron.ipcMain.handle(IPC_EVENTS.TAB_RELOAD, (_e, tabId) => { this.reload(tabId); }); electron.ipcMain.handle(IPC_EVENTS.TAB_BACK, (_e, tabId) => { this.goBack(tabId); }); electron.ipcMain.handle(IPC_EVENTS.TAB_FORWARD, (_e, tabId) => { this.goForward(tabId); }); electron.ipcMain.handle(IPC_EVENTS.TAB_SWITCH, (_e, tabId) => { this.switch(tabId); }); electron.ipcMain.handle(IPC_EVENTS.TAB_CLOSE, (_e, tabId) => { this.close(tabId); }); } enable() { this.enabled = true; this.updateActiveBounds(); if (this.activeId) this.attach(this.activeId); } disable() { this.enabled = false; const view = this.activeId ? this.views.get(this.activeId) : null; if (view) this.win.removeBrowserView(view); } destroy() { this.disable(); this.views.forEach((view) => { view.webContents.destroy(); }); this.views.clear(); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_CREATE); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_LIST); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_NAVIGATE); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_RELOAD); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_BACK); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_FORWARD); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_SWITCH); electron.ipcMain.removeHandler(IPC_EVENTS.TAB_CLOSE); } list() { return Array.from(this.views.entries()).map(([id, view]) => this.info(id, view)); } create(url, active = true) { const id = crypto.randomUUID(); const view = new electron.BrowserView({ webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true, preload: path$1.join(process.cwd(), "dist-electron/preload/preload.js") } }); this.views.set(id, view); if (this.enabled && active) this.attach(id); const target = url && url.length > 0 ? url : "about:blank"; view.webContents.loadURL(target); this.bindEvents(id, view); const info = this.info(id, view); this.win.webContents.send("tab-created", info); return info; } switch(tabId) { if (!this.views.has(tabId)) return; if (this.enabled) this.attach(tabId); this.win.webContents.send("tab-switched", { tabId }); } close(tabId) { const view = this.views.get(tabId); if (!view) return; if (this.activeId === tabId) { this.win.removeBrowserView(view); this.activeId = null; } view.webContents.destroy(); this.views.delete(tabId); this.win.webContents.send("tab-closed", { tabId }); const next = this.views.keys().next().value; if (next) this.switch(next); } navigate(tabId, url) { const view = this.views.get(tabId); if (!view) return; this.skipNextNavigate.set(tabId, true); view.webContents.loadURL(url); } reload(tabId) { const view = this.views.get(tabId); if (!view) return; view.webContents.reload(); } goBack(tabId) { const view = this.views.get(tabId); if (!view) return; if (view.webContents.canGoBack()) view.webContents.goBack(); } goForward(tabId) { const view = this.views.get(tabId); if (!view) return; if (view.webContents.canGoForward()) view.webContents.goForward(); } attach(tabId) { if (!this.enabled) return; const view = this.views.get(tabId); if (!view) return; if (this.activeId && this.views.get(this.activeId)) { const prev = this.views.get(this.activeId); this.win.removeBrowserView(prev); } this.activeId = tabId; this.win.addBrowserView(view); this.updateActiveBounds(); } updateActiveBounds() { if (!this.enabled || !this.activeId) return; const view = this.views.get(this.activeId); if (!view) return; const [winWidth, winHeight] = this.win.getContentSize(); const HEADER_HEIGHT = 88; const PADDING = 8; const RIGHT_PANEL_WIDTH = 392 + 80 + 8 + 8; const x = PADDING; const y = HEADER_HEIGHT + PADDING; const width = winWidth - RIGHT_PANEL_WIDTH - PADDING; const height = winHeight - HEADER_HEIGHT - PADDING * 2; view.setBounds({ x, y, width: Math.max(0, width), height: Math.max(0, height) }); } bindEvents(id, view) { const send = () => this.win.webContents.send("tab-updated", this.info(id, view)); view.webContents.on("did-start-loading", send); view.webContents.on("did-stop-loading", send); view.webContents.on("did-finish-load", send); view.webContents.on("page-title-updated", send); view.webContents.on("did-navigate", send); view.webContents.on("did-navigate-in-page", send); view.webContents.on("will-navigate", (event, url) => { if (this.skipNextNavigate.get(id)) { this.skipNextNavigate.set(id, false); return; } event.preventDefault(); this.create(url); }); view.webContents.setWindowOpenHandler(({ url }) => { this.create(url); return { action: "deny" }; }); } info(id, view) { const wc = view.webContents; return { id, url: wc.getURL(), title: wc.getTitle(), isLoading: wc.isLoading(), canGoBack: wc.canGoBack(), canGoForward: wc.canGoForward() }; } } const handleTray = (minimizeToTray) => { if (minimizeToTray) { trayManager.create(); return; } trayManager.destroy(); }; const registerMenus = (window2) => { const conversationItemMenuItemClick = (id) => { logManager.logUserOperation(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.CONVERSATION_ITEM}-${id}`); window2.webContents.send(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.CONVERSATION_ITEM}`, id); }; menuManager.register(MENU_IDS.CONVERSATION_ITEM, [ { id: CONVERSATION_ITEM_MENU_IDS.PIN, label: "menu.conversation.pinConversation", click: () => conversationItemMenuItemClick(CONVERSATION_ITEM_MENU_IDS.PIN) }, { id: CONVERSATION_ITEM_MENU_IDS.RENAME, label: "menu.conversation.renameConversation", click: () => conversationItemMenuItemClick(CONVERSATION_ITEM_MENU_IDS.RENAME) }, { id: CONVERSATION_ITEM_MENU_IDS.DEL, label: "menu.conversation.delConversation", click: () => conversationItemMenuItemClick(CONVERSATION_ITEM_MENU_IDS.DEL) } ]); const conversationListMenuItemClick = (id) => { logManager.logUserOperation(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.CONVERSATION_LIST}-${id}`); window2.webContents.send(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.CONVERSATION_LIST}`, id); }; menuManager.register(MENU_IDS.CONVERSATION_LIST, [ { id: CONVERSATION_LIST_MENU_IDS.NEW_CONVERSATION, label: "menu.conversation.newConversation", click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.NEW_CONVERSATION) }, { type: "separator" }, { id: CONVERSATION_LIST_MENU_IDS.SORT_BY, label: "menu.conversation.sortBy", submenu: [ { id: CONVERSATION_LIST_MENU_IDS.SORT_BY_CREATE_TIME, label: "menu.conversation.sortByCreateTime", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_BY_CREATE_TIME) }, { id: CONVERSATION_LIST_MENU_IDS.SORT_BY_UPDATE_TIME, label: "menu.conversation.sortByUpdateTime", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_BY_UPDATE_TIME) }, { id: CONVERSATION_LIST_MENU_IDS.SORT_BY_NAME, label: "menu.conversation.sortByName", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_BY_NAME) }, { id: CONVERSATION_LIST_MENU_IDS.SORT_BY_MODEL, label: "menu.conversation.sortByModel", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_BY_MODEL) }, { type: "separator" }, { id: CONVERSATION_LIST_MENU_IDS.SORT_ASCENDING, label: "menu.conversation.sortAscending", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_ASCENDING) }, { id: CONVERSATION_LIST_MENU_IDS.SORT_DESCENDING, label: "menu.conversation.sortDescending", type: "radio", checked: false, click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.SORT_DESCENDING) } ] }, { id: CONVERSATION_LIST_MENU_IDS.BATCH_OPERATIONS, label: "menu.conversation.batchOperations", click: () => conversationListMenuItemClick(CONVERSATION_LIST_MENU_IDS.BATCH_OPERATIONS) } ]); const messageItemMenuItemClick = (id) => { logManager.logUserOperation(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.MESSAGE_ITEM}-${id}`); window2.webContents.send(`${IPC_EVENTS.SHOW_CONTEXT_MENU}:${MENU_IDS.MESSAGE_ITEM}`, id); }; menuManager.register(MENU_IDS.MESSAGE_ITEM, [ { id: MESSAGE_ITEM_MENU_IDS.COPY, label: "menu.message.copyMessage", click: () => messageItemMenuItemClick(MESSAGE_ITEM_MENU_IDS.COPY) }, { id: MESSAGE_ITEM_MENU_IDS.SELECT, label: "menu.message.selectMessage", click: () => messageItemMenuItemClick(MESSAGE_ITEM_MENU_IDS.SELECT) }, { type: "separator" }, { id: MESSAGE_ITEM_MENU_IDS.DELETE, label: "menu.message.deleteMessage", click: () => messageItemMenuItemClick(MESSAGE_ITEM_MENU_IDS.DELETE) } ]); }; function setupMainWindow() { windowManager.onWindowCreate(WINDOW_NAMES.MAIN, (mainWindow) => { let minimizeToTray = configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY); configManager.onConfigChange((config) => { if (minimizeToTray === config[CONFIG_KEYS.MINIMIZE_TO_TRAY]) return; minimizeToTray = config[CONFIG_KEYS.MINIMIZE_TO_TRAY]; handleTray(minimizeToTray); }); handleTray(minimizeToTray); registerMenus(mainWindow); const tabManager = new TabManager(mainWindow); tabManager.enable(); mainWindow.on("closed", () => { tabManager.destroy(); }); }); windowManager.create(WINDOW_NAMES.MAIN, MAIN_WIN_SIZE); electron.ipcMain.on(IPC_EVENTS.START_A_DIALOGUE, async (_event, props) => { const { providerName, messages: messages2, messageId, selectedModel } = props; const mainWindow = windowManager.get(WINDOW_NAMES.MAIN); if (!mainWindow) { throw new Error("mainWindow not found"); } try { const provider = createProvider(providerName); const chunks = await provider?.chat(messages2, selectedModel); if (!chunks) { throw new Error("chunks or stream not found"); } for await (const chunk of chunks) { const chunkContent = { messageId, data: chunk }; mainWindow.webContents.send(IPC_EVENTS.START_A_DIALOGUE + "back" + messageId, chunkContent); } } catch (error) { const errorContent = { messageId, data: { isEnd: true, isError: true, result: error instanceof Error ? error.message : String(error) } }; mainWindow.webContents.send(IPC_EVENTS.START_A_DIALOGUE + "back" + messageId, errorContent); } }); } function getChromePath() { if (process.platform === "win32") { return "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"; } if (process.platform === "darwin") { return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; } if (process.platform === "linux") { return "google-chrome"; } } function getProfileDir(accountId) { return path$1.join(electron.app.getPath("userData"), `profiles`, accountId); } function isPortInUse(port) { return new Promise((resolve) => { const server = net.createServer(); server.once("error", (err) => resolve(true)); server.once("listening", () => { server.close(); resolve(false); }); server.listen(port); }); } async function isChromeRunning() { try { return new Promise((resolve) => { const req = http.get("http://127.0.0.1:9222/json/version", (res) => { resolve(res.statusCode === 200); }); req.on("error", () => resolve(false)); req.setTimeout(1e3, () => { req.destroy(); resolve(false); }); }); } catch (error) { return false; } } async function launchLocalChrome() { const chromePath = getChromePath(); const profileDir = getProfileDir("default"); log.info(`Launching Chrome with user data dir: ${profileDir}`); const portInUse = await isPortInUse(9222); if (portInUse) { log.info("Chrome already running on port 9222, skip launching."); return; } if (await isChromeRunning()) { log.info("Chrome already running, skip launching."); return; } return new Promise((resolve, reject) => { const chromeProcess = child_process.spawn(chromePath, [ "--remote-debugging-port=9222", "--window-size=1920,1080", "--window-position=0,0", "--no-first-run", `--user-data-dir=${profileDir}`, "--no-default-browser-check", "about:blank" // '--window-maximized', ], { detached: true, stdio: "ignore" }); chromeProcess.on("error", reject); setTimeout(() => { resolve(0); }, 1e3); }); } class executeScriptService extends events.EventEmitter { // 执行脚本 async executeScript(scriptPath, options) { const MAX_TAIL = 32 * 1024; const appendTail = (current, chunk) => { const next = current + chunk; return next.length > MAX_TAIL ? next.slice(next.length - MAX_TAIL) : next; }; return await new Promise((resolve) => { try { const roomType = options?.roomType ?? ""; const startTime = options?.startTime ?? ""; const endTime = options?.endTime ?? ""; const operation = options?.operation ?? ""; const tabIndex = options?.tabIndex ?? ""; const channels = options?.channels ?? ""; const startTabIndex = options?.startTabIndex ?? ""; const child = electron.utilityProcess.fork(scriptPath, [], { env: { ...process.env, ROOM_TYPE: String(roomType), START_DATE: String(startTime), END_DATE: String(endTime), OPERATION: String(operation), TAB_INDEX: String(tabIndex), CHANNELS: typeof channels === "string" ? channels : JSON.stringify(channels), START_TAB_INDEX: String(startTabIndex) }, stdio: "pipe" }); let stdoutTail = ""; let stderrTail = ""; if (child.stdout) { child.stdout.on("data", (data) => { const text = data.toString(); stdoutTail = appendTail(stdoutTail, text); log.info(`stdout: ${text}`); }); } if (child.stderr) { child.stderr.on("data", (data) => { const text = data.toString(); stderrTail = appendTail(stderrTail, text); log.info(`stderr: ${text}`); }); } child.on("exit", (code) => { log.info(`子进程退出,退出码 ${code}`); resolve({ success: code === 0, exitCode: code, stdoutTail, stderrTail, ...code === 0 ? {} : { error: `Script exited with code ${code}` } }); }); } catch (error) { resolve({ success: false, exitCode: null, stdoutTail: "", stderrTail: "", error: error?.message || "运行 Node 脚本时出错" }); } }); } } const openedTabIndexByChannelName = /* @__PURE__ */ new Map(); function getScriptsDir() { return electron.app.isPackaged ? path.join(__dirname, "scripts") : path.join(process.cwd(), "electron/scripts"); } function runTaskOperationService() { const executeScriptServiceInstance = new executeScriptService(); electron.ipcMain.handle(IPC_EVENTS.OPEN_CHANNEL, async (_event, channels) => { try { await launchLocalChrome(); const scriptsDir = getScriptsDir(); const scriptPath = path.join(scriptsDir, "open_all_channel.js"); openedTabIndexByChannelName.clear(); if (Array.isArray(channels)) { for (let i = 0; i < channels.length; i++) { const name = channels[i]?.channelName; if (name) openedTabIndexByChannelName.set(String(name), i); } } const result = await executeScriptServiceInstance.executeScript(scriptPath, { channels }); return { success: true, result }; } catch (error) { return { success: false, error: error?.message || "open channel failed" }; } }); electron.ipcMain.handle(IPC_EVENTS.EXECUTE_SCRIPT, async (_event, options) => { try { const roomType = options.roomList.find((item) => item.id === options.roomType); const pairs = [ ["fzName", "fg_trace.js"], ["mtName", "mt_trace.js"], ["dyHotelName", "dy_hotel_trace.js"], ["dyHotSpringName", "dy_hot_spring_trace.js"] ]; const scriptEntries = pairs.filter(([prop]) => roomType?.[prop]); const scriptsDir = getScriptsDir(); const scriptPaths = scriptEntries.map(([channel, fileName]) => { const p = path.join(scriptsDir, fileName); if (!fs.existsSync(p)) { throw new Error(`Script not found for channel ${channel}: ${p}`); } return { channel, scriptPath: p }; }); const results = []; for (let i = 0; i < scriptPaths.length; i++) { const item = scriptPaths[i]; const channelNameMap = { fzName: "fliggy", mtName: "meituan", dyHotelName: "douyin", dyHotSpringName: "douyin" }; const defaultTabIndexMap = { fliggy: 0, meituan: 1, douyin: 2 }; const mappedName = channelNameMap[item.channel]; const tabIndex = mappedName ? openedTabIndexByChannelName.get(mappedName) ?? defaultTabIndexMap[mappedName] ?? i : i; log.info(`Launching script for channel ${item.channel}: ${item.scriptPath} (tabIndex: ${tabIndex})`); const result = await executeScriptServiceInstance.executeScript(item.scriptPath, { roomType: roomType[item.channel], startTime: options.startTime, endTime: options.endTime, operation: options.operation, tabIndex }); results.push({ channel: item.channel, scriptPath: item.scriptPath, ...result }); } return { success: true, result: results }; } catch (error) { return { success: false, error: error.message }; } }); } class AppUpdater { mainWindow = null; static _instance; _initialized = false; constructor() { electronUpdater.autoUpdater.autoDownload = false; } init() { if (this._initialized) return; this._initialized = true; this.setupListeners(); this.registerHandlers(); } static getInstance() { if (!this._instance) { this._instance = new AppUpdater(); } return this._instance; } setMainWindow(window2) { this.mainWindow = window2; } setupListeners() { electronUpdater.autoUpdater.on("checking-for-update", () => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "checking" })); electronUpdater.autoUpdater.on("update-available", (info) => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "available", info })); electronUpdater.autoUpdater.on("update-not-available", () => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "not-available" })); electronUpdater.autoUpdater.on("download-progress", (progress) => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "downloading", progress })); electronUpdater.autoUpdater.on("update-downloaded", (info) => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "downloaded", info })); electronUpdater.autoUpdater.on("error", (error) => this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "error", error: error.message })); } sendToRenderer(channel, data) { electron.BrowserWindow.getAllWindows().forEach((win) => { if (!win.isDestroyed()) { win.webContents.send(channel, data); } }); } registerHandlers() { electron.ipcMain.handle(IPC_EVENTS.UPDATE_CHECK, () => { if (electron.app.isPackaged) { return electronUpdater.autoUpdater.checkForUpdates(); } else { this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "checking" }); setTimeout(() => { this.sendToRenderer(IPC_EVENTS.UPDATE_STATUS_CHANGED, { status: "not-available" }); }, 1500); return null; } }); electron.ipcMain.handle(IPC_EVENTS.UPDATE_DOWNLOAD, () => { if (electron.app.isPackaged) { return electronUpdater.autoUpdater.downloadUpdate(); } return null; }); electron.ipcMain.handle(IPC_EVENTS.UPDATE_INSTALL, () => { if (electron.app.isPackaged) { return electronUpdater.autoUpdater.quitAndInstall(); } return null; }); electron.ipcMain.handle(IPC_EVENTS.UPDATE_VERSION, () => { return electron.app.getVersion(); }); } } const appUpdater = AppUpdater.getInstance(); appUpdater.init(); if (started) { electron.app.quit(); } electron.app.whenReady().then(() => { setupMainWindow(); runTaskOperationService(); }); electron.app.on("window-all-closed", () => { if (process.platform !== "darwin" && !configManager.get(CONFIG_KEYS.MINIMIZE_TO_TRAY)) { log.info("app closing due to all windows being closed"); electron.app.quit(); } }); electron.app.on("activate", () => { if (electron.BrowserWindow.getAllWindows().length === 0) { setupMainWindow(); } });