feat(desktop): native desktop enhancements

- Add a minimal application menu with common items and shortcuts:
  New Chat (Cmd+N), Settings (Cmd+,), Reload, etc.
- Add system tray with show window, new chat, and quit; click icon to restore
- Add single-instance lock so relaunching focuses the existing window
- Implement close-to-tray: closing the window hides it; only a real quit destroys it
- Explicitly define Window menu's Close Window bound to Cmd+W / Ctrl+W to reliably trigger hide-to-tray
- Listen for menu-action in the renderer to handle menu-triggered new chat / open settings
This commit is contained in:
zhayujie
2026-06-22 15:32:22 +08:00
parent d1336b872e
commit 49452e035d
6 changed files with 226 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useCallback, useEffect } from 'react'
import { Routes, Route, useLocation } from 'react-router-dom'
import { Routes, Route, useLocation, useNavigate } from 'react-router-dom'
import { PanelLeftOpen } from 'lucide-react'
import NavRail from './layout/NavRail'
import SessionList from './layout/SessionList'
@@ -8,6 +8,7 @@ import StatusScreen from './components/StatusScreen'
import { useBackend } from './hooks/useBackend'
import { usePlatform } from './hooks/usePlatform'
import { useUIStore } from './store/uiStore'
import { useSessionStore } from './store/sessionStore'
import apiClient from './api/client'
import { t } from './i18n'
import ChatPage from './pages/ChatPage'
@@ -22,6 +23,7 @@ import LogsPage from './pages/LogsPage'
const App: React.FC = () => {
const backend = useBackend()
const location = useLocation()
const navigate = useNavigate()
const { isWin } = usePlatform()
const { sessionsCollapsed, toggleSessions } = useUIStore()
const [, forceUpdate] = useState(0)
@@ -30,6 +32,18 @@ const App: React.FC = () => {
if (backend.status === 'ready') apiClient.setBaseUrl(backend.baseUrl)
}, [backend.status, backend.baseUrl])
// Handle app-menu / shortcut actions forwarded from the main process.
useEffect(() => {
window.electronAPI?.onMenuAction?.((action) => {
if (action === 'new-chat') {
useSessionStore.getState().newSession()
navigate('/')
} else if (action === 'open-settings') {
navigate('/settings')
}
})
}, [navigate])
const handleLangChange = useCallback(() => forceUpdate((n) => n + 1), [])
if (backend.status !== 'ready') {

View File

@@ -15,6 +15,7 @@ export interface ElectronAPI {
windowClose: () => Promise<void>
windowIsMaximized: () => Promise<boolean>
onMaximizeChange: (callback: (maximized: boolean) => void) => void
onMenuAction?: (callback: (action: string) => void) => void
platform: string
}