diff --git a/docs/specs/sidebar-workspace-shortcuts/spec.md b/docs/specs/sidebar-workspace-shortcuts/spec.md new file mode 100644 index 000000000..337723890 --- /dev/null +++ b/docs/specs/sidebar-workspace-shortcuts/spec.md @@ -0,0 +1,25 @@ +# Sidebar And Workspace Shortcuts + +## User Need + +Laptop users need faster access to more chat space without reaching for the mouse. DeepChat should provide keyboard shortcuts to toggle the left sidebar and the right workspace panel from the main chat window. + +## Defaults + +- `ToggleSidebar`: `CommandOrControl+B` +- `ToggleWorkspace`: `CommandOrControl+J` + +## Acceptance Criteria + +- Both shortcuts are registered through the existing shortcut settings flow. +- Both shortcuts appear in the settings shortcut page and can be customized. +- `ToggleSidebar` toggles the main chat sidebar collapsed state. +- `ToggleWorkspace` toggles the workspace side panel for the active chat session only. +- Shortcut events are delivered only to the currently focused DeepChat chat window. +- Existing sidebar and workspace buttons keep working without behavior changes. + +## Non-Goals + +- No persistence change for sidebar collapsed state. +- No new buttons, menu items, or layout redesign. +- No localization sweep for every existing locale in this increment. diff --git a/package.json b/package.json index d5b2746df..0e47385c5 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "katex": "^0.16.27", "lint-staged": "^16.4.0", "lucide-vue-next": "^0.544.0", - "markstream-vue": "0.0.10-beta.6", + "markstream-vue": "0.0.12-beta.1", "mermaid": "^11.13.0", "minimatch": "^10.2.4", "monaco-editor": "^0.52.2", @@ -167,7 +167,7 @@ "pinia": "^3.0.4", "reka-ui": "^2.9.2", "simple-git-hooks": "^2.13.1", - "stream-monaco": "^0.0.21", + "stream-monaco": "^0.0.22", "tailwind-merge": "^3.5.0", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "^4.2.2", diff --git a/src/main/events.ts b/src/main/events.ts index 5c2ec8b80..efe9875ab 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -187,6 +187,8 @@ export const SHORTCUT_EVENTS = { CREATE_NEW_WINDOW: 'shortcut:create-new-window', CREATE_NEW_CONVERSATION: 'shortcut:create-new-conversation', TOGGLE_SPOTLIGHT: 'shortcut:toggle-spotlight', + TOGGLE_SIDEBAR: 'shortcut:toggle-sidebar', + TOGGLE_WORKSPACE: 'shortcut:toggle-workspace', GO_SETTINGS: 'shortcut:go-settings', CLEAN_CHAT_HISTORY: 'shortcut:clean-chat-history', DELETE_CONVERSATION: 'shortcut:delete-conversation' diff --git a/src/main/presenter/configPresenter/shortcutKeySettings.ts b/src/main/presenter/configPresenter/shortcutKeySettings.ts index 3fb017c11..0c0c487dc 100644 --- a/src/main/presenter/configPresenter/shortcutKeySettings.ts +++ b/src/main/presenter/configPresenter/shortcutKeySettings.ts @@ -7,6 +7,8 @@ const ShiftKey = 'Shift' export const rendererShortcutKey = { NewConversation: `${CommandKey}+N`, QuickSearch: `${CommandKey}+P`, + ToggleSidebar: `${CommandKey}+B`, + ToggleWorkspace: `${CommandKey}+J`, NewWindow: `${CommandKey}+${ShiftKey}+N`, CloseWindow: `${CommandKey}+W`, ZoomIn: `${CommandKey}+=`, diff --git a/src/main/presenter/shortcutPresenter.ts b/src/main/presenter/shortcutPresenter.ts index d68f2db83..08d4875b2 100644 --- a/src/main/presenter/shortcutPresenter.ts +++ b/src/main/presenter/shortcutPresenter.ts @@ -30,6 +30,19 @@ export class ShortcutPresenter implements IShortcutPresenter { ...this.configPresenter.getShortcutKey() } + const getFocusedChatWindow = () => { + const focusedWindow = presenter.windowPresenter.getFocusedWindow() + if (!focusedWindow?.isFocused()) { + return + } + + const isChatWindow = presenter.windowPresenter + .getAllWindows() + .some((window) => window.id === focusedWindow.id) + + return isChatWindow ? focusedWindow : undefined + } + // Command+N 或 Ctrl+N 创建新会话 if (this.shortcutKeys.NewConversation) { globalShortcut.register(this.shortcutKeys.NewConversation, async () => { @@ -68,6 +81,30 @@ export class ShortcutPresenter implements IShortcutPresenter { }) } + if (this.shortcutKeys.ToggleSidebar) { + globalShortcut.register(this.shortcutKeys.ToggleSidebar, () => { + const focusedWindow = getFocusedChatWindow() + if (focusedWindow) { + void presenter.windowPresenter.sendToWebContents( + focusedWindow.webContents.id, + SHORTCUT_EVENTS.TOGGLE_SIDEBAR + ) + } + }) + } + + if (this.shortcutKeys.ToggleWorkspace) { + globalShortcut.register(this.shortcutKeys.ToggleWorkspace, () => { + const focusedWindow = getFocusedChatWindow() + if (focusedWindow) { + void presenter.windowPresenter.sendToWebContents( + focusedWindow.webContents.id, + SHORTCUT_EVENTS.TOGGLE_WORKSPACE + ) + } + }) + } + // Command+Shift+N 或 Ctrl+Shift+N 创建新窗口 if (this.shortcutKeys.NewWindow) { globalShortcut.register(this.shortcutKeys.NewWindow, () => { diff --git a/src/renderer/settings/components/ShortcutSettings.vue b/src/renderer/settings/components/ShortcutSettings.vue index 9a7ca40fc..57a2f8316 100644 --- a/src/renderer/settings/components/ShortcutSettings.vue +++ b/src/renderer/settings/components/ShortcutSettings.vue @@ -188,6 +188,14 @@ const shortcutMapping: Record< icon: 'lucide:search', label: 'settings.shortcuts.quickSearch' }, + ToggleSidebar: { + icon: 'lucide:panel-left-close', + label: 'settings.shortcuts.toggleSidebar' + }, + ToggleWorkspace: { + icon: 'lucide:panel-right-close', + label: 'settings.shortcuts.toggleWorkspace' + }, NewWindow: { icon: 'lucide:app-window', label: 'settings.shortcuts.newWindow' diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 62187761e..accc1ca09 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -27,6 +27,8 @@ import { useDeviceVersion } from '@/composables/useDeviceVersion' import WindowSideBar from './components/WindowSideBar.vue' import SpotlightOverlay from '@/components/spotlight/SpotlightOverlay.vue' import { useSpotlightStore } from '@/stores/ui/spotlight' +import { useSidepanelStore } from '@/stores/ui/sidepanel' +import { useSidebarStore } from '@/stores/ui/sidebar' import { useAppIpcRuntime } from '@/composables/useAppIpcRuntime' const DEV_WELCOME_OVERRIDE_KEY = '__deepchat_dev_force_welcome' @@ -38,6 +40,8 @@ const sessionStore = useSessionStore() const agentStore = useAgentStore() const draftStore = useDraftStore() const pageRouterStore = usePageRouterStore() +const sidepanelStore = useSidepanelStore() +const sidebarStore = useSidebarStore() const spotlightStore = useSpotlightStore() const { toast } = useToast() const uiSettingsStore = useUiSettingsStore() @@ -300,6 +304,16 @@ const { setup: setupAppIpcRuntime, cleanup: cleanupAppIpcRuntime } = useAppIpcRu handleZoomOut, handleZoomResume, handleCreateNewConversation, + handleToggleSidebar: () => { + sidebarStore.toggleSidebar() + }, + handleToggleWorkspace: () => { + if (pageRouterStore.currentRoute !== 'chat' || !pageRouterStore.chatSessionId) { + return + } + + sidepanelStore.toggleWorkspace(pageRouterStore.chatSessionId) + }, openSpotlight: () => { spotlightStore.openSpotlight() }, diff --git a/src/renderer/src/components/WindowSideBar.vue b/src/renderer/src/components/WindowSideBar.vue index 5e5b605d9..4423a92e7 100644 --- a/src/renderer/src/components/WindowSideBar.vue +++ b/src/renderer/src/components/WindowSideBar.vue @@ -1,6 +1,7 @@