import { useHistory, useLocation } from '@docusaurus/router'; import searchConfig from '@docuservix-search/config'; import Link from '@docusaurus/Link'; import Layout from '@theme/Layout'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import type { SearchResult } from '../../types'; import styles from './styles.module.css'; const MAX_RESULTS = 25; function useQuery(): string { const location = useLocation(); const params = new URLSearchParams(location.search); return params.get('q') ?? ''; } export default function SearchPage(): JSX.Element { const urlQuery = useQuery(); const history = useHistory(); const [inputValue, setInputValue] = useState(urlQuery); const [results, setResults] = useState([]); const [notices, setNotices] = useState([]); const [errors, setErrors] = useState([]); const [loading, setLoading] = useState(false); const [activeFilter, setActiveFilter] = useState('all'); const abortRef = useRef(null); const runSearch = useCallback(async (q: string) => { if (!q.trim()) { setResults([]); setNotices([]); setErrors([]); return; } if (abortRef.current) { abortRef.current.abort(); } const controller = new AbortController(); abortRef.current = controller; const timeout = searchConfig.timeout ?? 5000; const timeoutId = setTimeout(() => controller.abort(), timeout); setLoading(true); setErrors([]); try { const settled = await Promise.allSettled( searchConfig.providers.map((provider) => provider.search(q, controller.signal)), ); if (controller.signal.aborted) { return; } const allResults: SearchResult[] = []; const allNotices: string[] = []; const allErrors: string[] = []; for (let i = 0; i < settled.length; i++) { const outcome = settled[i]; const provider = searchConfig.providers[i]; if (outcome.status === 'fulfilled') { allResults.push(...outcome.value.results); if (outcome.value.notice) { allNotices.push(outcome.value.notice); } } else { allErrors.push(`${provider.name}: недоступен`); } } allResults.sort((a, b) => b.relevance - a.relevance); setResults(allResults.slice(0, MAX_RESULTS)); setNotices(allNotices); setErrors(allErrors); setActiveFilter('all'); } finally { clearTimeout(timeoutId); setLoading(false); } }, []); useEffect(() => { setInputValue(urlQuery); runSearch(urlQuery); }, [urlQuery, runSearch]); useEffect(() => { return () => { if (abortRef.current) { abortRef.current.abort(); } }; }, []); const handleInputChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; const handleInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && inputValue.trim()) { history.push(`/search?q=${encodeURIComponent(inputValue.trim())}`); } }; const sourceTypes = ['all', ...Array.from(new Set(results.map((r) => r.type)))]; const filteredResults = activeFilter === 'all' ? results : results.filter((r) => r.type === activeFilter); const typeLabel: Record = { all: 'Все', docs: 'Docs', }; return (

Поиск

{urlQuery && ( Спросить ИИ → )} {errors.length > 0 && (
{errors.map((err, i) => (
{err}
))}
)} {notices.length > 0 && (
{notices.map((notice, i) => (
{notice}
))}
)} {loading &&
Поиск...
} {!loading && urlQuery && ( <> {sourceTypes.length > 2 && (
{sourceTypes.map((type) => ( ))}
)} {filteredResults.length === 0 ? (
Ничего не найдено
) : ( )} )}
); }