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 (
{state?.type === 'answer' ? ( <> {state.answer ? (
) : (
{t(language, 'search_ask_no_answer')}
)} ) : null} {state?.type === 'error' ? (
{t(language, 'search_ask_error')}
) : null} {state?.type === 'loading' ? (
) : null}
); } function AnswerBody(props: { answer: AskAnswerResult }) { const { answer } = props; const getSearchLinkProps = useSearchLink(); const language = useLanguage(); const [, setSearchState] = useSearch(); const onClose = () => { setSearchState(null); }; return ( <>
{answer.body}
{answer.followupQuestions.length > 0 ? (
{answer.followupQuestions.map((question) => ( path]:[stroke-opacity:0.64]', )} /> {question} ))}
) : null} {answer.sources.length > 0 ? (
{t(language, 'search_ask_sources')} {answer.sources.map((source) => ( {source.title} ))}
) : null} ); }