import React, { useMemo } from "react"; import { Paper, Text, Group, Stack, Loader, Box } from "@mantine/core"; import { IconSparkles, IconFileText } from "@tabler/icons-react"; import { Link } from "react-router-dom"; import { IAiSearchResponse } from "../services/ai-search-service.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts"; import { markdownToHtml } from "@docmost/editor-ext"; import DOMPurify from "dompurify"; import { useTranslation } from "react-i18next"; interface AiSearchResultProps { result?: IAiSearchResponse; isLoading?: boolean; streamingAnswer?: string; streamingSources?: any[]; } export function AiSearchResult({ result, isLoading, streamingAnswer = "", streamingSources = [], }: AiSearchResultProps) { const { t } = useTranslation(); // Use streaming data if available, otherwise fall back to result const answer = streamingAnswer || result?.answer || ""; const sources = streamingSources.length > 0 ? streamingSources : result?.sources || []; // Deduplicate sources by pageId, keeping the one with highest similarity const deduplicatedSources = useMemo(() => { if (!sources || sources.length === 0) return []; const pageMap = new Map(); sources.forEach((source) => { const existing = pageMap.get(source.pageId); if (!existing || source.similarity > existing.similarity) { pageMap.set(source.pageId, source); } }); return Array.from(pageMap.values()); }, [sources]); if (isLoading && !answer) { return ( {t("AI is thinking...")} ); } if (!answer && !isLoading) { return null; } return ( {t("AI Answer")} {isLoading && }
{deduplicatedSources.length > 0 && ( {t("Sources")} {deduplicatedSources.map((source) => ( {source.title} ))} )} ); }