import { BrowserWindow, clipboard, Menu } from 'electron' import type { IpcMainInvokeEvent, MenuItemConstructorOptions } from 'electron' import type { ChatClipboardAction, ChatTextMenuAction, ChatTextMenuOptions } from '../shared/chatTextMenu' function trustedChatFrame(event: IpcMainInvokeEvent): boolean { if (event.sender.isDestroyed() || event.senderFrame !== event.sender.mainFrame) return false const window = BrowserWindow.fromWebContents(event.sender) return Boolean(window && !window.isDestroyed()) } export function readChatClipboardState(event: IpcMainInvokeEvent): { hasText: boolean } { return { hasText: trustedChatFrame(event) && clipboard.readText().length > 0 } } export function performChatClipboardAction(event: IpcMainInvokeEvent, input: ChatClipboardAction): boolean { if (!trustedChatFrame(event) || !input) return false if (input.action === 'copy' && typeof input.text === 'string' && input.text.length > 0 && input.text.length <= 5_000_000) { clipboard.writeText(input.text) return true } if (input.action === 'paste' && clipboard.readText().length > 0) { event.sender.paste() return true } return false } export function showChatTextMenu(event: IpcMainInvokeEvent, input: ChatTextMenuOptions): Promise | null { if (!input || !['message', 'input'].includes(input.kind) || event.sender.isDestroyed() || event.senderFrame !== event.sender.mainFrame) return null const window = BrowserWindow.fromWebContents(event.sender) if (!window || window.isDestroyed()) return null const label = (value: unknown, fallback: string): string => typeof value === 'string' && value.trim() ? value.slice(0, 80) : fallback return new Promise((resolve) => { const items: MenuItemConstructorOptions[] = [{ label: label(input.labels?.copy, 'Copy'), enabled: input.canCopy === true, click: () => { if (!event.sender.isDestroyed() && input.canCopy === true) event.sender.copy() resolve(null) } }] if (input.kind === 'input') { items.push({ label: label(input.labels?.paste, 'Paste'), enabled: input.canPaste === true && clipboard.readText().length > 0, click: () => { if (!event.sender.isDestroyed() && input.canPaste === true && clipboard.readText().length > 0) event.sender.paste() resolve(null) } }) } else if (input.canClear !== undefined) { items.push({ type: 'separator' }, { label: label(input.labels?.clear, 'Delete conversation'), enabled: input.canClear === true, click: () => resolve(input.canClear === true ? 'clear' : null) }) } Menu.buildFromTemplate(items).popup({ window, callback: () => resolve(null) }) }) }