UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

234 lines (202 loc) 6.19 kB
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc'; import { DisplayBalloonOptions, Tray as ElectronTray, Menu, MenuItemConstructorOptions, app, nativeImage, } from 'electron'; import { join } from 'node:path'; import { resourcesDir } from '@/const/dir'; import { createLogger } from '@/utils/logger'; import type { App } from '../App'; // Create logger const logger = createLogger('core:Tray'); export interface TrayOptions { /** * Tray icon path (relative to resource directory) */ iconPath: string; /** * Tray identifier */ identifier: string; /** * Tray tooltip text */ tooltip?: string; } export class Tray { private app: App; /** * Internal Electron tray */ private _tray?: ElectronTray; /** * Identifier */ identifier: string; /** * Options when created */ options: TrayOptions; /** * Get tray instance */ get tray() { return this.retrieveOrInitialize(); } /** * Construct tray object * @param options Tray options * @param application App instance */ constructor(options: TrayOptions, application: App) { logger.debug(`Creating tray instance: ${options.identifier}`); logger.debug(`Tray options: ${JSON.stringify(options)}`); this.app = application; this.identifier = options.identifier; this.options = options; // Initialize this.retrieveOrInitialize(); } /** * Initialize tray */ retrieveOrInitialize() { // If tray already exists and is not destroyed, return it if (this._tray) { logger.debug(`[${this.identifier}] Returning existing tray instance`); return this._tray; } const { iconPath, tooltip } = this.options; // Load tray icon logger.info(`Creating new tray instance: ${this.identifier}`); const iconFile = join(resourcesDir, iconPath); logger.debug(`[${this.identifier}] Loading icon: ${iconFile}`); try { const icon = nativeImage.createFromPath(iconFile); this._tray = new ElectronTray(icon); // Set tooltip if (tooltip) { logger.debug(`[${this.identifier}] Setting tooltip: ${tooltip}`); this._tray.setToolTip(tooltip); } // Set default context menu this.setContextMenu(); // Set click event this._tray.on('click', () => { logger.debug(`[${this.identifier}] Tray clicked`); this.onClick(); }); logger.debug(`[${this.identifier}] Tray instance created successfully`); return this._tray; } catch (error) { logger.error(`[${this.identifier}] Failed to create tray:`, error); throw error; } } /** * Set tray context menu * @param template Menu template, if not provided default template will be used */ setContextMenu(template?: MenuItemConstructorOptions[]) { logger.debug(`[${this.identifier}] Setting tray context menu`); // If no template provided, use default menu const defaultTemplate: MenuItemConstructorOptions[] = template || [ { click: () => { logger.debug(`[${this.identifier}] Menu item "Show Main Window" clicked`); this.app.browserManager.showMainWindow(); }, label: 'Show Main Window', }, { type: 'separator' }, { click: () => { logger.debug(`[${this.identifier}] Menu item "Quit" clicked`); app.quit(); }, label: 'Quit', }, ]; const contextMenu = Menu.buildFromTemplate(defaultTemplate); this._tray?.setContextMenu(contextMenu); logger.debug(`[${this.identifier}] Tray context menu has been set`); } /** * Handle tray click event */ onClick() { logger.debug(`[${this.identifier}] Handling tray click event`); const mainWindow = this.app.browserManager.getMainWindow(); if (mainWindow) { if (mainWindow.browserWindow.isVisible() && mainWindow.browserWindow.isFocused()) { logger.debug(`[${this.identifier}] Main window is visible and focused, hiding it now`); mainWindow.hide(); } else { logger.debug(`[${this.identifier}] Showing and focusing main window`); mainWindow.show(); mainWindow.browserWindow.focus(); } } } /** * Update tray icon * @param iconPath New icon path (relative to resource directory) */ updateIcon(iconPath: string) { logger.debug(`[${this.identifier}] Updating icon: ${iconPath}`); try { const iconFile = join(resourcesDir, iconPath); const icon = nativeImage.createFromPath(iconFile); this._tray?.setImage(icon); this.options.iconPath = iconPath; logger.debug(`[${this.identifier}] Icon updated successfully`); } catch (error) { logger.error(`[${this.identifier}] Failed to update icon:`, error); } } /** * Update tooltip text * @param tooltip New tooltip text */ updateTooltip(tooltip: string) { logger.debug(`[${this.identifier}] Updating tooltip: ${tooltip}`); this._tray?.setToolTip(tooltip); this.options.tooltip = tooltip; } /** * Display balloon notification (only supported on Windows) * @param options Balloon options */ displayBalloon(options: DisplayBalloonOptions) { if (process.platform === 'win32' && this._tray) { logger.debug( `[${this.identifier}] Displaying balloon notification: ${JSON.stringify(options)}`, ); this._tray.displayBalloon(options); } else { logger.debug(`[${this.identifier}] Balloon notification is only supported on Windows`); } } /** * Broadcast event */ broadcast = <T extends MainBroadcastEventKey>(channel: T, data?: MainBroadcastParams<T>) => { logger.debug(`Broadcasting to tray ${this.identifier}, channel: ${channel}`); // Can forward message to main window through App instance's browserManager this.app.browserManager.getMainWindow()?.broadcast(channel, data); }; /** * Destroy tray instance */ destroy() { logger.debug(`Destroying tray instance: ${this.identifier}`); if (this._tray) { this._tray.destroy(); this._tray = undefined; } } }