| import { useMemo } from 'react'; | |
| import type { TAttachment } from 'librechat-data-provider'; | |
| import { StackedFavicons } from '~/components/Web/Sources'; | |
| import { useSearchContext } from '~/Providers'; | |
| import ProgressText from './ProgressText'; | |
| import { useLocalize } from '~/hooks'; | |
| type ProgressKeys = | |
| | 'com_ui_web_searching' | |
| | 'com_ui_web_searching_again' | |
| | 'com_ui_web_search_processing' | |
| | 'com_ui_web_search_reading'; | |
| export default function WebSearch({ | |
| initialProgress: progress = 0.1, | |
| isSubmitting, | |
| isLast, | |
| output, | |
| }: { | |
| isLast?: boolean; | |
| isSubmitting: boolean; | |
| output?: string | null; | |
| initialProgress: number; | |
| attachments?: TAttachment[]; | |
| }) { | |
| const localize = useLocalize(); | |
| const { searchResults } = useSearchContext(); | |
| const error = typeof output === 'string' && output.toLowerCase().includes('error processing'); | |
| const cancelled = (!isSubmitting && progress < 1) || error === true; | |
| const complete = !isLast && progress === 1; | |
| const finalizing = isSubmitting && isLast && progress === 1; | |
| const processedSources = useMemo(() => { | |
| if (complete && !finalizing) { | |
| return []; | |
| } | |
| if (!searchResults) return []; | |
| const values = Object.values(searchResults); | |
| const result = values[values.length - 1]; | |
| if (!result) return []; | |
| if (finalizing) { | |
| return [...(result.organic || []), ...(result.topStories || [])]; | |
| } | |
| return [...(result.organic || []), ...(result.topStories || [])].filter( | |
| (source) => source.processed === true, | |
| ); | |
| }, [searchResults, complete, finalizing]); | |
| const turns = useMemo(() => { | |
| if (!searchResults) return 0; | |
| return Object.values(searchResults).length; | |
| }, [searchResults]); | |
| const clampedProgress = useMemo(() => { | |
| return Math.min(progress, 0.99); | |
| }, [progress]); | |
| const showSources = processedSources.length > 0; | |
| const progressText = useMemo(() => { | |
| let text: ProgressKeys = turns > 1 ? 'com_ui_web_searching_again' : 'com_ui_web_searching'; | |
| if (showSources) { | |
| text = 'com_ui_web_search_processing'; | |
| } | |
| if (finalizing) { | |
| text = 'com_ui_web_search_reading'; | |
| } | |
| return localize(text); | |
| }, [turns, localize, showSources, finalizing]); | |
| if (complete || cancelled) { | |
| return null; | |
| } | |
| return ( | |
| <> | |
| <div className="relative my-2.5 flex size-5 shrink-0 items-center gap-2.5"> | |
| {showSources && ( | |
| <div className="mr-2"> | |
| <StackedFavicons sources={processedSources} start={-5} /> | |
| </div> | |
| )} | |
| <ProgressText | |
| finishedText="" | |
| hasInput={false} | |
| error={cancelled} | |
| isExpanded={false} | |
| progress={clampedProgress} | |
| inProgressText={progressText} | |
| /> | |
| </div> | |
| </> | |
| ); | |
| } | |