/** * CommandPalette — 快速搜索面板 (Cmd+K) */ import { useState, useEffect, useRef, useCallback } from 'react'; import { Dialog, DialogContent, InputBase, List, ListItemButton, ListItemIcon, ListItemText, Typography, Box, Divider } from '@mui/material'; import { Search, MessageSquare, Settings, Plus, Trash2 } from 'lucide-react'; import { useUIStore } from '@renderer/stores/ui-store'; import { useSessionStore } from '@renderer/stores/session-store'; import { useAgentStore } from '@renderer/stores/agent-store'; interface CommandItem { id: string; icon: typeof Search; label: string; description?: string; action: () => void; } export function CommandPalette(): React.JSX.Element { const [open, setOpen] = useState(false); const [query, setQuery] = useState(''); const [selectedIndex, setSelectedIndex] = useState(0); const inputRef = useRef(null); useEffect(() => { const h = (e: KeyboardEvent) => { if (e.key === 'k' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); setOpen((p) => !p); setQuery(''); setSelectedIndex(0); } }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, []); useEffect(() => { if (open) setTimeout(() => inputRef.current?.focus(), 50); }, [open]); const getCommands = useCallback((): CommandItem[] => { const cmds: CommandItem[] = [ { id: 'new-session', icon: Plus, label: '新建会话', description: '创建一个新的对话会话', action: async () => { if (window.metona?.sessions?.create) { const r = await window.metona.sessions.create() as MetonaSessionInfo; useSessionStore.getState().addSession(r); useSessionStore.getState().setCurrentSession(r.id); useAgentStore.getState().setCurrentSession(r.id); } setOpen(false); } }, { id: 'clear', icon: Trash2, label: '清空当前会话', action: () => { useAgentStore.getState().clearMessages(); setOpen(false); } }, { id: 'settings', icon: Settings, label: '打开设置', action: () => { useUIStore.getState().openSettings(); setOpen(false); } }, ]; if (query) { const filtered = useSessionStore.getState().sessions.filter((s) => s.title.toLowerCase().includes(query.toLowerCase())); for (const s of filtered.slice(0, 5)) cmds.push({ id: `s-${s.id}`, icon: MessageSquare, label: s.title, description: `${s.messageCount} 条消息`, action: () => { useSessionStore.getState().setCurrentSession(s.id); useAgentStore.getState().setCurrentSession(s.id); setOpen(false); } }); } return cmds; }, [query]); const commands = getCommands(); return ( setOpen(false)} maxWidth="sm" fullWidth slotProps={{ paper: { sx: { mt: '20vh', maxWidth: 520, borderRadius: 3, overflow: 'hidden' } } }}> { setQuery(e.target.value); setSelectedIndex(0); }} onKeyDown={(e) => { if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((p) => Math.min(p + 1, commands.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex((p) => Math.max(p - 1, 0)); } else if (e.key === 'Enter') { e.preventDefault(); commands[selectedIndex]?.action(); } }} placeholder="搜索会话、命令..." sx={{ flex: 1, fontSize: 13 }} /> {commands.length === 0 ? ( 无匹配结果 ) : commands.map((cmd, i) => { const Icon = cmd.icon; return ( {cmd.label}} secondary={cmd.description ? {cmd.description} : undefined} /> ); })} ↑↓ 导航↵ 选择Esc 关闭 ); }