feat(desktop): first-run onboarding, OS-language default, native polish

This commit is contained in:
zhayujie
2026-06-24 16:38:51 +08:00
parent ab6f49a822
commit 44b61684ed
13 changed files with 470 additions and 41 deletions

View File

@@ -7,6 +7,10 @@ import { buildAppMenu } from './menu'
import { createTray, destroyTray } from './tray'
import { initUpdater, checkForUpdates, startDownload, quitAndInstall } from './updater'
// Force the product name so the Dock/menu shows "CowAgent" even in dev mode,
// where the default Electron binary would otherwise report "Electron".
app.setName('CowAgent')
let mainWindow: BrowserWindow | null = null
let pythonBackend: PythonBackend | null = null
// True once the user explicitly quits (menu/tray), so close-to-tray is bypassed.
@@ -43,16 +47,6 @@ function getIconPath(ext: string = 'png'): string | undefined {
return undefined
}
// Resolve the optional monochrome macOS menu-bar template icon. Returns
// undefined when the asset isn't present, so the tray falls back gracefully.
function getTrayTemplatePath(): string | undefined {
const file = 'trayTemplate.png'
const p = isDev
? path.resolve(__dirname, '../../resources', file)
: path.join(process.resourcesPath, file)
return fs.existsSync(p) ? p : undefined
}
const isMac = process.platform === 'darwin'
const isWin = process.platform === 'win32'
@@ -224,6 +218,12 @@ function setupIPC() {
ipcMain.handle('update-check', () => checkForUpdates())
ipcMain.handle('update-download', () => startDownload())
ipcMain.handle('update-install', () => quitAndInstall())
// Synchronous OS locale lookup (e.g. "zh-CN", "en-US"). Used by the renderer
// to pick a sensible default UI language on first run before any paint.
ipcMain.on('get-system-locale', (event) => {
event.returnValue = app.getLocale() || app.getSystemLocale?.() || ''
})
}
function emitMaximizeState() {
@@ -265,17 +265,18 @@ app.whenReady().then(async () => {
setupIPC()
createWindow()
buildAppMenu(() => mainWindow)
createTray({
getWindow: () => mainWindow,
iconPath: getIconPath('png'),
// Monochrome menu-bar icon for macOS; falls back to the colored icon when
// the template asset is absent (see createTray).
templateIconPath: getTrayTemplatePath(),
onQuit: () => {
isQuitting = true
app.quit()
},
})
// No menu-bar tray on macOS — the Dock + window controls are enough there.
// Keep the tray on Windows/Linux where minimizing to a tray icon is expected.
if (!isMac) {
createTray({
getWindow: () => mainWindow,
iconPath: getIconPath('png'),
onQuit: () => {
isQuitting = true
app.quit()
},
})
}
await startBackend()
// Wire auto-update and do a first silent check a few seconds after launch so

View File

@@ -50,4 +50,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
},
platform: process.platform,
// OS UI language (e.g. "zh-CN"), read synchronously so the renderer can pick
// a default language on first run. Falls back to '' if unavailable.
systemLocale: (() => {
try {
return ipcRenderer.sendSync('get-system-locale') as string
} catch {
return ''
}
})(),
})

View File

@@ -6,31 +6,21 @@ interface TrayDeps {
getWindow: () => BrowserWindow | null
// Colored icon used on Windows/Linux trays.
iconPath?: string
// Monochrome (black + alpha) template icon for the macOS menu bar; renders
// correctly in both light and dark menu bars when set as a template image.
templateIconPath?: string
// Called when the user picks "Quit" so the app can fully exit.
onQuit: () => void
}
// Build a system tray icon with a minimal menu. The tray lets users restore the
// window after closing it to the background and start a new chat quickly.
export function createTray({ getWindow, iconPath, templateIconPath, onQuit }: TrayDeps): Tray | null {
// Build a system tray icon with a minimal menu (Windows/Linux only — macOS
// uses the Dock instead). Lets users restore the window after closing it to the
// background and start a new chat quickly.
export function createTray({ getWindow, iconPath, onQuit }: TrayDeps): Tray | null {
if (tray) return tray
if (!iconPath) return null
const isMac = process.platform === 'darwin'
// Prefer the monochrome template icon on macOS (menu-bar convention).
const sourcePath = isMac && templateIconPath ? templateIconPath : iconPath
if (!sourcePath) return null
let image = nativeImage.createFromPath(sourcePath)
let image = nativeImage.createFromPath(iconPath)
if (image.isEmpty()) return null
// Tray icons render small; resize to avoid an oversized image on some platforms.
image = image.resize({ width: 18, height: 18 })
// A template image must be pure black + alpha; macOS then auto-inverts it for
// light/dark menu bars. Only mark it as such when we actually loaded the
// dedicated template asset (a colored icon as template would render as a blob).
if (isMac && templateIconPath) image.setTemplateImage(true)
tray = new Tray(image)
tray.setToolTip(app.name)