From 5ae3a9992533b8e90c200c79b77a22fe2b0fe458 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 19:49:07 +0800 Subject: [PATCH 01/18] =?UTF-8?q?feat(types):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 ShortcutBehavior 类型(new-conversation | current-conversation) - 添加 ShortcutConfig 接口(accelerator, enabled, behavior) - 添加默认配置常量(DEFAULT_CHAT_SHORTCUT, DEFAULT_AGENT_SHORTCUT) - 在 AppSettings 接口中添加 chatShortcut 和 agentShortcut 字段 - 扩展 SETTINGS_IPC_CHANNELS 常量(REGISTER_SHORTCUTS, UNREGISTER_SHORTCUTS, VALIDATE_SHORTCUT) Co-Authored-By: Claude Sonnet 4.5 --- apps/electron/src/types/settings.ts | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/apps/electron/src/types/settings.ts b/apps/electron/src/types/settings.ts index 19bb710..b681bbb 100644 --- a/apps/electron/src/types/settings.ts +++ b/apps/electron/src/types/settings.ts @@ -12,6 +12,33 @@ export type ThemeMode = 'light' | 'dark' | 'system' /** 默认主题模式 */ export const DEFAULT_THEME_MODE: ThemeMode = 'dark' +/** 快捷键行为模式 */ +export type ShortcutBehavior = 'new-conversation' | 'current-conversation' + +/** 快捷键配置 */ +export interface ShortcutConfig { + /** 快捷键组合(如 'CommandOrControl+Shift+C') */ + accelerator: string + /** 是否启用 */ + enabled: boolean + /** 行为模式 */ + behavior: ShortcutBehavior +} + +/** 默认 Chat 快捷键配置 */ +export const DEFAULT_CHAT_SHORTCUT: ShortcutConfig = { + accelerator: 'CommandOrControl+Shift+C', + enabled: true, + behavior: 'new-conversation', +} + +/** 默认 Agent 快捷键配置 */ +export const DEFAULT_AGENT_SHORTCUT: ShortcutConfig = { + accelerator: 'CommandOrControl+Shift+A', + enabled: true, + behavior: 'new-conversation', +} + /** 应用设置 */ export interface AppSettings { /** 主题模式 */ @@ -30,6 +57,10 @@ export interface AppSettings { lastEnvironmentCheck?: EnvironmentCheckResult /** 是否启用桌面通知 */ notificationsEnabled?: boolean + /** Chat 模式快捷键配置 */ + chatShortcut?: ShortcutConfig + /** Agent 模式快捷键配置 */ + agentShortcut?: ShortcutConfig } /** 设置 IPC 通道 */ @@ -38,4 +69,7 @@ export const SETTINGS_IPC_CHANNELS = { UPDATE: 'settings:update', GET_SYSTEM_THEME: 'settings:get-system-theme', ON_SYSTEM_THEME_CHANGED: 'settings:system-theme-changed', + REGISTER_SHORTCUTS: 'settings:register-shortcuts', + UNREGISTER_SHORTCUTS: 'settings:unregister-shortcuts', + VALIDATE_SHORTCUT: 'settings:validate-shortcut', } as const From 2fbce9a3de7581d2de91ff59c05ebf4a327f7333 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 19:51:12 +0800 Subject: [PATCH 02/18] =?UTF-8?q?feat(main):=20=E6=B7=BB=E5=8A=A0=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建 GlobalShortcutService 管理全局快捷键 - 实现 registerShortcuts() 注册快捷键 - 实现 unregisterShortcuts() 注销快捷键 - 实现 validateShortcut() 验证快捷键可用性 - 实现窗口显示和聚焦逻辑 - 处理 Chat 和 Agent 快捷键触发事件 Co-Authored-By: Claude Sonnet 4.5 --- .../src/main/lib/global-shortcut-service.ts | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 apps/electron/src/main/lib/global-shortcut-service.ts diff --git a/apps/electron/src/main/lib/global-shortcut-service.ts b/apps/electron/src/main/lib/global-shortcut-service.ts new file mode 100644 index 0000000..e252708 --- /dev/null +++ b/apps/electron/src/main/lib/global-shortcut-service.ts @@ -0,0 +1,129 @@ +/** + * 全局快捷键服务 + * + * 管理全局快捷键的注册、注销和触发处理。 + */ + +import { globalShortcut, BrowserWindow } from 'electron' +import { getSettings } from './settings-service' +import type { ShortcutConfig } from '../../types' + +let mainWindow: BrowserWindow | null = null + +/** + * 设置主窗口引用 + */ +export function setMainWindow(window: BrowserWindow): void { + mainWindow = window +} + +/** + * 显示并聚焦窗口 + */ +function showAndFocusWindow(): void { + if (!mainWindow) return + + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + + mainWindow.show() + mainWindow.focus() +} + +/** + * 处理 Chat 快捷键触发 + */ +function handleChatShortcut(config: ShortcutConfig): void { + console.log('[快捷键] Chat 快捷键触发:', config) + + showAndFocusWindow() + + // 发送 IPC 事件到渲染进程 + if (mainWindow) { + mainWindow.webContents.send('shortcut:chat', config.behavior) + } +} + +/** + * 处理 Agent 快捷键触发 + */ +function handleAgentShortcut(config: ShortcutConfig): void { + console.log('[快捷键] Agent 快捷键触发:', config) + + showAndFocusWindow() + + // 发送 IPC 事件到渲染进程 + if (mainWindow) { + mainWindow.webContents.send('shortcut:agent', config.behavior) + } +} + +/** + * 注册全局快捷键 + */ +export function registerShortcuts(): boolean { + try { + const settings = getSettings() + + // 注册 Chat 快捷键 + if (settings.chatShortcut?.enabled && settings.chatShortcut.accelerator) { + const registered = globalShortcut.register( + settings.chatShortcut.accelerator, + () => handleChatShortcut(settings.chatShortcut!) + ) + + if (!registered) { + console.error('[快捷键] Chat 快捷键注册失败:', settings.chatShortcut.accelerator) + return false + } + + console.log('[快捷键] Chat 快捷键已注册:', settings.chatShortcut.accelerator) + } + + // 注册 Agent 快捷键 + if (settings.agentShortcut?.enabled && settings.agentShortcut.accelerator) { + const registered = globalShortcut.register( + settings.agentShortcut.accelerator, + () => handleAgentShortcut(settings.agentShortcut!) + ) + + if (!registered) { + console.error('[快捷键] Agent 快捷键注册失败:', settings.agentShortcut.accelerator) + return false + } + + console.log('[快捷键] Agent 快捷键已注册:', settings.agentShortcut.accelerator) + } + + return true + } catch (error) { + console.error('[快捷键] 注册失败:', error) + return false + } +} + +/** + * 注销所有全局快捷键 + */ +export function unregisterShortcuts(): void { + globalShortcut.unregisterAll() + console.log('[快捷键] 所有快捷键已注销') +} + +/** + * 验证快捷键是否可用 + */ +export function validateShortcut(accelerator: string): boolean { + try { + // 尝试注册并立即注销 + const registered = globalShortcut.register(accelerator, () => {}) + if (registered) { + globalShortcut.unregister(accelerator) + return true + } + return false + } catch { + return false + } +} From e7361028564bf8808e173d0a27afd3eefc999022 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 19:53:33 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat(settings):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E9=85=8D=E7=BD=AE=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 导入 DEFAULT_CHAT_SHORTCUT 和 DEFAULT_AGENT_SHORTCUT - 在 getSettings() 中添加快捷键默认值处理 - 文件不存在时返回默认快捷键配置 - 从文件读取时使用默认值填充缺失的快捷键配置 - 读取失败时返回默认快捷键配置 - updateSettings() 已支持快捷键字段(通过展开运算符) Co-Authored-By: Claude Sonnet 4.5 --- apps/electron/src/main/lib/settings-service.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/electron/src/main/lib/settings-service.ts b/apps/electron/src/main/lib/settings-service.ts index cd96d33..9fc5248 100644 --- a/apps/electron/src/main/lib/settings-service.ts +++ b/apps/electron/src/main/lib/settings-service.ts @@ -7,7 +7,7 @@ import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { getSettingsPath } from './config-paths' -import { DEFAULT_THEME_MODE } from '../../types' +import { DEFAULT_THEME_MODE, DEFAULT_CHAT_SHORTCUT, DEFAULT_AGENT_SHORTCUT } from '../../types' import type { AppSettings } from '../../types' /** @@ -24,6 +24,8 @@ export function getSettings(): AppSettings { onboardingCompleted: false, environmentCheckSkipped: false, notificationsEnabled: true, + chatShortcut: DEFAULT_CHAT_SHORTCUT, + agentShortcut: DEFAULT_AGENT_SHORTCUT, } } @@ -39,6 +41,8 @@ export function getSettings(): AppSettings { environmentCheckSkipped: data.environmentCheckSkipped ?? false, lastEnvironmentCheck: data.lastEnvironmentCheck, notificationsEnabled: data.notificationsEnabled ?? true, + chatShortcut: data.chatShortcut || DEFAULT_CHAT_SHORTCUT, + agentShortcut: data.agentShortcut || DEFAULT_AGENT_SHORTCUT, } } catch (error) { console.error('[设置] 读取失败:', error) @@ -47,6 +51,8 @@ export function getSettings(): AppSettings { onboardingCompleted: false, environmentCheckSkipped: false, notificationsEnabled: true, + chatShortcut: DEFAULT_CHAT_SHORTCUT, + agentShortcut: DEFAULT_AGENT_SHORTCUT, } } } From e0c933bb7955019e6d245db98e9e1a0a0a1d76cf Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 19:56:29 +0800 Subject: [PATCH 04/18] =?UTF-8?q?feat(ipc):=20=E6=B7=BB=E5=8A=A0=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=20IPC=20=E5=A4=84=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 注册全局快捷键 - 注销全局快捷键 - 验证快捷键可用性 --- apps/electron/src/main/ipc.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/electron/src/main/ipc.ts b/apps/electron/src/main/ipc.ts index 1073e2f..226edec 100644 --- a/apps/electron/src/main/ipc.ts +++ b/apps/electron/src/main/ipc.ts @@ -87,6 +87,7 @@ import { import { extractTextFromAttachment } from './lib/document-parser' import { getUserProfile, updateUserProfile } from './lib/user-profile-service' import { getSettings, updateSettings } from './lib/settings-service' +import { registerShortcuts, unregisterShortcuts, validateShortcut } from './lib/global-shortcut-service' import { checkEnvironment } from './lib/environment-checker' import { getProxySettings, saveProxySettings } from './lib/proxy-settings-service' import { detectSystemProxy } from './lib/system-proxy-detector' @@ -471,6 +472,31 @@ export function registerIpcHandlers(): void { }) }) + // 注册全局快捷键 + ipcMain.handle( + SETTINGS_IPC_CHANNELS.REGISTER_SHORTCUTS, + async (): Promise => { + return registerShortcuts() + } + ) + + // 注销全局快捷键 + ipcMain.handle( + SETTINGS_IPC_CHANNELS.UNREGISTER_SHORTCUTS, + async (): Promise => { + unregisterShortcuts() + return true + } + ) + + // 验证快捷键是否可用 + ipcMain.handle( + SETTINGS_IPC_CHANNELS.VALIDATE_SHORTCUT, + async (_, accelerator: string): Promise => { + return validateShortcut(accelerator) + } + ) + // ===== 环境检测相关 ===== // 执行环境检测 From aa182108b2c2bba34468841f8867e2cea72e9e6f Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 19:59:05 +0800 Subject: [PATCH 05/18] =?UTF-8?q?feat(renderer):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=92=8C=20Preload=20=E6=A1=A5=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 5 & 6: - 创建快捷键 Atoms (shortcut-atoms.ts) - 扩展 Preload 桥接暴露快捷键 IPC 方法 - 支持加载、更新、验证快捷键配置 --- apps/electron/src/preload/index.ts | 21 +++++++ .../src/renderer/atoms/shortcut-atoms.ts | 63 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 apps/electron/src/renderer/atoms/shortcut-atoms.ts diff --git a/apps/electron/src/preload/index.ts b/apps/electron/src/preload/index.ts index 20c563f..e2ec70d 100644 --- a/apps/electron/src/preload/index.ts +++ b/apps/electron/src/preload/index.ts @@ -204,6 +204,15 @@ export interface ElectronAPI { /** 订阅系统主题变化事件(返回清理函数) */ onSystemThemeChanged: (callback: (isDark: boolean) => void) => () => void + /** 注册全局快捷键 */ + registerShortcuts: () => Promise + + /** 注销全局快捷键 */ + unregisterShortcuts: () => Promise + + /** 验证快捷键是否可用 */ + validateShortcut: (accelerator: string) => Promise + // ===== 环境检测相关 ===== /** 执行环境检测 */ @@ -590,6 +599,18 @@ const electronAPI: ElectronAPI = { return () => { ipcRenderer.removeListener(SETTINGS_IPC_CHANNELS.ON_SYSTEM_THEME_CHANGED, listener) } }, + registerShortcuts: () => { + return ipcRenderer.invoke(SETTINGS_IPC_CHANNELS.REGISTER_SHORTCUTS) + }, + + unregisterShortcuts: () => { + return ipcRenderer.invoke(SETTINGS_IPC_CHANNELS.UNREGISTER_SHORTCUTS) + }, + + validateShortcut: (accelerator: string) => { + return ipcRenderer.invoke(SETTINGS_IPC_CHANNELS.VALIDATE_SHORTCUT, accelerator) + }, + // 环境检测 checkEnvironment: () => { return ipcRenderer.invoke(ENVIRONMENT_IPC_CHANNELS.CHECK) diff --git a/apps/electron/src/renderer/atoms/shortcut-atoms.ts b/apps/electron/src/renderer/atoms/shortcut-atoms.ts new file mode 100644 index 0000000..234a89f --- /dev/null +++ b/apps/electron/src/renderer/atoms/shortcut-atoms.ts @@ -0,0 +1,63 @@ +/** + * 快捷键状态管理 + */ + +import { atom } from 'jotai' +import type { ShortcutConfig } from '../../types' + +/** + * Chat 快捷键配置 + */ +export const chatShortcutAtom = atom(null) + +/** + * Agent 快捷键配置 + */ +export const agentShortcutAtom = atom(null) + +/** + * 加载快捷键配置 + */ +export const loadShortcutsAtom = atom(null, async (get, set) => { + const settings = await window.electronAPI.getSettings() + set(chatShortcutAtom, settings.chatShortcut || null) + set(agentShortcutAtom, settings.agentShortcut || null) +}) + +/** + * 更新 Chat 快捷键 + */ +export const updateChatShortcutAtom = atom( + null, + async (get, set, config: ShortcutConfig) => { + await window.electronAPI.updateSettings({ chatShortcut: config }) + set(chatShortcutAtom, config) + + // 重新注册快捷键 + await window.electronAPI.registerShortcuts() + } +) + +/** + * 更新 Agent 快捷键 + */ +export const updateAgentShortcutAtom = atom( + null, + async (get, set, config: ShortcutConfig) => { + await window.electronAPI.updateSettings({ agentShortcut: config }) + set(agentShortcutAtom, config) + + // 重新注册快捷键 + await window.electronAPI.registerShortcuts() + } +) + +/** + * 验证快捷键是否可用 + */ +export const validateShortcutAtom = atom( + null, + async (get, set, accelerator: string): Promise => { + return window.electronAPI.validateShortcut(accelerator) + } +) From 318f30c9b766a75467637a81268a270b8acd52bf Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:03:37 +0800 Subject: [PATCH 06/18] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E5=BD=95=E5=88=B6=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现键盘事件监听 - 支持快捷键格式化(Electron accelerator 格式) - 添加冲突检测提示 - 支持清除快捷键 --- .../settings/primitives/ShortcutRecorder.tsx | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx diff --git a/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx new file mode 100644 index 0000000..fd94a9b --- /dev/null +++ b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx @@ -0,0 +1,93 @@ +/** + * ShortcutRecorder - 快捷键录制组件 + * + * 监听键盘输入并格式化为 Electron accelerator 格式。 + */ + +import * as React from 'react' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { X } from 'lucide-react' + +interface ShortcutRecorderProps { + value: string + onChange: (accelerator: string) => void + onValidate?: (accelerator: string) => Promise + placeholder?: string +} + +export function ShortcutRecorder({ + value, + onChange, + onValidate, + placeholder = '按下快捷键组合...', +}: ShortcutRecorderProps): React.ReactElement { + const [isRecording, setIsRecording] = React.useState(false) + const [isValid, setIsValid] = React.useState(true) + const inputRef = React.useRef(null) + + const handleKeyDown = React.useCallback( + async (e: React.KeyboardEvent) => { + if (!isRecording) return + + e.preventDefault() + e.stopPropagation() + + // 构建 accelerator 字符串 + const modifiers: string[] = [] + if (e.metaKey || e.ctrlKey) modifiers.push('CommandOrControl') + if (e.shiftKey) modifiers.push('Shift') + if (e.altKey) modifiers.push('Alt') + + // 获取主键 + let key = e.key + if (key === ' ') key = 'Space' + if (key.length === 1) key = key.toUpperCase() + + // 忽略单独的修饰键 + if (['Control', 'Meta', 'Shift', 'Alt'].includes(key)) return + + const accelerator = [...modifiers, key].join('+') + + // 验证快捷键 + if (onValidate) { + const valid = await onValidate(accelerator) + setIsValid(valid) + if (!valid) return + } + + onChange(accelerator) + setIsRecording(false) + inputRef.current?.blur() + }, + [isRecording, onChange, onValidate] + ) + + const handleClear = () => { + onChange('') + setIsValid(true) + } + + return ( +
+ setIsRecording(true)} + onBlur={() => setIsRecording(false)} + onKeyDown={handleKeyDown} + placeholder={placeholder} + readOnly + className={!isValid ? 'border-red-500' : ''} + /> + {value && ( + + )} + {!isValid && ( + 快捷键已被占用 + )} +
+ ) +} From a0ec197e09f8f51c539af6bd29c52120d7b6dcab Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:08:47 +0800 Subject: [PATCH 07/18] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 SettingsSection/SettingsCard/SettingsRow 组件 - 集成快捷键录制组件 - 支持启用/禁用开关 - 支持行为选项配置(新对话/当前对话) - 添加 macOS 权限提示 --- .../components/settings/ShortcutSettings.tsx | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 apps/electron/src/renderer/components/settings/ShortcutSettings.tsx diff --git a/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx b/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx new file mode 100644 index 0000000..3a2c6d5 --- /dev/null +++ b/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx @@ -0,0 +1,137 @@ +/** + * ShortcutSettings - 快捷键设置页面 + */ + +import * as React from 'react' +import { useAtom, useSetAtom } from 'jotai' +import { + SettingsSection, + SettingsCard, + SettingsRow, + SettingsToggle, + SettingsSelect, +} from './primitives' +import { ShortcutRecorder } from './primitives/ShortcutRecorder' +import { + chatShortcutAtom, + agentShortcutAtom, + updateChatShortcutAtom, + updateAgentShortcutAtom, +} from '@/atoms/shortcut-atoms' +import type { ShortcutBehavior } from '../../../types' + +export function ShortcutSettings(): React.ReactElement { + const [chatShortcut] = useAtom(chatShortcutAtom) + const [agentShortcut] = useAtom(agentShortcutAtom) + const updateChatShortcut = useSetAtom(updateChatShortcutAtom) + const updateAgentShortcut = useSetAtom(updateAgentShortcutAtom) + + const handleChatAcceleratorChange = (accelerator: string) => { + if (!chatShortcut) return + updateChatShortcut({ ...chatShortcut, accelerator }) + } + + const handleChatBehaviorChange = (value: string) => { + if (!chatShortcut) return + updateChatShortcut({ ...chatShortcut, behavior: value as ShortcutBehavior }) + } + + const handleChatEnabledChange = (enabled: boolean) => { + if (!chatShortcut) return + updateChatShortcut({ ...chatShortcut, enabled }) + } + + const handleAgentAcceleratorChange = (accelerator: string) => { + if (!agentShortcut) return + updateAgentShortcut({ ...agentShortcut, accelerator }) + } + + const handleAgentBehaviorChange = (value: string) => { + if (!agentShortcut) return + updateAgentShortcut({ ...agentShortcut, behavior: value as ShortcutBehavior }) + } + + const handleAgentEnabledChange = (enabled: boolean) => { + if (!agentShortcut) return + updateAgentShortcut({ ...agentShortcut, enabled }) + } + + const validateShortcut = async (accelerator: string): Promise => { + return window.electronAPI.validateShortcut(accelerator) + } + + const behaviorOptions = [ + { value: 'new-conversation', label: '创建新对话' }, + { value: 'current-conversation', label: '打开当前对话' }, + ] + + return ( + + {/* Chat 快捷键 */} + +
+

Chat 模式快捷键

+

快速打开 Chat 对话

+
+ + + + + + + + +
+ + {/* Agent 快捷键 */} + +
+

Agent 模式快捷键

+

快速打开 Agent 会话

+
+ + + + + + + + +
+ +
+

提示:macOS 需要授予辅助功能权限才能使用全局快捷键。

+

如果快捷键不生效,请在系统偏好设置中检查权限。

+
+
+ ) +} From fa08a188b25ff2a337290799d0d5e990ddf04aab Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:11:23 +0800 Subject: [PATCH 08/18] =?UTF-8?q?feat(settings):=20=E9=9B=86=E6=88=90?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E8=AE=BE=E7=BD=AE=E5=88=B0=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 settings-tab.ts 中添加 'shortcuts' 类型 - 在 SettingsPanel.tsx 中添加快捷键标签页 - 在 index.ts 中导出 ShortcutSettings - 使用 Keyboard 图标 --- apps/electron/src/renderer/atoms/settings-tab.ts | 2 +- .../src/renderer/components/settings/SettingsPanel.tsx | 6 +++++- apps/electron/src/renderer/components/settings/index.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/electron/src/renderer/atoms/settings-tab.ts b/apps/electron/src/renderer/atoms/settings-tab.ts index ebcf90e..1bdc583 100644 --- a/apps/electron/src/renderer/atoms/settings-tab.ts +++ b/apps/electron/src/renderer/atoms/settings-tab.ts @@ -11,7 +11,7 @@ import { atom } from 'jotai' -export type SettingsTab = 'general' | 'channels' | 'proxy' | 'appearance' | 'about' | 'agent' | 'prompts' | 'memory' +export type SettingsTab = 'general' | 'channels' | 'proxy' | 'appearance' | 'about' | 'agent' | 'prompts' | 'shortcuts' | 'memory' /** 当前设置标签页(不持久化,每次打开设置默认显示渠道) */ export const settingsTabAtom = atom('channels') diff --git a/apps/electron/src/renderer/components/settings/SettingsPanel.tsx b/apps/electron/src/renderer/components/settings/SettingsPanel.tsx index 2a551ce..a8bbc88 100644 --- a/apps/electron/src/renderer/components/settings/SettingsPanel.tsx +++ b/apps/electron/src/renderer/components/settings/SettingsPanel.tsx @@ -9,7 +9,7 @@ import * as React from 'react' import { useAtom, useAtomValue } from 'jotai' import { cn } from '@/lib/utils' -import { Settings, Radio, Palette, Info, Plug, Globe, BookOpen, Brain } from 'lucide-react' +import { Settings, Radio, Palette, Info, Plug, Globe, BookOpen, Brain, Keyboard } from 'lucide-react' import { ScrollArea } from '@/components/ui/scroll-area' import { settingsTabAtom } from '@/atoms/settings-tab' import type { SettingsTab } from '@/atoms/settings-tab' @@ -23,6 +23,7 @@ import { AppearanceSettings } from './AppearanceSettings' import { AboutSettings } from './AboutSettings' import { AgentSettings } from './AgentSettings' import { PromptSettings } from './PromptSettings' +import { ShortcutSettings } from './ShortcutSettings' import { MemorySettings } from './MemorySettings' /** 设置 Tab 定义 */ @@ -37,6 +38,7 @@ const BASE_TABS: TabItem[] = [ { id: 'general', label: '通用', icon: }, { id: 'channels', label: '渠道', icon: }, { id: 'prompts', label: '提示词', icon: }, + { id: 'shortcuts', label: '快捷键', icon: }, { id: 'proxy', label: '代理', icon: }, ] @@ -59,6 +61,8 @@ function renderTabContent(tab: SettingsTab): React.ReactElement { return case 'prompts': return + case 'shortcuts': + return case 'proxy': return case 'agent': diff --git a/apps/electron/src/renderer/components/settings/index.ts b/apps/electron/src/renderer/components/settings/index.ts index f18eb29..b322e47 100644 --- a/apps/electron/src/renderer/components/settings/index.ts +++ b/apps/electron/src/renderer/components/settings/index.ts @@ -7,5 +7,6 @@ export * from './ChannelSettings' export * from './ChannelForm' export * from './GeneralSettings' export * from './AppearanceSettings' +export * from './ShortcutSettings' export * from './AboutSettings' export * from './primitives' From bc912b55defab67fbab246dfa2cc41b5d99fd208 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:15:27 +0800 Subject: [PATCH 09/18] =?UTF-8?q?feat(main):=20=E5=BA=94=E7=94=A8=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=97=B6=E6=B3=A8=E5=86=8C=E5=85=A8=E5=B1=80=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 app.whenReady() 中设置主窗口引用并注册快捷键 - 在 app.on('before-quit') 中注销快捷键 - 确保应用启动时快捷键立即生效 --- apps/electron/src/main/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/electron/src/main/index.ts b/apps/electron/src/main/index.ts index 03d2c60..754969f 100644 --- a/apps/electron/src/main/index.ts +++ b/apps/electron/src/main/index.ts @@ -11,6 +11,7 @@ import { stopAllGenerations } from './lib/chat-service' import { initAutoUpdater, cleanupUpdater } from './lib/updater/auto-updater' import { startWorkspaceWatcher, stopWorkspaceWatcher } from './lib/workspace-watcher' import { getIsQuitting, setQuitting, isUpdating } from './lib/app-lifecycle' +import { registerShortcuts, unregisterShortcuts, setMainWindow } from './lib/global-shortcut-service' let mainWindow: BrowserWindow | null = null @@ -177,6 +178,12 @@ app.whenReady().then(async () => { // Create main window (will be shown when ready) createWindow() + // 设置主窗口引用并注册全局快捷键 + if (mainWindow) { + setMainWindow(mainWindow) + registerShortcuts() + } + // 启动工作区文件监听(Agent MCP/Skills + 文件浏览器自动刷新) if (mainWindow) { startWorkspaceWatcher(mainWindow) @@ -216,6 +223,8 @@ app.on('before-quit', () => { return } + // 注销全局快捷键 + unregisterShortcuts() // 中止所有活跃的 Agent 和 Chat 子进程 stopAllAgents() stopAllGenerations() From f5fd8f3b4c371843f20105c51b113b8201554b84 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:19:41 +0800 Subject: [PATCH 10/18] =?UTF-8?q?feat(preload):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E4=BA=8B=E4=BB=B6=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 onChatShortcut 和 onAgentShortcut 事件监听器 - 监听主进程发送的 shortcut:chat 和 shortcut:agent 事件 - 返回清理函数用于取消订阅 --- apps/electron/src/preload/index.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/electron/src/preload/index.ts b/apps/electron/src/preload/index.ts index e2ec70d..b6320b2 100644 --- a/apps/electron/src/preload/index.ts +++ b/apps/electron/src/preload/index.ts @@ -63,7 +63,7 @@ import type { SystemPromptUpdateInput, MemoryConfig, } from '@proma/shared' -import type { UserProfile, AppSettings } from '../types' +import type { UserProfile, AppSettings, ShortcutBehavior } from '../types' /** * 暴露给渲染进程的 API 接口定义 @@ -213,6 +213,12 @@ export interface ElectronAPI { /** 验证快捷键是否可用 */ validateShortcut: (accelerator: string) => Promise + /** 订阅 Chat 快捷键触发事件(返回清理函数) */ + onChatShortcut: (callback: (behavior: ShortcutBehavior) => void) => () => void + + /** 订阅 Agent 快捷键触发事件(返回清理函数) */ + onAgentShortcut: (callback: (behavior: ShortcutBehavior) => void) => () => void + // ===== 环境检测相关 ===== /** 执行环境检测 */ @@ -599,6 +605,18 @@ const electronAPI: ElectronAPI = { return () => { ipcRenderer.removeListener(SETTINGS_IPC_CHANNELS.ON_SYSTEM_THEME_CHANGED, listener) } }, + onChatShortcut: (callback: (behavior: ShortcutBehavior) => void) => { + const listener = (_: unknown, behavior: ShortcutBehavior): void => callback(behavior) + ipcRenderer.on('shortcut:chat', listener) + return () => { ipcRenderer.removeListener('shortcut:chat', listener) } + }, + + onAgentShortcut: (callback: (behavior: ShortcutBehavior) => void) => { + const listener = (_: unknown, behavior: ShortcutBehavior): void => callback(behavior) + ipcRenderer.on('shortcut:agent', listener) + return () => { ipcRenderer.removeListener('shortcut:agent', listener) } + }, + registerShortcuts: () => { return ipcRenderer.invoke(SETTINGS_IPC_CHANNELS.REGISTER_SHORTCUTS) }, From 708ebfe340d242751a4f85fe48303b770598c24d Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 20:21:43 +0800 Subject: [PATCH 11/18] =?UTF-8?q?feat(renderer):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=88=9D=E5=A7=8B=E5=8C=96=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task 11 & 12: 完成快捷键功能的渲染进程集成 - 创建 ShortcutInitializer 组件 - Task 12: 应用启动时加载快捷键配置 - Task 11: 监听 Chat/Agent 快捷键触发事件 - 根据 behavior 执行相应操作: - new-conversation: 创建新对话/会话 - current-conversation: 打开当前对话/会话(无则创建) - 自动切换到对应模式和视图 --- apps/electron/src/renderer/main.tsx | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index 54d520a..38238bf 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -28,6 +28,17 @@ import { notificationsEnabledAtom, initializeNotifications, } from './atoms/notifications' +import { loadShortcutsAtom } from './atoms/shortcut-atoms' +import { appModeAtom } from './atoms/app-mode' +import { activeViewAtom } from './atoms/active-view' +import { + conversationsAtom, + currentConversationIdAtom, +} from './atoms/chat-atoms' +import { + agentSessionsAtom, + currentAgentSessionIdAtom, +} from './atoms/agent-atoms' import { useGlobalAgentListeners } from './hooks/useGlobalAgentListeners' import { Toaster } from './components/ui/sonner' import { UpdateDialog } from './components/settings/UpdateDialog' @@ -158,6 +169,77 @@ function NotificationsInitializer(): null { return null } +/** + * 快捷键初始化组件 + * + * 负责加载快捷键配置并监听快捷键触发事件。 + */ +function ShortcutInitializer(): null { + const setAppMode = useSetAtom(appModeAtom) + const setActiveView = useSetAtom(activeViewAtom) + const setCurrentConversationId = useSetAtom(currentConversationIdAtom) + const setCurrentAgentSessionId = useSetAtom(currentAgentSessionIdAtom) + const conversations = useAtomValue(conversationsAtom) + const agentSessions = useAtomValue(agentSessionsAtom) + const loadShortcuts = useSetAtom(loadShortcutsAtom) + + // Task 12: 加载快捷键配置 + useEffect(() => { + loadShortcuts() + }, [loadShortcuts]) + + // Task 11: 监听快捷键触发事件 + useEffect(() => { + const handleChatShortcut = async (behavior: 'new-conversation' | 'current-conversation'): Promise => { + // 切换到 Chat 模式 + setAppMode('chat') + setActiveView('conversations') + + if (behavior === 'new-conversation') { + // 创建新对话 + const newConv = await window.electronAPI.createConversation() + setCurrentConversationId(newConv.id) + } else { + // 打开当前对话,如果没有对话则创建新的 + if (conversations.length === 0) { + const newConv = await window.electronAPI.createConversation() + setCurrentConversationId(newConv.id) + } + // 如果有对话,保持当前对话不变(已经在 atom 中) + } + } + + const handleAgentShortcut = async (behavior: 'new-conversation' | 'current-conversation'): Promise => { + // 切换到 Agent 模式 + setAppMode('agent') + setActiveView('conversations') + + if (behavior === 'new-conversation') { + // 创建新会话 + const newSession = await window.electronAPI.createAgentSession() + setCurrentAgentSessionId(newSession.id) + } else { + // 打开当前会话,如果没有会话则创建新的 + if (agentSessions.length === 0) { + const newSession = await window.electronAPI.createAgentSession() + setCurrentAgentSessionId(newSession.id) + } + // 如果有会话,保持当前会话不变(已经在 atom 中) + } + } + + const cleanupChat = window.electronAPI.onChatShortcut(handleChatShortcut) + const cleanupAgent = window.electronAPI.onAgentShortcut(handleAgentShortcut) + + return () => { + cleanupChat() + cleanupAgent() + } + }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, conversations, agentSessions]) + + return null +} + /** * Agent IPC 监听器初始化组件 * @@ -174,6 +256,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render( + From d0a549f4c85e6b9b7c4f133a2903553efd02e4c9 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 07:58:35 +0800 Subject: [PATCH 12/18] =?UTF-8?q?fix(shortcuts):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E8=A1=8C=E4=B8=BA=E5=92=8C=E6=B3=A8?= =?UTF-8?q?=E9=94=80=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题3和4修复: - 触发时重新读取最新设置,确保使用最新的 behavior 配置 - 注册前先注销所有旧快捷键,确保配置更新生效 - 禁用的快捷键不再触发 --- .../src/main/lib/global-shortcut-service.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/electron/src/main/lib/global-shortcut-service.ts b/apps/electron/src/main/lib/global-shortcut-service.ts index e252708..59978a2 100644 --- a/apps/electron/src/main/lib/global-shortcut-service.ts +++ b/apps/electron/src/main/lib/global-shortcut-service.ts @@ -33,8 +33,18 @@ function showAndFocusWindow(): void { /** * 处理 Chat 快捷键触发 + * 每次触发时重新读取最新设置,确保使用最新的 behavior 配置 */ -function handleChatShortcut(config: ShortcutConfig): void { +function handleChatShortcut(): void { + // 重新读取最新设置 + const settings = getSettings() + const config = settings.chatShortcut + + if (!config || !config.enabled) { + console.log('[快捷键] Chat 快捷键已禁用,忽略触发') + return + } + console.log('[快捷键] Chat 快捷键触发:', config) showAndFocusWindow() @@ -47,8 +57,18 @@ function handleChatShortcut(config: ShortcutConfig): void { /** * 处理 Agent 快捷键触发 + * 每次触发时重新读取最新设置,确保使用最新的 behavior 配置 */ -function handleAgentShortcut(config: ShortcutConfig): void { +function handleAgentShortcut(): void { + // 重新读取最新设置 + const settings = getSettings() + const config = settings.agentShortcut + + if (!config || !config.enabled) { + console.log('[快捷键] Agent 快捷键已禁用,忽略触发') + return + } + console.log('[快捷键] Agent 快捷键触发:', config) showAndFocusWindow() @@ -61,16 +81,20 @@ function handleAgentShortcut(config: ShortcutConfig): void { /** * 注册全局快捷键 + * 注册前先注销所有旧的快捷键,确保配置更新生效 */ export function registerShortcuts(): boolean { try { + // 先注销所有旧的快捷键 + unregisterShortcuts() + const settings = getSettings() // 注册 Chat 快捷键 if (settings.chatShortcut?.enabled && settings.chatShortcut.accelerator) { const registered = globalShortcut.register( settings.chatShortcut.accelerator, - () => handleChatShortcut(settings.chatShortcut!) + handleChatShortcut ) if (!registered) { @@ -85,7 +109,7 @@ export function registerShortcuts(): boolean { if (settings.agentShortcut?.enabled && settings.agentShortcut.accelerator) { const registered = globalShortcut.register( settings.agentShortcut.accelerator, - () => handleAgentShortcut(settings.agentShortcut!) + handleAgentShortcut ) if (!registered) { From d800fc05d41c50fdf7e5a44bd5bc0bbc308762d5 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 07:58:47 +0800 Subject: [PATCH 13/18] =?UTF-8?q?fix(shortcuts):=20=E6=96=B0=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E5=88=9B=E5=BB=BA=E5=90=8E=E7=AB=8B=E5=8D=B3=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=9C=A8=E5=88=97=E8=A1=A8=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题2修复: - 创建新对话/会话后立即刷新列表 - 确保左侧对话框立即显示,无需等待发送消息 --- apps/electron/src/renderer/main.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index 38238bf..a2bd369 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -179,6 +179,8 @@ function ShortcutInitializer(): null { const setActiveView = useSetAtom(activeViewAtom) const setCurrentConversationId = useSetAtom(currentConversationIdAtom) const setCurrentAgentSessionId = useSetAtom(currentAgentSessionIdAtom) + const setConversations = useSetAtom(conversationsAtom) + const setAgentSessions = useSetAtom(agentSessionsAtom) const conversations = useAtomValue(conversationsAtom) const agentSessions = useAtomValue(agentSessionsAtom) const loadShortcuts = useSetAtom(loadShortcutsAtom) @@ -199,11 +201,19 @@ function ShortcutInitializer(): null { // 创建新对话 const newConv = await window.electronAPI.createConversation() setCurrentConversationId(newConv.id) + + // 刷新对话列表,确保新对话立即显示 + const updatedList = await window.electronAPI.listConversations() + setConversations(updatedList) } else { // 打开当前对话,如果没有对话则创建新的 if (conversations.length === 0) { const newConv = await window.electronAPI.createConversation() setCurrentConversationId(newConv.id) + + // 刷新对话列表 + const updatedList = await window.electronAPI.listConversations() + setConversations(updatedList) } // 如果有对话,保持当前对话不变(已经在 atom 中) } @@ -218,11 +228,19 @@ function ShortcutInitializer(): null { // 创建新会话 const newSession = await window.electronAPI.createAgentSession() setCurrentAgentSessionId(newSession.id) + + // 刷新会话列表,确保新会话立即显示 + const updatedList = await window.electronAPI.listAgentSessions() + setAgentSessions(updatedList) } else { // 打开当前会话,如果没有会话则创建新的 if (agentSessions.length === 0) { const newSession = await window.electronAPI.createAgentSession() setCurrentAgentSessionId(newSession.id) + + // 刷新会话列表 + const updatedList = await window.electronAPI.listAgentSessions() + setAgentSessions(updatedList) } // 如果有会话,保持当前会话不变(已经在 atom 中) } @@ -235,7 +253,7 @@ function ShortcutInitializer(): null { cleanupChat() cleanupAgent() } - }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, conversations, agentSessions]) + }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, setConversations, setAgentSessions, conversations, agentSessions]) return null } From 234b47751ddb8c4ace5b61dbb8dd5b021c54711b Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 07:58:56 +0800 Subject: [PATCH 14/18] =?UTF-8?q?fix(shortcuts):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E6=98=BE=E7=A4=BA=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题1修复: - 添加 formatAccelerator 函数格式化显示 - macOS: CommandOrControl → Cmd, Alt → Option - Windows/Linux: CommandOrControl → Ctrl - 显示更简洁友好,如 'Cmd+Shift+C' 而不是 'CommandOrControl+Shift+C' --- .../settings/primitives/ShortcutRecorder.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx index fd94a9b..4e50139 100644 --- a/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx +++ b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx @@ -16,6 +16,32 @@ interface ShortcutRecorderProps { placeholder?: string } +/** + * 格式化快捷键显示 + * 将 Electron accelerator 格式转换为更友好的显示格式 + */ +function formatAccelerator(accelerator: string): string { + if (!accelerator) return '' + + const isMac = navigator.platform.toLowerCase().includes('mac') + + return accelerator + .split('+') + .map((part) => { + switch (part) { + case 'CommandOrControl': + return isMac ? 'Cmd' : 'Ctrl' + case 'Alt': + return isMac ? 'Option' : 'Alt' + case 'Shift': + return 'Shift' + default: + return part + } + }) + .join('+') +} + export function ShortcutRecorder({ value, onChange, @@ -72,7 +98,7 @@ export function ShortcutRecorder({
setIsRecording(true)} onBlur={() => setIsRecording(false)} onKeyDown={handleKeyDown} From 0a8eb2d5eb3543dc1ee53ab43de9f0acadb6448e Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 08:08:43 +0800 Subject: [PATCH 15/18] =?UTF-8?q?fix(shortcuts):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=88=9B=E5=BB=BA=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=92=8C=E6=8C=89=E9=94=AE=E8=AF=86=E5=88=AB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Agent 会话现在正确关联工作区 - 快捷键创建的会话传递 currentWorkspaceId 参数 - 会话现在可以在对应工作区中找到 2. 快捷键录制器现在能区分 Command 和 Control 键 - 分别检测 metaKey 和 ctrlKey - 用户可以设置 Mac 专用的 Command 快捷键或跨平台的 Control 快捷键 - 更新 formatAccelerator 支持新格式 Co-Authored-By: Claude Sonnet 4.5 --- .../settings/primitives/ShortcutRecorder.tsx | 8 +++++++- apps/electron/src/renderer/main.tsx | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx index 4e50139..573c744 100644 --- a/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx +++ b/apps/electron/src/renderer/components/settings/primitives/ShortcutRecorder.tsx @@ -31,6 +31,10 @@ function formatAccelerator(accelerator: string): string { switch (part) { case 'CommandOrControl': return isMac ? 'Cmd' : 'Ctrl' + case 'Command': + return 'Cmd' + case 'Control': + return 'Ctrl' case 'Alt': return isMac ? 'Option' : 'Alt' case 'Shift': @@ -61,7 +65,9 @@ export function ShortcutRecorder({ // 构建 accelerator 字符串 const modifiers: string[] = [] - if (e.metaKey || e.ctrlKey) modifiers.push('CommandOrControl') + // 分别检测 Command 和 Control 键,允许用户区分它们 + if (e.metaKey) modifiers.push('Command') + if (e.ctrlKey) modifiers.push('Control') if (e.shiftKey) modifiers.push('Shift') if (e.altKey) modifiers.push('Alt') diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index a2bd369..e014296 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -183,6 +183,7 @@ function ShortcutInitializer(): null { const setAgentSessions = useSetAtom(agentSessionsAtom) const conversations = useAtomValue(conversationsAtom) const agentSessions = useAtomValue(agentSessionsAtom) + const currentWorkspaceId = useAtomValue(currentAgentWorkspaceIdAtom) const loadShortcuts = useSetAtom(loadShortcutsAtom) // Task 12: 加载快捷键配置 @@ -225,8 +226,12 @@ function ShortcutInitializer(): null { setActiveView('conversations') if (behavior === 'new-conversation') { - // 创建新会话 - const newSession = await window.electronAPI.createAgentSession() + // 创建新会话,传递当前工作区 ID + const newSession = await window.electronAPI.createAgentSession( + undefined, + undefined, + currentWorkspaceId || undefined + ) setCurrentAgentSessionId(newSession.id) // 刷新会话列表,确保新会话立即显示 @@ -235,7 +240,11 @@ function ShortcutInitializer(): null { } else { // 打开当前会话,如果没有会话则创建新的 if (agentSessions.length === 0) { - const newSession = await window.electronAPI.createAgentSession() + const newSession = await window.electronAPI.createAgentSession( + undefined, + undefined, + currentWorkspaceId || undefined + ) setCurrentAgentSessionId(newSession.id) // 刷新会话列表 @@ -253,7 +262,7 @@ function ShortcutInitializer(): null { cleanupChat() cleanupAgent() } - }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, setConversations, setAgentSessions, conversations, agentSessions]) + }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, setConversations, setAgentSessions, conversations, agentSessions, currentWorkspaceId]) return null } From 95133d04dc7e1f3fe1118b350d567174a9167971 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 08:36:28 +0800 Subject: [PATCH 16/18] =?UTF-8?q?fix(shortcuts):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=86=85=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复问题2和问题3: - 添加 validateChatShortcut 和 validateAgentShortcut 函数 - 在录制快捷键时检测与另一个快捷键的冲突 - 如果冲突,显示"快捷键已被占用"提示 - 防止设置相同的快捷键给 Chat 和 Agent Co-Authored-By: Claude Sonnet 4.5 --- .../components/settings/ShortcutSettings.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx b/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx index 3a2c6d5..4e9c13e 100644 --- a/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx +++ b/apps/electron/src/renderer/components/settings/ShortcutSettings.tsx @@ -56,7 +56,21 @@ export function ShortcutSettings(): React.ReactElement { updateAgentShortcut({ ...agentShortcut, enabled }) } - const validateShortcut = async (accelerator: string): Promise => { + const validateChatShortcut = async (accelerator: string): Promise => { + // 检查是否与 Agent 快捷键冲突 + if (agentShortcut?.enabled && agentShortcut.accelerator === accelerator) { + return false + } + // 检查系统占用 + return window.electronAPI.validateShortcut(accelerator) + } + + const validateAgentShortcut = async (accelerator: string): Promise => { + // 检查是否与 Chat 快捷键冲突 + if (chatShortcut?.enabled && chatShortcut.accelerator === accelerator) { + return false + } + // 检查系统占用 return window.electronAPI.validateShortcut(accelerator) } @@ -87,7 +101,7 @@ export function ShortcutSettings(): React.ReactElement { @@ -116,7 +130,7 @@ export function ShortcutSettings(): React.ReactElement { From 186f0131fbb289b92c83160201c4f07700619113 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 08:39:38 +0800 Subject: [PATCH 17/18] =?UTF-8?q?fix(shortcuts):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=89=93=E5=BC=80=E5=BD=93=E5=89=8D=E5=AF=B9=E8=AF=9D=E8=A1=8C?= =?UTF-8?q?=E4=B8=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:当没有选中的对话时(currentConversationId 为 null), 但历史对话列表不为空(conversations.length > 0), 快捷键不会创建新对话也不会打开任何对话。 修复: - 添加 currentConversationId 和 currentAgentSessionId 的读取 - 修改逻辑为三种情况: 1. 没有任何对话 → 创建新对话 2. 有对话但没有选中 → 打开第一个对话 3. 已经有选中的对话 → 保持不变 Co-Authored-By: Claude Sonnet 4.5 --- apps/electron/src/renderer/main.tsx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index e014296..9ef73b2 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -183,6 +183,8 @@ function ShortcutInitializer(): null { const setAgentSessions = useSetAtom(agentSessionsAtom) const conversations = useAtomValue(conversationsAtom) const agentSessions = useAtomValue(agentSessionsAtom) + const currentConversationId = useAtomValue(currentConversationIdAtom) + const currentAgentSessionId = useAtomValue(currentAgentSessionIdAtom) const currentWorkspaceId = useAtomValue(currentAgentWorkspaceIdAtom) const loadShortcuts = useSetAtom(loadShortcutsAtom) @@ -207,16 +209,20 @@ function ShortcutInitializer(): null { const updatedList = await window.electronAPI.listConversations() setConversations(updatedList) } else { - // 打开当前对话,如果没有对话则创建新的 + // 打开当前对话 if (conversations.length === 0) { + // 没有任何对话,创建新的 const newConv = await window.electronAPI.createConversation() setCurrentConversationId(newConv.id) // 刷新对话列表 const updatedList = await window.electronAPI.listConversations() setConversations(updatedList) + } else if (!currentConversationId) { + // 有对话但没有选中,打开第一个 + setCurrentConversationId(conversations[0]!.id) } - // 如果有对话,保持当前对话不变(已经在 atom 中) + // 如果已经有选中的对话,保持不变 } } @@ -238,8 +244,9 @@ function ShortcutInitializer(): null { const updatedList = await window.electronAPI.listAgentSessions() setAgentSessions(updatedList) } else { - // 打开当前会话,如果没有会话则创建新的 + // 打开当前会话 if (agentSessions.length === 0) { + // 没有任何会话,创建新的 const newSession = await window.electronAPI.createAgentSession( undefined, undefined, @@ -250,8 +257,11 @@ function ShortcutInitializer(): null { // 刷新会话列表 const updatedList = await window.electronAPI.listAgentSessions() setAgentSessions(updatedList) + } else if (!currentAgentSessionId) { + // 有会话但没有选中,打开第一个 + setCurrentAgentSessionId(agentSessions[0]!.id) } - // 如果有会话,保持当前会话不变(已经在 atom 中) + // 如果已经有选中的会话,保持不变 } } @@ -262,7 +272,7 @@ function ShortcutInitializer(): null { cleanupChat() cleanupAgent() } - }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, setConversations, setAgentSessions, conversations, agentSessions, currentWorkspaceId]) + }, [setAppMode, setActiveView, setCurrentConversationId, setCurrentAgentSessionId, setConversations, setAgentSessions, conversations, agentSessions, currentConversationId, currentAgentSessionId, currentWorkspaceId]) return null } From 7e369aac3be7a8af00fb574edfd3d781b9f03b88 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Fri, 27 Feb 2026 08:59:35 +0800 Subject: [PATCH 18/18] =?UTF-8?q?fix(shortcuts):=20=E4=BF=AE=E5=A4=8D=20Ag?= =?UTF-8?q?ent=20=E6=A8=A1=E5=BC=8F=E6=89=93=E5=BC=80=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E7=9A=84=E5=B7=A5=E4=BD=9C=E5=8C=BA=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:使用打开当前对话行为时,如果当前工作区没有会话, 但其他工作区有会话,代码会打开其他工作区的会话, 导致在当前工作区左侧看不到这个会话。 根本原因:代码直接使用全局 agentSessions[0], 没有考虑工作区过滤。 修复:先过滤出当前工作区的会话,然后再判断: - 当前工作区没有会话 → 创建新会话 - 当前工作区有会话但没有选中 → 打开第一个 - 已经有选中的会话 → 保持不变 Co-Authored-By: Claude Sonnet 4.5 --- apps/electron/src/renderer/main.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index 9ef73b2..ff06412 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -245,8 +245,13 @@ function ShortcutInitializer(): null { setAgentSessions(updatedList) } else { // 打开当前会话 - if (agentSessions.length === 0) { - // 没有任何会话,创建新的 + // 过滤出当前工作区的会话 + const workspaceSessions = agentSessions.filter( + (s) => s.workspaceId === currentWorkspaceId + ) + + if (workspaceSessions.length === 0) { + // 当前工作区没有会话,创建新的 const newSession = await window.electronAPI.createAgentSession( undefined, undefined, @@ -258,8 +263,8 @@ function ShortcutInitializer(): null { const updatedList = await window.electronAPI.listAgentSessions() setAgentSessions(updatedList) } else if (!currentAgentSessionId) { - // 有会话但没有选中,打开第一个 - setCurrentAgentSessionId(agentSessions[0]!.id) + // 当前工作区有会话但没有选中,打开第一个 + setCurrentAgentSessionId(workspaceSessions[0]!.id) } // 如果已经有选中的会话,保持不变 }