import IconBox from '@geist-ui/icons/box'; import IconSearch from '@geist-ui/icons/search'; import Link from 'next/link'; import React from 'react'; import { atom, useRecoilState } from 'recoil'; import { Loading } from '@/components/primitives'; import { useLanguage } from '@/intl/client'; import { t } from '@/intl/translate'; import { iterateStreamResponse } from '@/lib/actions'; import { tcls } from '@/lib/tailwind'; import { AskAnswerResult, streamAskQuestion } from './server-actions'; import { useSearch, useSearchLink } from './useSearch'; /** * Store the state of the answer in a global state so that it can be * accessed from anywhere to show a loading indicator. */ export const searchAskState = atom< | { type: 'answer'; answer: AskAnswerResult | null; } | { type: 'error'; } | { type: 'loading'; } | null >({ key: 'searchAskState', default: null, }); /** * Fetch and render the answers to a question. */ export function SearchAskAnswer(props: { spaceId: string; query: string }) { const { spaceId, query } = props; const language = useLanguage(); const [, setSearchState] = useSearch(); const [state, setState] = useRecoilState(searchAskState); React.useEffect(() => { let cancelled = false; setState({ type: 'loading', }); (async () => { const stream = iterateStreamResponse(streamAskQuestion(spaceId, query)); setSearchState((prev) => prev ? { ...prev, ask: true, query, } : null, ); for await (const chunk of stream) { if (cancelled) { return; } setState({ type: 'answer', answer: chunk, }); } })().catch((error) => { if (cancelled) { return; } setState({ type: 'error', }); }); return () => { cancelled = true; }; }, [spaceId, query, setSearchState, setState]); React.useEffect(() => { return () => { setState(null); }; }, [setState]); return (