diff --git a/src/components/app/AppContent.tsx b/src/components/app/AppContent.tsx index 63c11df0..1671fa44 100644 --- a/src/components/app/AppContent.tsx +++ b/src/components/app/AppContent.tsx @@ -1,8 +1,10 @@ import { useEffect, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; + import Sidebar from '../sidebar/view/Sidebar'; import MainContent from '../main-content/view/MainContent'; +import CommandPalette from '../command-palette/CommandPalette'; import { useWebSocket } from '../../contexts/WebSocketContext'; import { useDeviceSettings } from '../../hooks/useDeviceSettings'; import { useSessionProtection } from '../../hooks/useSessionProtection'; @@ -40,6 +42,7 @@ export default function AppContent() { openSettings, refreshProjectsSilently, sidebarSharedProps, + handleNewSession, } = useProjectsState({ sessionId, navigate, @@ -202,6 +205,11 @@ export default function AppContent() { /> + openSettings()} + /> ); } diff --git a/src/components/command-palette/CommandPalette.tsx b/src/components/command-palette/CommandPalette.tsx new file mode 100644 index 00000000..d2109e77 --- /dev/null +++ b/src/components/command-palette/CommandPalette.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { MessageSquarePlus, Settings, SunMoon } from 'lucide-react'; + +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + Dialog, + DialogContent, + DialogTitle, +} from '../../shared/view/ui'; +import { useTheme } from '../../contexts/ThemeContext'; +import type { Project } from '../../types/app'; + +type CommandPaletteProps = { + selectedProject: Project | null; + onStartNewChat: (project: Project) => void; + onOpenSettings: () => void; +}; + +export default function CommandPalette({ + selectedProject, + onStartNewChat, + onOpenSettings, +}: CommandPaletteProps) { + const [open, setOpen] = React.useState(false); + const { toggleDarkMode } = useTheme(); + + React.useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + const isCmdK = (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey && e.key.toLowerCase() === 'k'; + if (!isCmdK) return; + e.preventDefault(); + setOpen((prev) => !prev); + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); + + const run = React.useCallback((fn: () => void) => { + setOpen(false); + fn(); + }, []); + + const startNewChatDisabled = !selectedProject; + + return ( + + + Command palette + + + + No results. + + { + if (!selectedProject) return; + run(() => onStartNewChat(selectedProject)); + }} + > + + Start new chat + {startNewChatDisabled && ( + Select a project first + )} + + run(onOpenSettings)} + > + + Open settings + + run(toggleDarkMode)} + > + + Toggle theme + + + + + + + ); +}