import { useMemo, useState } from 'react' import { Box, TextField, Button, InputAdornment } from '@mui/material' import { Search, Close, MenuBook } from '@mui/icons-material' import { useI18nStore } from '../stores/i18n.store' import { useRedisStateStore } from '../stores/redis-state.store' import P3xrDialog from '../components/P3xrDialog' interface AiCheatsheetDialogProps { open: boolean onClose: () => void onPick: (prompt: string) => void } interface CheatGroup { key: string name: string description?: string prompts: string[] } export default function AiCheatsheetDialog({ open, onClose, onPick }: AiCheatsheetDialogProps) { const strings = useI18nStore(s => s.strings) const modules = useRedisStateStore(s => s.modules) || [] const info = useRedisStateStore(s => s.info) const [filter, setFilter] = useState('') const moduleNames = useMemo(() => modules.map((m: any) => (m?.name || '').toLowerCase()), [modules]) const version = useMemo(() => { const v = info?.server?.redis_version || '' const match = /^(\d+)/.exec(v) return match ? parseInt(match[1], 10) : 0 }, [info]) const isCluster = info?.server?.redis_mode === 'cluster' const visibleGroups = useMemo(() => { const cs = strings?.label?.cheatsheet?.groups if (!cs) return [] const result: CheatGroup[] = [] const push = (key: string, g: any) => { if (!g || !Array.isArray(g.prompts) || g.prompts.length === 0) return result.push({ key, name: g.name, description: g.description, prompts: g.prompts }) } push('diagnostics', cs.diagnostics) push('keys', cs.keys) push('dataTypes', cs.dataTypes) if (moduleNames.includes('rejson') || moduleNames.includes('rejson-rl') || moduleNames.includes('json')) push('json', cs.json) if (moduleNames.includes('search') || moduleNames.includes('searchlight')) push('search', cs.search) if (moduleNames.includes('timeseries')) push('timeseries', cs.timeseries) if (moduleNames.includes('bf')) push('bloom', cs.bloom) if (version >= 8) { push('vectorSet', cs.vectorSet) push('redis8', cs.redis8) } push('scripting', cs.scripting) if (isCluster) push('cluster', cs.cluster) if (version >= 6) push('acl', cs.acl) push('qna', cs.qna) push('translate', cs.translate) return result }, [strings, moduleNames, version, isCluster]) const filterPrompts = (prompts: string[]) => { const q = filter.trim().toLowerCase() if (!q) return prompts return prompts.filter(p => p.toLowerCase().includes(q)) } const emptyResults = visibleGroups.every(g => filterPrompts(g.prompts).length === 0) const cs = strings?.label?.cheatsheet if (!open) return null return ( }> {/* Sticky header — P3xrDialog has contentPadding={false} so we own all internal padding. Sticky sits at the true top of the scroll container with its own consistent padding all around. */} {cs?.subtitle && ( {cs.subtitle} )} {cs?.footerHint && ( {cs.footerHint} )} setFilter(e.target.value)} onKeyDown={e => e.stopPropagation()} sx={{ '& .MuiFormHelperText-root': { display: 'none' }, '& .MuiFilledInput-root': { mb: 0 }, }} InputProps={{ startAdornment: ( ), }} /> {visibleGroups.map(g => { const prompts = filterPrompts(g.prompts) if (prompts.length === 0) return null return ( {g.name} {g.description && ( {g.description} )} {prompts.map((p, i) => ( ))} ) })} {emptyResults && ( {cs?.empty} )} ) }