import { createSignal, For, Show, onMount } from 'solid-js'; import { useSearchParams } from '@solidjs/router'; import { IconSearch, IconFilter, IconBookmark, IconChecklist, IconNotebook, IconFolder, IconX, IconStar, IconEye, IconEyeOff, IconFileText } from '@tabler/icons-solidjs'; import { Input } from '@/components/ui/Input'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { SavedSearches } from './SavedSearches'; interface SearchFilters { query: string; content_type: 'all' | 'bookmarks' | 'tasks' | 'notes' | 'files'; tags: string[]; date_range: { start: string; end: string; }; author: string; language: string; file_types: string[]; is_favorite?: boolean; is_read?: boolean; is_public?: boolean; limit: number; offset: number; search_mode: 'fulltext' | 'semantic' | 'hybrid'; // New field threshold: number; // Similarity threshold for semantic search } interface SearchResult { id: number; type: string; title: string; description: string; content: string; tags: Array<{ id: number; name: string; color: string }>; created_at: string; updated_at: string; url?: string; status?: string; priority?: string; due_date?: string; is_favorite?: boolean; is_read?: boolean; is_public?: boolean; author?: string; file_size?: number; mime_type?: string; file_type?: string; progress?: number; highlights?: Record; score: number; similarity?: number; // Semantic similarity score } interface SearchResponse { results: SearchResult[]; total: number; query: string; filters: SearchFilters; took: number; suggestions: string[]; aggregations: Record; } export const EnhancedSearch = () => { const [activeTab, setActiveTab] = createSignal<'search' | 'saved'>('search'); const [searchQuery, setSearchQuery] = createSignal(''); const [filters, setFilters] = createSignal({ query: '', content_type: 'all', tags: [], date_range: { start: '', end: '' }, author: '', language: '', file_types: [], limit: 20, offset: 0, search_mode: 'fulltext', threshold: 0.7 }); const [searchResults, setSearchResults] = createSignal([]); const [total, setTotal] = createSignal(0); const [loading, setLoading] = createSignal(false); const [showFilters, setShowFilters] = createSignal(false); const [aggregations, setAggregations] = createSignal>({}); const [took, setTook] = createSignal(0); const [searchParams] = useSearchParams(); // API call to search const performSearch = async (resetOffset = true) => { setLoading(true); const currentFilters = { ...filters() }; currentFilters.query = searchQuery(); if (resetOffset) { currentFilters.offset = 0; } try { // Try multiple token sources for better compatibility const token = localStorage.getItem('token') || localStorage.getItem('auth_token') || localStorage.getItem('trackeep_token'); let response; if (currentFilters.search_mode === 'semantic') { // Use semantic search API response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/semantic`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' }, body: JSON.stringify({ query: currentFilters.query, content_type: currentFilters.content_type, limit: currentFilters.limit, threshold: currentFilters.threshold }) }); if (response.ok) { const data = await response.json(); if (resetOffset) { setSearchResults(data.results); } else { setSearchResults(prev => [...prev, ...data.results]); } setTotal(data.results.length); // Semantic search doesn't return total count setTook(data.took); } } else { // Use enhanced full-text search API response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/search/enhanced`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' }, body: JSON.stringify(currentFilters) }); if (response.ok) { const data: SearchResponse = await response.json(); if (resetOffset) { setSearchResults(data.results); } else { setSearchResults(prev => [...prev, ...data.results]); } setTotal(data.total); setAggregations(data.aggregations); setTook(data.took); } } if (!response.ok) { // If unauthorized, fallback to mock data if (response.status === 401) { console.warn('Search authorization failed, using mock data'); const mockResults: SearchResult[] = [ { id: 1, type: 'bookmark', title: `Mock result for "${currentFilters.query}"`, description: 'This is a mock search result due to authorization issues', content: 'Mock content for demonstration purposes', tags: [{ id: 1, name: 'demo', color: '#6b7280' }], created_at: new Date().toISOString(), updated_at: new Date().toISOString(), url: 'https://example.com', score: 0.9 }, { id: 2, type: 'note', title: `Another mock result for "${currentFilters.query}"`, description: 'Another mock search result in demo mode', content: 'Additional mock content for search demonstration', tags: [{ id: 2, name: 'mock', color: '#3b82f6' }], created_at: new Date().toISOString(), updated_at: new Date().toISOString(), score: 0.8 } ]; if (resetOffset) { setSearchResults(mockResults); } else { setSearchResults(prev => [...prev, ...mockResults]); } setTotal(mockResults.length); setTook(50); return; } throw new Error('Search failed'); } } catch (error) { console.error('Search failed:', error); // Fallback to mock data on any error const mockResults: SearchResult[] = [ { id: 1, type: 'bookmark', title: `Fallback result for "${currentFilters.query}"`, description: 'This is a fallback search result due to API errors', content: 'Fallback content for demonstration purposes', tags: [{ id: 1, name: 'fallback', color: '#ef4444' }], created_at: new Date().toISOString(), updated_at: new Date().toISOString(), url: 'https://example.com', score: 0.7 } ]; if (resetOffset) { setSearchResults(mockResults); } else { setSearchResults(prev => [...prev, ...mockResults]); } setTotal(mockResults.length); setTook(100); } finally { setLoading(false); } }; // Debounced search let searchTimeout: number; const debouncedSearch = () => { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => performSearch(), 300); }; // Handle search input const handleSearchInput = (value: string) => { setSearchQuery(value); debouncedSearch(); }; // Handle filter changes const updateFilter = (key: keyof SearchFilters, value: any) => { setFilters(prev => ({ ...prev, [key]: value })); setTimeout(() => performSearch(), 100); }; // Add/remove tag filter const toggleTag = (tag: string) => { const currentTags = filters().tags; const newTags = currentTags.includes(tag) ? currentTags.filter(t => t !== tag) : [...currentTags, tag]; updateFilter('tags', newTags); }; // Clear all filters const clearFilters = () => { setFilters({ query: searchQuery(), content_type: 'all', tags: [], date_range: { start: '', end: '' }, author: '', language: '', file_types: [], limit: 20, offset: 0, search_mode: 'fulltext', threshold: 0.7 }); setTimeout(() => performSearch(), 100); }; // Load more results const loadMore = () => { setFilters(prev => ({ ...prev, offset: prev.offset + prev.limit })); setTimeout(() => performSearch(false), 100); }; // Get icon for content type const getIcon = (type: string) => { switch (type) { case 'bookmark': return IconBookmark; case 'task': return IconChecklist; case 'note': return IconNotebook; case 'file': return IconFolder; default: return IconFileText; } }; // Get color for content type const getTypeColor = (type: string) => { switch (type) { case 'bookmark': return 'text-green-400'; case 'task': return 'text-yellow-400'; case 'note': return 'text-purple-400'; case 'file': return 'text-orange-400'; default: return 'text-gray-400'; } }; // Format file size const formatFileSize = (bytes?: number) => { if (!bytes) return ''; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; }; // Format date const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString(); }; // Get priority color const getPriorityColor = (priority?: string) => { switch (priority) { case 'urgent': return 'bg-red-500'; case 'high': return 'bg-orange-500'; case 'medium': return 'bg-yellow-500'; case 'low': return 'bg-green-500'; default: return 'bg-gray-500'; } }; // Get status color const getStatusColor = (status?: string) => { switch (status) { case 'completed': return 'bg-green-500'; case 'in_progress': return 'bg-blue-500'; case 'pending': return 'bg-yellow-500'; case 'cancelled': return 'bg-red-500'; default: return 'bg-gray-500'; } }; // Initial search on mount (respect URL query/tag params) onMount(() => { const urlQuery = (searchParams as any).query || ''; const urlTag = (searchParams as any).tag || ''; const initialQuery = urlQuery || urlTag || ''; if (initialQuery) { setSearchQuery(initialQuery); setFilters(prev => ({ ...prev, query: initialQuery, tags: urlTag ? [urlTag] : prev.tags, })); } performSearch(); }); return (
{/* Header with Tabs */}

Enhanced Search

Search across all your content with powerful filters and AI-powered discovery

{/* Tabs */}
{/* Tab Content */}
{/* Search Input */}
handleSearchInput(e.target?.value || '')} class="pl-10 pr-12 h-12 text-lg" />
{/* Search Stats */} 0}>
Found {total()} results in {took()}ms
{([type, count]) => ( {type}: {count} )}
{/* Filters Panel */}

Filters

{/* Search Mode */}
{/* Similarity Threshold (for semantic search) */}
updateFilter('threshold', parseFloat(e.target.value))} class="w-full" />
More results More precise
{/* Content Type Filter */}
{/* Date Range */}
updateFilter('date_range', { ...filters().date_range, start: e.target?.value || '' })} placeholder="Start date" /> updateFilter('date_range', { ...filters().date_range, end: e.target?.value || '' })} placeholder="End date" />
{/* Author Filter */}
updateFilter('author', e.target?.value || '')} placeholder="Filter by author" />
{/* Boolean Filters */}
{/* Active Tags */} 0}>
{(tag) => ( toggleTag(tag)}> {tag} )}
{/* Search Results */}

Searching...

No results found

Try adjusting your search terms or filters

0}>
{(result) => { const Icon = getIcon(result.type); return (
{/* Type Icon */}
{/* Content */}

{result.title}

{result.description}

{/* Content preview */}

{result.content.substring(0, 200)}...

{/* Tags */} 0}>
{(tag) => ( toggleTag(tag.name)} > {tag.name} )}
{/* Metadata */}
{result.type} Created {formatDate(result.created_at)} Updated {formatDate(result.updated_at)} By {result.author} {formatFileSize(result.file_size)} Score: {result.score.toFixed(1)} Similarity: {(result.similarity! * 100).toFixed(1)}%
{/* Status/Priority Indicators */}
{result.status} {result.priority} {result.is_read ? ( ) : ( )}
{result.progress}%
{/* URL for bookmarks */}
); }}
{/* Load More */}
); };