import { useState, useMemo, useEffect } from 'react' import { useKV } from '@/hooks/use-kv' import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from '@/components/ui/command' import { Code, Database, Tree, PaintBrush, Flask, Play, BookOpen, Cube, FlowArrow, Wrench, FileText, Gear, DeviceMobile, Faders, ChartBar, Image, File, Folder, MagnifyingGlass, ClockCounterClockwise, X, Lightbulb, } from '@phosphor-icons/react' import { ProjectFile, PrismaModel, ComponentNode, ComponentTree, Workflow, Lambda, PlaywrightTest, StorybookStory, UnitTest } from '@/types/project' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' interface SearchResult { id: string title: string subtitle?: string category: string icon: React.ReactNode action: () => void tags?: string[] } interface SearchHistoryItem { id: string query: string timestamp: number resultId?: string resultTitle?: string resultCategory?: string } interface GlobalSearchProps { open: boolean onOpenChange: (open: boolean) => void files: ProjectFile[] models: PrismaModel[] components: ComponentNode[] componentTrees: ComponentTree[] workflows: Workflow[] lambdas: Lambda[] playwrightTests: PlaywrightTest[] storybookStories: StorybookStory[] unitTests: UnitTest[] onNavigate: (tab: string, itemId?: string) => void onFileSelect: (fileId: string) => void } export function GlobalSearch({ open, onOpenChange, files, models, components, componentTrees, workflows, lambdas, playwrightTests, storybookStories, unitTests, onNavigate, onFileSelect, }: GlobalSearchProps) { const [searchQuery, setSearchQuery] = useState('') const [searchHistory, setSearchHistory] = useKV('search-history', []) useEffect(() => { if (!open) { setSearchQuery('') } }, [open]) const addToHistory = (query: string, result?: SearchResult) => { if (!query.trim()) return const historyItem: SearchHistoryItem = { id: `history-${Date.now()}`, query: query.trim(), timestamp: Date.now(), resultId: result?.id, resultTitle: result?.title, resultCategory: result?.category, } setSearchHistory((currentHistory) => { const filtered = (currentHistory || []).filter( (item) => item.query.toLowerCase() !== query.toLowerCase() ) return [historyItem, ...filtered].slice(0, 20) }) } const clearHistory = () => { setSearchHistory([]) } const removeHistoryItem = (id: string) => { setSearchHistory((currentHistory) => (currentHistory || []).filter((item) => item.id !== id) ) } const allResults = useMemo(() => { const results: SearchResult[] = [] results.push({ id: 'nav-dashboard', title: 'Dashboard', subtitle: 'View project overview and statistics', category: 'Navigation', icon: , action: () => onNavigate('dashboard'), tags: ['home', 'overview', 'stats', 'metrics'], }) results.push({ id: 'nav-code', title: 'Code Editor', subtitle: 'Edit project files with Monaco', category: 'Navigation', icon: , action: () => onNavigate('code'), tags: ['editor', 'monaco', 'typescript', 'javascript'], }) results.push({ id: 'nav-models', title: 'Models', subtitle: 'Design Prisma database models', category: 'Navigation', icon: , action: () => onNavigate('models'), tags: ['prisma', 'database', 'schema', 'orm'], }) results.push({ id: 'nav-components', title: 'Components', subtitle: 'Build React components', category: 'Navigation', icon: , action: () => onNavigate('components'), tags: ['react', 'mui', 'ui', 'design'], }) results.push({ id: 'nav-component-trees', title: 'Component Trees', subtitle: 'Manage component hierarchies', category: 'Navigation', icon: , action: () => onNavigate('component-trees'), tags: ['hierarchy', 'structure', 'layout'], }) results.push({ id: 'nav-workflows', title: 'Workflows', subtitle: 'Design n8n-style workflows', category: 'Navigation', icon: , action: () => onNavigate('workflows'), tags: ['automation', 'n8n', 'flow', 'pipeline'], }) results.push({ id: 'nav-lambdas', title: 'Lambdas', subtitle: 'Create serverless functions', category: 'Navigation', icon: , action: () => onNavigate('lambdas'), tags: ['serverless', 'functions', 'api'], }) results.push({ id: 'nav-styling', title: 'Styling', subtitle: 'Design themes and colors', category: 'Navigation', icon: , action: () => onNavigate('styling'), tags: ['theme', 'colors', 'css', 'design'], }) results.push({ id: 'nav-flask', title: 'Flask API', subtitle: 'Configure Flask backend', category: 'Navigation', icon: , action: () => onNavigate('flask'), tags: ['python', 'backend', 'api', 'rest'], }) results.push({ id: 'nav-playwright', title: 'Playwright Tests', subtitle: 'E2E testing configuration', category: 'Navigation', icon: , action: () => onNavigate('playwright'), tags: ['testing', 'e2e', 'automation'], }) results.push({ id: 'nav-storybook', title: 'Storybook', subtitle: 'Component documentation', category: 'Navigation', icon: , action: () => onNavigate('storybook'), tags: ['documentation', 'components', 'stories'], }) results.push({ id: 'nav-unit-tests', title: 'Unit Tests', subtitle: 'Configure unit testing', category: 'Navigation', icon: , action: () => onNavigate('unit-tests'), tags: ['testing', 'jest', 'vitest'], }) results.push({ id: 'nav-errors', title: 'Error Repair', subtitle: 'Auto-detect and fix errors', category: 'Navigation', icon: , action: () => onNavigate('errors'), tags: ['debugging', 'errors', 'fixes'], }) results.push({ id: 'nav-docs', title: 'Documentation', subtitle: 'View project documentation', category: 'Navigation', icon: , action: () => onNavigate('docs'), tags: ['readme', 'guide', 'help'], }) results.push({ id: 'nav-sass', title: 'Sass Styles', subtitle: 'Custom Sass styling', category: 'Navigation', icon: , action: () => onNavigate('sass'), tags: ['sass', 'scss', 'styles', 'css'], }) results.push({ id: 'nav-favicon', title: 'Favicon Designer', subtitle: 'Design app icons', category: 'Navigation', icon: , action: () => onNavigate('favicon'), tags: ['icon', 'logo', 'design'], }) results.push({ id: 'nav-settings', title: 'Settings', subtitle: 'Configure Next.js and npm', category: 'Navigation', icon: , action: () => onNavigate('settings'), tags: ['config', 'nextjs', 'npm', 'packages'], }) results.push({ id: 'nav-pwa', title: 'PWA Settings', subtitle: 'Progressive Web App config', category: 'Navigation', icon: , action: () => onNavigate('pwa'), tags: ['mobile', 'install', 'offline'], }) results.push({ id: 'nav-features', title: 'Feature Toggles', subtitle: 'Enable or disable features', category: 'Navigation', icon: , action: () => onNavigate('features'), tags: ['settings', 'toggles', 'enable'], }) results.push({ id: 'nav-ideas', title: 'Feature Ideas', subtitle: 'Brainstorm and organize ideas', category: 'Navigation', icon: , action: () => onNavigate('ideas'), tags: ['brainstorm', 'ideas', 'planning'], }) files.forEach((file) => { results.push({ id: `file-${file.id}`, title: file.name, subtitle: file.path, category: 'Files', icon: , action: () => { onNavigate('code') onFileSelect(file.id) }, tags: [file.language, file.path, 'code', 'file'], }) }) models.forEach((model) => { results.push({ id: `model-${model.id}`, title: model.name, subtitle: `${model.fields.length} fields`, category: 'Models', icon: , action: () => onNavigate('models', model.id), tags: ['prisma', 'database', 'schema', model.name.toLowerCase()], }) }) components.forEach((component) => { results.push({ id: `component-${component.id}`, title: component.name, subtitle: component.type, category: 'Components', icon: , action: () => onNavigate('components', component.id), tags: ['react', 'component', component.type.toLowerCase(), component.name.toLowerCase()], }) }) componentTrees.forEach((tree) => { results.push({ id: `tree-${tree.id}`, title: tree.name, subtitle: tree.description || `${tree.rootNodes.length} root nodes`, category: 'Component Trees', icon: , action: () => onNavigate('component-trees', tree.id), tags: ['hierarchy', 'structure', tree.name.toLowerCase()], }) }) workflows.forEach((workflow) => { results.push({ id: `workflow-${workflow.id}`, title: workflow.name, subtitle: workflow.description || `${workflow.nodes.length} nodes`, category: 'Workflows', icon: , action: () => onNavigate('workflows', workflow.id), tags: ['automation', 'flow', workflow.name.toLowerCase()], }) }) lambdas.forEach((lambda) => { results.push({ id: `lambda-${lambda.id}`, title: lambda.name, subtitle: lambda.description || lambda.runtime, category: 'Lambdas', icon: , action: () => onNavigate('lambdas', lambda.id), tags: ['serverless', 'function', lambda.runtime, lambda.name.toLowerCase()], }) }) playwrightTests.forEach((test) => { results.push({ id: `playwright-${test.id}`, title: test.name, subtitle: test.description, category: 'Playwright Tests', icon: , action: () => onNavigate('playwright', test.id), tags: ['testing', 'e2e', test.name.toLowerCase()], }) }) storybookStories.forEach((story) => { results.push({ id: `storybook-${story.id}`, title: story.storyName, subtitle: story.componentName, category: 'Storybook Stories', icon: , action: () => onNavigate('storybook', story.id), tags: ['documentation', 'story', story.componentName.toLowerCase()], }) }) unitTests.forEach((test) => { results.push({ id: `unit-test-${test.id}`, title: test.name, subtitle: test.description, category: 'Unit Tests', icon: , action: () => onNavigate('unit-tests', test.id), tags: ['testing', 'unit', test.name.toLowerCase()], }) }) return results }, [ files, models, components, componentTrees, workflows, lambdas, playwrightTests, storybookStories, unitTests, onNavigate, onFileSelect, ]) const filteredResults = useMemo(() => { if (!searchQuery.trim()) { return [] } const query = searchQuery.toLowerCase().trim() const queryWords = query.split(/\s+/) return allResults .map((result) => { let score = 0 const titleLower = result.title.toLowerCase() const subtitleLower = result.subtitle?.toLowerCase() || '' const categoryLower = result.category.toLowerCase() const tagsLower = result.tags?.map(t => t.toLowerCase()) || [] if (titleLower === query) score += 100 else if (titleLower.startsWith(query)) score += 50 else if (titleLower.includes(query)) score += 30 if (subtitleLower.includes(query)) score += 20 if (categoryLower.includes(query)) score += 15 tagsLower.forEach(tag => { if (tag === query) score += 40 else if (tag.includes(query)) score += 10 }) queryWords.forEach(word => { if (titleLower.includes(word)) score += 5 if (subtitleLower.includes(word)) score += 3 if (tagsLower.some(tag => tag.includes(word))) score += 2 }) return { result, score } }) .filter(({ score }) => score > 0) .sort((a, b) => b.score - a.score) .slice(0, 50) .map(({ result }) => result) }, [allResults, searchQuery]) const recentSearches = useMemo(() => { const safeHistory = searchHistory || [] return safeHistory .slice(0, 10) .map((item) => { const result = allResults.find((r) => r.id === item.resultId) return { historyItem: item, result, } }) }, [searchHistory, allResults]) const groupedResults = useMemo(() => { const groups: Record = {} filteredResults.forEach((result) => { if (!groups[result.category]) { groups[result.category] = [] } groups[result.category].push(result) }) return groups }, [filteredResults]) const handleSelect = (result: SearchResult) => { addToHistory(searchQuery, result) result.action() onOpenChange(false) } const handleHistorySelect = (historyItem: SearchHistoryItem, result?: SearchResult) => { if (result) { result.action() onOpenChange(false) } else { setSearchQuery(historyItem.query) } } return (

No results found

{!searchQuery.trim() && recentSearches.length > 0 && ( <> Recent Searches } > {recentSearches.map(({ historyItem, result }) => ( handleHistorySelect(historyItem, result)} className="flex items-center gap-3 px-4 py-3 cursor-pointer group" >
{result ? ( <>
{result.title}
Searched: {historyItem.query}
) : ( <>
{historyItem.query}
Search again
)}
{result && ( {result.category} )}
))}
)} {Object.entries(groupedResults).map(([category, results], index) => (
{index > 0 && } {results.map((result) => ( handleSelect(result)} className="flex items-center gap-3 px-4 py-3 cursor-pointer" >
{result.icon}
{result.title}
{result.subtitle && (
{result.subtitle}
)}
{category}
))}
))}
) }