From 4dd587131837c43099afc1a282a2db7c00738ee5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 15 May 2026 03:56:01 +0530 Subject: [PATCH] refactor: enhance citation components with mobile support and improved styling for better user experience --- .../assistant-ui/inline-citation.tsx | 98 ++++++++++++++----- .../citation-panel/citation-panel.tsx | 29 ++---- .../citation/citation-hover-popover.tsx | 6 ++ .../components/tool-ui/citation/citation.tsx | 12 +-- 4 files changed, 92 insertions(+), 53 deletions(-) diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index eba617c15..28b4c43c3 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -5,13 +5,22 @@ import { useSetAtom } from "jotai"; import { ExternalLink, FileText } from "lucide-react"; import dynamic from "next/dynamic"; import type { FC } from "react"; +import { useState } from "react"; import { openCitationPanelAtom } from "@/atoms/citation/citation-panel.atom"; import { useCitationMetadata } from "@/components/assistant-ui/citation-metadata-context"; import { Citation } from "@/components/tool-ui/citation"; import { CitationHoverPopover } from "@/components/tool-ui/citation/citation-hover-popover"; import { Button } from "@/components/ui/button"; +import { + Drawer, + DrawerContent, + DrawerHandle, + DrawerHeader, + DrawerTitle, +} from "@/components/ui/drawer"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { useMediaQuery } from "@/hooks/use-media-query"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { cacheKeys } from "@/lib/query-client/cache-keys"; @@ -51,7 +60,7 @@ export const InlineCitation: FC = ({ chunkId, isDocsChunk = @@ -78,7 +87,7 @@ const NumericChunkCitation: FC<{ chunkId: number }> = ({ chunkId }) => { type="button" variant="ghost" onClick={() => openCitationPanel({ chunkId })} - className="ml-0.5 h-5 min-w-5 cursor-pointer rounded-md bg-muted/60 px-1.5 text-[11px] font-medium text-muted-foreground align-baseline shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" + className="ml-0.5 inline-flex h-5 min-w-5 items-center justify-center gap-0.5 rounded-md bg-popover px-1.5 text-[11px] font-medium text-popover-foreground/80 align-baseline" title={`View source chunk #${chunkId}`} aria-label={`View cited chunk ${chunkId}`} > @@ -88,36 +97,77 @@ const NumericChunkCitation: FC<{ chunkId: number }> = ({ chunkId }) => { }; const SurfsenseDocCitation: FC<{ chunkId: number }> = ({ chunkId }) => { + const isTouchLike = useMediaQuery("(hover: none), (pointer: coarse)"); + const [mobilePreviewOpen, setMobilePreviewOpen] = useState(false); + const docQuery = useSurfsenseDocPreviewQuery(chunkId, mobilePreviewOpen); + + const handleMobileClick = () => { + setMobilePreviewOpen(true); + }; + return ( - ( - + )} + > + + + + - - doc - - )} - > - - + + + Surfsense documentation + + + + + ); }; -const SurfsenseDocPreview: FC<{ chunkId: number }> = ({ chunkId }) => { - const { data, isLoading, error } = useQuery({ +function useSurfsenseDocPreviewQuery(chunkId: number, enabled = true) { + return useQuery({ queryKey: cacheKeys.documents.byChunk(`doc-${chunkId}`), queryFn: () => documentsApiService.getSurfsenseDocByChunk(chunkId), staleTime: 5 * 60 * 1000, + enabled, }); +} + +type SurfsenseDocPreviewQuery = ReturnType; + +const SurfsenseDocPreview: FC<{ chunkId: number }> = ({ chunkId }) => { + const query = useSurfsenseDocPreviewQuery(chunkId); + + return ; +}; + +const SurfsenseDocPreviewContent: FC<{ + chunkId: number; + query: SurfsenseDocPreviewQuery; + contentClassName?: string; +}> = ({ chunkId, query, contentClassName = "max-h-72" }) => { + const { data, isLoading, error } = query; const citedChunk = data?.chunks.find((c) => c.id === chunkId) ?? data?.chunks[0]; @@ -142,7 +192,7 @@ const SurfsenseDocPreview: FC<{ chunkId: number }> = ({ chunkId }) => { )} -
+
{isLoading && (
diff --git a/surfsense_web/components/citation-panel/citation-panel.tsx b/surfsense_web/components/citation-panel/citation-panel.tsx index c588bb93d..715facbde 100644 --- a/surfsense_web/components/citation-panel/citation-panel.tsx +++ b/surfsense_web/components/citation-panel/citation-panel.tsx @@ -50,14 +50,6 @@ export const CitationPanelContent: FC = ({ chunkId, o const totalChunks = data?.total_chunks ?? data?.chunks.length ?? 0; const startIndex = data?.chunk_start_index ?? 0; - const citedIndexInWindow = data - ? Math.max( - 0, - data.chunks.findIndex((c) => c.id === chunkId) - ) - : 0; - const shownAbove = citedIndexInWindow; - const shownBelow = data ? Math.max(0, data.chunks.length - 1 - citedIndexInWindow) : 0; const hasMoreAbove = startIndex > 0; const hasMoreBelow = data ? startIndex + data.chunks.length < totalChunks : false; @@ -117,18 +109,16 @@ export const CitationPanelContent: FC = ({ chunkId, o {data?.title ?? (isLoading ? "Loading…" : `Chunk #${chunkId}`)}

-
- Chunk #{chunkId} - {totalChunks > 0 && · {totalChunks} chunks} +
+ {totalChunks > 0 && {totalChunks} chunks}
{isLoading && ( -
- - Loading citation… +
+
)} @@ -163,14 +153,14 @@ export const CitationPanelContent: FC = ({ chunkId, o - {isCited ? "Cited chunk" : `Chunk #${chunk.id}`} + Chunk #{chunk.id} {isCited && ( - #{chunk.id} + Cited chunk )}
@@ -191,10 +181,7 @@ export const CitationPanelContent: FC = ({ chunkId, o
{!isLoading && !error && data && ( -
-
- Showing {shownAbove} above · cited · {shownBelow} below -
+
{(hasMoreAbove || hasMoreBelow) && !expanded && ( )} > -
+
{iconElement} {domain}