- Implement electron-updater integration with IPC handlers for update operations - Add Pinia store for managing update state and user preferences - Create settings UI with version display, update controls, and auto-update toggles - Update IPC constants and preload API for update-related communication - Refactor preload invoke methods to use async IPC consistently - Add rounded corners to settings page layout for better visual consistency
1645 lines
62 KiB
JavaScript
1645 lines
62 KiB
JavaScript
"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();
|
||
}
|
||
});
|