mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-03 15:08:25 +00:00
134
electron/main.ts
134
electron/main.ts
@@ -171,6 +171,118 @@ const AUTO_UPDATE_ENABLED =
|
|||||||
process.env.AUTO_UPDATE_ENABLED === '1' ||
|
process.env.AUTO_UPDATE_ENABLED === '1' ||
|
||||||
(process.env.AUTO_UPDATE_ENABLED == null && !process.env.VITE_DEV_SERVER_URL)
|
(process.env.AUTO_UPDATE_ENABLED == null && !process.env.VITE_DEV_SERVER_URL)
|
||||||
|
|
||||||
|
const getLaunchAtStartupUnsupportedReason = (): string | null => {
|
||||||
|
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
||||||
|
return '当前平台暂不支持开机自启动'
|
||||||
|
}
|
||||||
|
if (!app.isPackaged) {
|
||||||
|
return '仅安装后的 Windows / macOS 版本支持开机自启动'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLaunchAtStartupSupported = (): boolean => getLaunchAtStartupUnsupportedReason() == null
|
||||||
|
|
||||||
|
const getStoredLaunchAtStartupPreference = (): boolean | undefined => {
|
||||||
|
const value = configService?.get('launchAtStartup')
|
||||||
|
return typeof value === 'boolean' ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSystemLaunchAtStartup = (): boolean => {
|
||||||
|
if (!isLaunchAtStartupSupported()) return false
|
||||||
|
try {
|
||||||
|
return app.getLoginItemSettings().openAtLogin === true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[WeFlow] 读取开机自启动状态失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildLaunchAtStartupSettings = (enabled: boolean): Parameters<typeof app.setLoginItemSettings>[0] =>
|
||||||
|
process.platform === 'win32'
|
||||||
|
? { openAtLogin: enabled, path: process.execPath }
|
||||||
|
: { openAtLogin: enabled }
|
||||||
|
|
||||||
|
const setSystemLaunchAtStartup = (enabled: boolean): { success: boolean; enabled: boolean; error?: string } => {
|
||||||
|
try {
|
||||||
|
app.setLoginItemSettings(buildLaunchAtStartupSettings(enabled))
|
||||||
|
const effectiveEnabled = app.getLoginItemSettings().openAtLogin === true
|
||||||
|
if (effectiveEnabled !== enabled) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
enabled: effectiveEnabled,
|
||||||
|
error: '系统未接受该开机自启动设置'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { success: true, enabled: effectiveEnabled }
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
enabled: getSystemLaunchAtStartup(),
|
||||||
|
error: `设置开机自启动失败: ${String((error as Error)?.message || error)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLaunchAtStartupStatus = (): { enabled: boolean; supported: boolean; reason?: string } => {
|
||||||
|
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||||
|
if (unsupportedReason) {
|
||||||
|
return {
|
||||||
|
enabled: getStoredLaunchAtStartupPreference() === true,
|
||||||
|
supported: false,
|
||||||
|
reason: unsupportedReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
enabled: getSystemLaunchAtStartup(),
|
||||||
|
supported: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyLaunchAtStartupPreference = (
|
||||||
|
enabled: boolean
|
||||||
|
): { success: boolean; enabled: boolean; supported: boolean; reason?: string; error?: string } => {
|
||||||
|
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||||
|
if (unsupportedReason) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
enabled: getStoredLaunchAtStartupPreference() === true,
|
||||||
|
supported: false,
|
||||||
|
reason: unsupportedReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = setSystemLaunchAtStartup(enabled)
|
||||||
|
configService?.set('launchAtStartup', result.enabled)
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
supported: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncLaunchAtStartupPreference = () => {
|
||||||
|
if (!configService) return
|
||||||
|
|
||||||
|
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||||
|
if (unsupportedReason) return
|
||||||
|
|
||||||
|
const storedPreference = getStoredLaunchAtStartupPreference()
|
||||||
|
const systemEnabled = getSystemLaunchAtStartup()
|
||||||
|
|
||||||
|
if (typeof storedPreference !== 'boolean') {
|
||||||
|
configService.set('launchAtStartup', systemEnabled)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedPreference === systemEnabled) return
|
||||||
|
|
||||||
|
const result = setSystemLaunchAtStartup(storedPreference)
|
||||||
|
configService.set('launchAtStartup', result.enabled)
|
||||||
|
if (!result.success && result.error) {
|
||||||
|
console.error('[WeFlow] 同步开机自启动设置失败:', result.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 使用白名单过滤 PATH,避免被第三方目录中的旧版 VC++ 运行库劫持。
|
// 使用白名单过滤 PATH,避免被第三方目录中的旧版 VC++ 运行库劫持。
|
||||||
// 仅保留系统目录(Windows/System32/SysWOW64)和应用自身目录(可执行目录、resources)。
|
// 仅保留系统目录(Windows/System32/SysWOW64)和应用自身目录(可执行目录、resources)。
|
||||||
function sanitizePathEnv() {
|
function sanitizePathEnv() {
|
||||||
@@ -1250,7 +1362,12 @@ function registerIpcHandlers() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('config:set', async (_, key: string, value: any) => {
|
ipcMain.handle('config:set', async (_, key: string, value: any) => {
|
||||||
const result = configService?.set(key as any, value)
|
let result: unknown
|
||||||
|
if (key === 'launchAtStartup') {
|
||||||
|
result = applyLaunchAtStartupPreference(value === true)
|
||||||
|
} else {
|
||||||
|
result = configService?.set(key as any, value)
|
||||||
|
}
|
||||||
if (key === 'updateChannel') {
|
if (key === 'updateChannel') {
|
||||||
applyAutoUpdateChannel('settings')
|
applyAutoUpdateChannel('settings')
|
||||||
}
|
}
|
||||||
@@ -1259,6 +1376,12 @@ function registerIpcHandlers() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('config:clear', async () => {
|
ipcMain.handle('config:clear', async () => {
|
||||||
|
if (isLaunchAtStartupSupported() && getSystemLaunchAtStartup()) {
|
||||||
|
const result = setSystemLaunchAtStartup(false)
|
||||||
|
if (!result.success && result.error) {
|
||||||
|
console.error('[WeFlow] 清空配置时关闭开机自启动失败:', result.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
configService?.clear()
|
configService?.clear()
|
||||||
messagePushService.handleConfigCleared()
|
messagePushService.handleConfigCleared()
|
||||||
return true
|
return true
|
||||||
@@ -1301,6 +1424,14 @@ function registerIpcHandlers() {
|
|||||||
return app.getVersion()
|
return app.getVersion()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('app:getLaunchAtStartupStatus', async () => {
|
||||||
|
return getLaunchAtStartupStatus()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('app:setLaunchAtStartup', async (_, enabled: boolean) => {
|
||||||
|
return applyLaunchAtStartupPreference(enabled === true)
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('app:checkWayland', async () => {
|
ipcMain.handle('app:checkWayland', async () => {
|
||||||
if (process.platform !== 'linux') return false;
|
if (process.platform !== 'linux') return false;
|
||||||
|
|
||||||
@@ -2881,6 +3012,7 @@ app.whenReady().then(async () => {
|
|||||||
updateSplashProgress(5, '正在加载配置...')
|
updateSplashProgress(5, '正在加载配置...')
|
||||||
configService = new ConfigService()
|
configService = new ConfigService()
|
||||||
applyAutoUpdateChannel('startup')
|
applyAutoUpdateChannel('startup')
|
||||||
|
syncLaunchAtStartupPreference()
|
||||||
|
|
||||||
// 将用户主题配置推送给 Splash 窗口
|
// 将用户主题配置推送给 Splash 窗口
|
||||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
app: {
|
app: {
|
||||||
getDownloadsPath: () => ipcRenderer.invoke('app:getDownloadsPath'),
|
getDownloadsPath: () => ipcRenderer.invoke('app:getDownloadsPath'),
|
||||||
getVersion: () => ipcRenderer.invoke('app:getVersion'),
|
getVersion: () => ipcRenderer.invoke('app:getVersion'),
|
||||||
|
getLaunchAtStartupStatus: () => ipcRenderer.invoke('app:getLaunchAtStartupStatus'),
|
||||||
|
setLaunchAtStartup: (enabled: boolean) => ipcRenderer.invoke('app:setLaunchAtStartup', enabled),
|
||||||
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
|
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
|
||||||
downloadAndInstall: () => ipcRenderer.invoke('app:downloadAndInstall'),
|
downloadAndInstall: () => ipcRenderer.invoke('app:downloadAndInstall'),
|
||||||
ignoreUpdate: (version: string) => ipcRenderer.invoke('app:ignoreUpdate', version),
|
ignoreUpdate: (version: string) => ipcRenderer.invoke('app:ignoreUpdate', version),
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface ConfigSchema {
|
|||||||
themeId: string
|
themeId: string
|
||||||
language: string
|
language: string
|
||||||
logEnabled: boolean
|
logEnabled: boolean
|
||||||
|
launchAtStartup?: boolean
|
||||||
llmModelPath: string
|
llmModelPath: string
|
||||||
whisperModelName: string
|
whisperModelName: string
|
||||||
whisperModelDir: string
|
whisperModelDir: string
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right')
|
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right')
|
||||||
const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all')
|
const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all')
|
||||||
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
|
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
|
||||||
|
const [launchAtStartup, setLaunchAtStartup] = useState(false)
|
||||||
|
const [launchAtStartupSupported, setLaunchAtStartupSupported] = useState(isWindows || isMac)
|
||||||
|
const [launchAtStartupReason, setLaunchAtStartupReason] = useState('')
|
||||||
const [windowCloseBehavior, setWindowCloseBehavior] = useState<configService.WindowCloseBehavior>('ask')
|
const [windowCloseBehavior, setWindowCloseBehavior] = useState<configService.WindowCloseBehavior>('ask')
|
||||||
const [quoteLayout, setQuoteLayout] = useState<configService.QuoteLayout>('quote-top')
|
const [quoteLayout, setQuoteLayout] = useState<configService.QuoteLayout>('quote-top')
|
||||||
const [updateChannel, setUpdateChannel] = useState<configService.UpdateChannel>('stable')
|
const [updateChannel, setUpdateChannel] = useState<configService.UpdateChannel>('stable')
|
||||||
@@ -162,6 +165,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const [isFetchingDbKey, setIsFetchingDbKey] = useState(false)
|
const [isFetchingDbKey, setIsFetchingDbKey] = useState(false)
|
||||||
const [isFetchingImageKey, setIsFetchingImageKey] = useState(false)
|
const [isFetchingImageKey, setIsFetchingImageKey] = useState(false)
|
||||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
|
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
|
||||||
|
const [isUpdatingLaunchAtStartup, setIsUpdatingLaunchAtStartup] = useState(false)
|
||||||
const [appVersion, setAppVersion] = useState('')
|
const [appVersion, setAppVersion] = useState('')
|
||||||
const [message, setMessage] = useState<{ text: string; success: boolean } | null>(null)
|
const [message, setMessage] = useState<{ text: string; success: boolean } | null>(null)
|
||||||
const [showDecryptKey, setShowDecryptKey] = useState(false)
|
const [showDecryptKey, setShowDecryptKey] = useState(false)
|
||||||
@@ -337,6 +341,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
const savedNotificationFilterMode = await configService.getNotificationFilterMode()
|
const savedNotificationFilterMode = await configService.getNotificationFilterMode()
|
||||||
const savedNotificationFilterList = await configService.getNotificationFilterList()
|
const savedNotificationFilterList = await configService.getNotificationFilterList()
|
||||||
const savedMessagePushEnabled = await configService.getMessagePushEnabled()
|
const savedMessagePushEnabled = await configService.getMessagePushEnabled()
|
||||||
|
const savedLaunchAtStartupStatus = await window.electronAPI.app.getLaunchAtStartupStatus()
|
||||||
const savedWindowCloseBehavior = await configService.getWindowCloseBehavior()
|
const savedWindowCloseBehavior = await configService.getWindowCloseBehavior()
|
||||||
const savedQuoteLayout = await configService.getQuoteLayout()
|
const savedQuoteLayout = await configService.getQuoteLayout()
|
||||||
const savedUpdateChannel = await configService.getUpdateChannel()
|
const savedUpdateChannel = await configService.getUpdateChannel()
|
||||||
@@ -386,6 +391,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
setNotificationFilterMode(savedNotificationFilterMode)
|
setNotificationFilterMode(savedNotificationFilterMode)
|
||||||
setNotificationFilterList(savedNotificationFilterList)
|
setNotificationFilterList(savedNotificationFilterList)
|
||||||
setMessagePushEnabled(savedMessagePushEnabled)
|
setMessagePushEnabled(savedMessagePushEnabled)
|
||||||
|
setLaunchAtStartup(savedLaunchAtStartupStatus.enabled)
|
||||||
|
setLaunchAtStartupSupported(savedLaunchAtStartupStatus.supported)
|
||||||
|
setLaunchAtStartupReason(savedLaunchAtStartupStatus.reason || '')
|
||||||
setWindowCloseBehavior(savedWindowCloseBehavior)
|
setWindowCloseBehavior(savedWindowCloseBehavior)
|
||||||
setQuoteLayout(savedQuoteLayout)
|
setQuoteLayout(savedQuoteLayout)
|
||||||
if (savedUpdateChannel) {
|
if (savedUpdateChannel) {
|
||||||
@@ -428,6 +436,29 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleLaunchAtStartupChange = async (enabled: boolean) => {
|
||||||
|
if (isUpdatingLaunchAtStartup) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsUpdatingLaunchAtStartup(true)
|
||||||
|
const result = await window.electronAPI.app.setLaunchAtStartup(enabled)
|
||||||
|
setLaunchAtStartup(result.enabled)
|
||||||
|
setLaunchAtStartupSupported(result.supported)
|
||||||
|
setLaunchAtStartupReason(result.reason || '')
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showMessage(enabled ? '已开启开机自启动' : '已关闭开机自启动', true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessage(result.error || result.reason || '设置开机自启动失败', false)
|
||||||
|
} catch (e: any) {
|
||||||
|
showMessage(`设置开机自启动失败: ${e?.message || String(e)}`, false)
|
||||||
|
} finally {
|
||||||
|
setIsUpdatingLaunchAtStartup(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshWhisperStatus = async (modelDirValue = whisperModelDir) => {
|
const refreshWhisperStatus = async (modelDirValue = whisperModelDir) => {
|
||||||
try {
|
try {
|
||||||
const result = await window.electronAPI.whisper?.getModelStatus()
|
const result = await window.electronAPI.whisper?.getModelStatus()
|
||||||
@@ -1199,6 +1230,39 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
|
|
||||||
<div className="divider" />
|
<div className="divider" />
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label>开机自启动</label>
|
||||||
|
<span className="form-hint">
|
||||||
|
{launchAtStartupSupported
|
||||||
|
? '开启后,登录系统时会自动启动 WeFlow。'
|
||||||
|
: launchAtStartupReason || '当前环境暂不支持开机自启动。'}
|
||||||
|
</span>
|
||||||
|
<div className="log-toggle-line">
|
||||||
|
<span className="log-status">
|
||||||
|
{isUpdatingLaunchAtStartup
|
||||||
|
? '保存中...'
|
||||||
|
: launchAtStartupSupported
|
||||||
|
? (launchAtStartup ? '已开启' : '已关闭')
|
||||||
|
: '当前不可用'}
|
||||||
|
</span>
|
||||||
|
<label className="switch" htmlFor="launch-at-startup-toggle">
|
||||||
|
<input
|
||||||
|
id="launch-at-startup-toggle"
|
||||||
|
className="switch-input"
|
||||||
|
type="checkbox"
|
||||||
|
checked={launchAtStartup}
|
||||||
|
disabled={!launchAtStartupSupported || isUpdatingLaunchAtStartup}
|
||||||
|
onChange={(e) => {
|
||||||
|
void handleLaunchAtStartupChange(e.target.checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="switch-slider" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divider" />
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>关闭主窗口时</label>
|
<label>关闭主窗口时</label>
|
||||||
<span className="form-hint">设置点击关闭按钮后的默认行为;选择“每次询问”时会弹出关闭确认。</span>
|
<span className="form-hint">设置点击关闭按钮后的默认行为;选择“每次询问”时会弹出关闭确认。</span>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export const CONFIG_KEYS = {
|
|||||||
LAST_SESSION: 'lastSession',
|
LAST_SESSION: 'lastSession',
|
||||||
WINDOW_BOUNDS: 'windowBounds',
|
WINDOW_BOUNDS: 'windowBounds',
|
||||||
CACHE_PATH: 'cachePath',
|
CACHE_PATH: 'cachePath',
|
||||||
|
LAUNCH_AT_STARTUP: 'launchAtStartup',
|
||||||
|
|
||||||
EXPORT_PATH: 'exportPath',
|
EXPORT_PATH: 'exportPath',
|
||||||
AGREEMENT_ACCEPTED: 'agreementAccepted',
|
AGREEMENT_ACCEPTED: 'agreementAccepted',
|
||||||
@@ -258,6 +259,18 @@ export async function setLogEnabled(enabled: boolean): Promise<void> {
|
|||||||
await config.set(CONFIG_KEYS.LOG_ENABLED, enabled)
|
await config.set(CONFIG_KEYS.LOG_ENABLED, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取开机自启动偏好
|
||||||
|
export async function getLaunchAtStartup(): Promise<boolean | null> {
|
||||||
|
const value = await config.get(CONFIG_KEYS.LAUNCH_AT_STARTUP)
|
||||||
|
if (typeof value === 'boolean') return value
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置开机自启动偏好
|
||||||
|
export async function setLaunchAtStartup(enabled: boolean): Promise<void> {
|
||||||
|
await config.set(CONFIG_KEYS.LAUNCH_AT_STARTUP, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取 LLM 模型路径
|
// 获取 LLM 模型路径
|
||||||
export async function getLlmModelPath(): Promise<string | null> {
|
export async function getLlmModelPath(): Promise<string | null> {
|
||||||
const value = await config.get(CONFIG_KEYS.LLM_MODEL_PATH)
|
const value = await config.get(CONFIG_KEYS.LLM_MODEL_PATH)
|
||||||
|
|||||||
8
src/types/electron.d.ts
vendored
8
src/types/electron.d.ts
vendored
@@ -56,6 +56,14 @@ export interface ElectronAPI {
|
|||||||
app: {
|
app: {
|
||||||
getDownloadsPath: () => Promise<string>
|
getDownloadsPath: () => Promise<string>
|
||||||
getVersion: () => Promise<string>
|
getVersion: () => Promise<string>
|
||||||
|
getLaunchAtStartupStatus: () => Promise<{ enabled: boolean; supported: boolean; reason?: string }>
|
||||||
|
setLaunchAtStartup: (enabled: boolean) => Promise<{
|
||||||
|
success: boolean
|
||||||
|
enabled: boolean
|
||||||
|
supported: boolean
|
||||||
|
reason?: string
|
||||||
|
error?: string
|
||||||
|
}>
|
||||||
checkForUpdates: () => Promise<{ hasUpdate: boolean; version?: string; releaseNotes?: string }>
|
checkForUpdates: () => Promise<{ hasUpdate: boolean; version?: string; releaseNotes?: string }>
|
||||||
downloadAndInstall: () => Promise<void>
|
downloadAndInstall: () => Promise<void>
|
||||||
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
|
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
|
||||||
|
|||||||
Reference in New Issue
Block a user