import { useEffect, useState } from 'react'; import { ComplexSearchQuery, ProjectConfiguration, SearchTypes, SupportedEmbeddingTypes } from '@vertesia/common'; import { Button, Checkbox, Input, Modal, ModalBody, ModalFooter, ModalTitle, NumberInput, useToast } from '@vertesia/ui/core'; import { useUserSession } from '@vertesia/ui/session'; import { Settings } from 'lucide-react'; import { useUITranslation } from '../../../../i18n/index.js'; interface VectorSearchWidgetProps { onChange: (query?: ComplexSearchQuery) => void; className?: string; status?: boolean; isLoading?: boolean; refresh: number; searchTypes?: (keyof typeof SearchTypes)[]; } const allTypes = Object.values(SearchTypes); const embeddingTypes = Object.values(SupportedEmbeddingTypes); export function VectorSearchWidget({ onChange, isLoading, refresh, searchTypes }: VectorSearchWidgetProps) { const { client, project } = useUserSession(); const { t } = useUITranslation(); const toast = useToast(); const [searchText, setSearchText] = useState(undefined); const [config, setConfig] = useState(undefined); const isReady = !!project && (!!config?.embeddings.text || !!config?.embeddings.image); const [status, setStatus] = useState(undefined); const [showSettings, setShowSettings] = useState(false); // Default to all types, or use prop if provided const [selectedTypes, setSelectedTypes] = useState<(keyof typeof SearchTypes)[]>(searchTypes || allTypes); const [limit, setLimit] = useState(100); useEffect(() => { if (searchTypes) setSelectedTypes(searchTypes); }, [searchTypes]); // Always derive embeddingSearchTypes and full_text from selectedTypes const embeddingSearchTypes: Record = {}; let fullTextEnabled = false; let vectorSearchEnabled = false; selectedTypes.forEach(type => { if (type === SearchTypes.full_text) { fullTextEnabled = true; } else { vectorSearchEnabled = true; } if (embeddingTypes.includes(type as SupportedEmbeddingTypes)) { embeddingSearchTypes[type] = true; } }); useEffect(() => { setSearchText(undefined); setStatus(undefined); }, [refresh]); useEffect(() => { if (!project) return; client.projects.retrieve(project.id).then((project) => { setConfig(project.configuration); }) }, [project]); useEffect(() => { if (status) { toast({ title: status, status: 'success', duration: 2000 }); } }, [status]); useEffect(() => { if (!searchText || searchText.length === 0) { onChange(undefined); } }, [searchText]); const fireSearch = () => { if (!isReady || !searchText) return; const query: ComplexSearchQuery = { vector: vectorSearchEnabled ? { text: searchText, config: embeddingSearchTypes, } : undefined, full_text: fullTextEnabled ? searchText : undefined, limit: limit }; onChange(query); setStatus("Searching..."); }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { fireSearch(); } }; // Modal state for search type selection const handleCheckboxChange = (type: keyof typeof SearchTypes) => (checked: boolean) => { if (checked) { setSelectedTypes(prev => Array.from(new Set([...prev, type]))); } else { setSelectedTypes(prev => prev.filter(t => t !== type)); } }; return (
setShowSettings(false)}> {t('store.searchTypes')}
{t('store.embeddings')}
{embeddingTypes.map(type => ( ))}
{t('store.limit')} setLimit(Number(v ?? 100))} style={{ width: 80 }} />
); }