Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/specs/sidebar-workspace-shortcuts/spec.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/main/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions src/main/presenter/configPresenter/shortcutKeySettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}+=`,
Expand Down
37 changes: 37 additions & 0 deletions src/main/presenter/shortcutPresenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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
)
}
})
}
Comment on lines +84 to +106
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restrict shortcut dispatch to chat window only.

At Line 73 and Line 85, dispatch currently targets any focused window. If the settings window is focused, TOGGLE_SIDEBAR / TOGGLE_WORKSPACE are sent to the wrong renderer context.

💡 Suggested fix
     if (this.shortcutKeys.ToggleSidebar) {
       globalShortcut.register(this.shortcutKeys.ToggleSidebar, () => {
         const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_SIDEBAR
-          )
-        }
+        const settingsWindowId = presenter.windowPresenter.getSettingsWindowId()
+        if (!focusedWindow?.isFocused() || focusedWindow.id === settingsWindowId) return
+        void presenter.windowPresenter.sendToWebContents(
+          focusedWindow.webContents.id,
+          SHORTCUT_EVENTS.TOGGLE_SIDEBAR
+        )
       })
     }

     if (this.shortcutKeys.ToggleWorkspace) {
       globalShortcut.register(this.shortcutKeys.ToggleWorkspace, () => {
         const focusedWindow = presenter.windowPresenter.getFocusedWindow()
-        if (focusedWindow?.isFocused()) {
-          void presenter.windowPresenter.sendToWebContents(
-            focusedWindow.webContents.id,
-            SHORTCUT_EVENTS.TOGGLE_WORKSPACE
-          )
-        }
+        const settingsWindowId = presenter.windowPresenter.getSettingsWindowId()
+        if (!focusedWindow?.isFocused() || focusedWindow.id === settingsWindowId) return
+        void presenter.windowPresenter.sendToWebContents(
+          focusedWindow.webContents.id,
+          SHORTCUT_EVENTS.TOGGLE_WORKSPACE
+        )
       })
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/presenter/shortcutPresenter.ts` around lines 71 - 93, The shortcut
handlers currently send SHORTCUT_EVENTS to any focused window; restrict them to
the chat renderer by verifying the focused window is the chat window before
calling sendToWebContents. In the ToggleSidebar and ToggleWorkspace handlers,
replace the loose focusedWindow?.isFocused() check with a check against the chat
window (e.g., compare focusedWindow to
presenter.windowPresenter.getChatWindow(), or check focusedWindow?.type ===
'chat' / focusedWindow?.webContents.id ===
presenter.windowPresenter.getChatWindow()?.webContents.id) and only call
presenter.windowPresenter.sendToWebContents(...) when that check passes.


// Command+Shift+N 或 Ctrl+Shift+N 创建新窗口
if (this.shortcutKeys.NewWindow) {
globalShortcut.register(this.shortcutKeys.NewWindow, () => {
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/settings/components/ShortcutSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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()
Expand Down Expand Up @@ -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()
},
Expand Down
8 changes: 6 additions & 2 deletions src/renderer/src/components/WindowSideBar.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<TooltipProvider :delay-duration="200">
<div
data-testid="window-sidebar"
class="flex flex-row h-full shrink-0 window-drag-region transition-all duration-200"
:class="collapsed ? 'w-12' : 'w-[288px]'"
>
Expand Down Expand Up @@ -90,8 +91,9 @@
<Tooltip>
<TooltipTrigger as-child>
<Button
data-testid="window-sidebar-toggle"
class="flex items-center justify-center w-9 h-9 rounded-xl bg-transparent border-none hover:bg-white/30 dark:hover:bg-white/10 shadow-none"
@click="collapsed = !collapsed"
@click="sidebarStore.toggleSidebar()"
>
<Icon
:icon="collapsed ? 'lucide:panel-left-open' : 'lucide:panel-left-close'"
Expand Down Expand Up @@ -348,6 +350,7 @@ import type {
import AgentAvatar from './icons/AgentAvatar.vue'
import WindowSideBarSessionItem from './WindowSideBarSessionItem.vue'
import { useI18n } from 'vue-i18n'
import { useSidebarStore } from '@/stores/ui/sidebar'

type PinFeedbackMode = 'pinning' | 'unpinning'

Expand All @@ -360,9 +363,10 @@ const { t } = useI18n()
const agentStore = useAgentStore()
const pageRouterStore = usePageRouterStore()
const sessionStore = useSessionStore()
const sidebarStore = useSidebarStore()
const spotlightStore = useSpotlightStore()

const collapsed = ref(false)
const collapsed = computed(() => sidebarStore.collapsed)
const sessionSearchQuery = ref('')
const remoteControlStatus = ref<{
telegram: TelegramRemoteStatus | null
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/src/composables/useAppIpcRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface UseAppIpcRuntimeOptions {
handleZoomOut: () => void
handleZoomResume: () => void
handleCreateNewConversation: () => void | Promise<void>
handleToggleSidebar: () => void
handleToggleWorkspace: () => void
openSpotlight: () => void
handleDataResetComplete: () => void
handleSystemNotificationClick: (payload: unknown) => void
Expand Down Expand Up @@ -35,6 +37,8 @@ export function useAppIpcRuntime(options: UseAppIpcRuntimeOptions) {

void options.handleCreateNewConversation()
})
scope.on(SHORTCUT_EVENTS.TOGGLE_SIDEBAR, options.handleToggleSidebar)
scope.on(SHORTCUT_EVENTS.TOGGLE_WORKSPACE, options.handleToggleWorkspace)
scope.on(SHORTCUT_EVENTS.TOGGLE_SPOTLIGHT, options.openSpotlight)
scope.on(NOTIFICATION_EVENTS.DATA_RESET_COMPLETE_DEV, options.handleDataResetComplete)
scope.on(NOTIFICATION_EVENTS.SYS_NOTIFY_CLICKED, (_event, payload) => {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ export const SHORTCUT_EVENTS = {
ZOOM_RESUME: 'shortcut:zoom-resume',
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'
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/da-DK/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,8 @@
"goSettings": "Åbn indstillinger",
"hideWindow": "Skjul vindue",
"quitApp": "Afslut app",
"toggleSidebar": "Vis/skjul sidepanel",
"toggleWorkspace": "Vis/skjul arbejdsområde",
"zoomIn": "Zoom ind",
"zoomOut": "Zoom ud",
"zoomReset": "Nulstil zoom",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,8 @@
"goSettings": "Open Settings",
"hideWindow": "Hide window",
"quitApp": "Quit app",
"toggleSidebar": "Toggle Sidebar",
"toggleWorkspace": "Toggle Workspace",
"zoomIn": "Zoom in ",
"zoomOut": "Zoom out ",
"zoomReset": "Reset zoom",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "باز کردن تنظیمات",
"hideWindow": "مخفی کردن پنجره",
"quitApp": "خروج از برنامه",
"toggleSidebar": "نمایش/پنهان کردن نوار کناری",
"toggleWorkspace": "نمایش/پنهان کردن فضای کاری",
"zoomIn": "بزرگ‌نمایی",
"zoomOut": "کوچک‌نمایی",
"zoomReset": "بازنشانی بزرگ‌نمایی",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "Paramètres ouvrir",
"hideWindow": "Cache-fenêtre",
"quitApp": "Quitter l'application",
"toggleSidebar": "Afficher/masquer la barre latérale",
"toggleWorkspace": "Afficher/masquer l’espace de travail",
"zoomIn": "Zoomer",
"zoomOut": "Zoom out",
"zoomReset": "Réinitialiser le zoom",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/he-IL/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "פתח הגדרות",
"hideWindow": "הסתר חלון",
"quitApp": "צא מהאפליקציה",
"toggleSidebar": "הצג/הסתר סרגל צד",
"toggleWorkspace": "הצג/הסתר סביבת עבודה",
"zoomIn": "הגדל תצוגה ",
"zoomOut": "הקטן תצוגה ",
"zoomReset": "אפס תצוגה",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ja-JP/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "設定を開く",
"hideWindow": "ウィンドウを非表示にします",
"quitApp": "プログラムを終了します",
"toggleSidebar": "サイドバーを表示/非表示",
"toggleWorkspace": "ワークスペースを表示/非表示",
"zoomIn": "フォントにズームインします",
"zoomOut": "フォントを削減します",
"zoomReset": "フォントをリセットします",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ko-KR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "열기 설정",
"hideWindow": "창을 숨기십시오",
"quitApp": "프로그램을 종료하십시오",
"toggleSidebar": "사이드바 표시/숨기기",
"toggleWorkspace": "작업 영역 표시/숨기기",
"zoomIn": "글꼴을 확대하십시오",
"zoomOut": "글꼴을 줄입니다",
"zoomReset": "글꼴을 재설정하십시오",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/pt-BR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "Abrir Configurações",
"hideWindow": "Ocultar janela",
"quitApp": "Sair do app",
"toggleSidebar": "Mostrar/ocultar barra lateral",
"toggleWorkspace": "Mostrar/ocultar espaço de trabalho",
"zoomIn": "Ampliar ",
"zoomOut": "Reduzir ",
"zoomReset": "Redefinir zoom",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ru-RU/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "Откройте настройки",
"hideWindow": "Скрыть окно",
"quitApp": "Отказаться от приложения",
"toggleSidebar": "Показать/скрыть боковую панель",
"toggleWorkspace": "Показать/скрыть рабочую область",
"zoomIn": "Увеличить",
"zoomOut": "Увеличить",
"zoomReset": "Сбросить Zoom",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,8 @@
"deleteConversation": "删除会话",
"hideWindow": "隐藏窗口",
"quitApp": "退出程序",
"toggleSidebar": "切换侧边栏",
"toggleWorkspace": "切换工作区",
"newWindow": "打开新窗口",
"showHideWindow": "显示/隐藏窗口",
"newConversation": "新会话",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-HK/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "打開設置",
"hideWindow": "隱藏窗口",
"quitApp": "退出程序",
"toggleSidebar": "顯示/隱藏側邊欄",
"toggleWorkspace": "顯示/隱藏工作區",
"zoomIn": "放大字體",
"zoomOut": "縮小字體",
"zoomReset": "重置字體",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-TW/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,8 @@
"goSettings": "打開設置",
"hideWindow": "隱藏窗口",
"quitApp": "退出程序",
"toggleSidebar": "顯示/隱藏側邊欄",
"toggleWorkspace": "顯示/隱藏工作區",
"zoomIn": "放大字體",
"zoomOut": "縮小字體",
"zoomReset": "重置字體",
Expand Down
20 changes: 20 additions & 0 deletions src/renderer/src/stores/ui/sidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useSidebarStore = defineStore('sidebar', () => {
const collapsed = ref(false)

const toggleSidebar = () => {
collapsed.value = !collapsed.value
}

const setCollapsed = (value: boolean) => {
collapsed.value = value
}

return {
collapsed,
toggleSidebar,
setCollapsed
}
})
Loading
Loading