From 5ecf4e3e9d683b63f6b9ce8d8ac90eb581b8dc81 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:00:29 +0530 Subject: [PATCH] refactor: remove unused comment-related state and components; streamline comment panel styling for improved responsiveness --- .../new-chat/[[...chat_id]]/page.tsx | 1 - .../atoms/chat/current-thread.atom.ts | 61 +------ .../assistant-ui/assistant-message.tsx | 149 +++++++----------- .../components/assistant-ui/thread.tsx | 8 +- .../comment-panel-container/types.ts | 4 +- .../comment-panel/comment-panel.tsx | 13 +- .../chat-comments/comment-panel/types.ts | 4 +- .../comment-trigger/comment-trigger.tsx | 36 ----- .../chat-comments/comment-trigger/types.ts | 6 - .../layout/ui/sidebar/InboxSidebar.tsx | 5 +- .../components/report-panel/report-panel.tsx | 3 - 11 files changed, 78 insertions(+), 212 deletions(-) delete mode 100644 surfsense_web/components/chat-comments/comment-trigger/comment-trigger.tsx delete mode 100644 surfsense_web/components/chat-comments/comment-trigger/types.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 2fb2527c1..439e50f79 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -405,7 +405,6 @@ export default function NewChatPage() { id: currentThread?.id ?? null, visibility: currentThread?.visibility ?? null, hasComments: currentThread?.has_comments ?? false, - addingCommentToMessageId: null, })); }, [currentThread, setCurrentThreadState]); diff --git a/surfsense_web/atoms/chat/current-thread.atom.ts b/surfsense_web/atoms/chat/current-thread.atom.ts index aee5bf7b4..d781df8d2 100644 --- a/surfsense_web/atoms/chat/current-thread.atom.ts +++ b/surfsense_web/atoms/chat/current-thread.atom.ts @@ -1,33 +1,17 @@ import { atom } from "jotai"; import type { ChatVisibility } from "@/lib/chat/thread-persistence"; -import { reportPanelAtom, reportPanelOpenAtom } from "./report-panel.atom"; - -// TODO: Update `hasComments` to true when the first comment is created on a thread. -// Currently it only updates on thread load. The gutter still works because -// `addingCommentToMessageId` keeps it open, but the state is technically stale. - -// TODO: Reset `addingCommentToMessageId` to null after a comment is successfully created. -// Currently it stays set until navigation or clicking another message's bubble. -// Not causing issues since panel visibility is driven by per-message comment count. - -// TODO: Consider calling `resetCurrentThreadAtom` when unmounting the chat page -// for explicit cleanup, though React navigation handles this implicitly. +import { reportPanelAtom } from "./report-panel.atom"; interface CurrentThreadState { id: number | null; visibility: ChatVisibility | null; hasComments: boolean; - addingCommentToMessageId: number | null; - /** Whether the right-side comments panel is collapsed (desktop only) */ - commentsCollapsed: boolean; } const initialState: CurrentThreadState = { id: null, visibility: null, hasComments: false, - addingCommentToMessageId: null, - commentsCollapsed: false, }; export const currentThreadAtom = atom(initialState); @@ -36,63 +20,22 @@ export const commentsEnabledAtom = atom( (get) => get(currentThreadAtom).visibility === "SEARCH_SPACE" ); -export const showCommentsGutterAtom = atom((get) => { - const thread = get(currentThreadAtom); - // Hide gutter if comments are collapsed - if (thread.commentsCollapsed) return false; - // Hide gutter if report panel is open (report panel takes the right side) - if (get(reportPanelOpenAtom)) return false; - return ( - thread.visibility === "SEARCH_SPACE" && - (thread.hasComments || thread.addingCommentToMessageId !== null) - ); -}); - -export const addingCommentToMessageIdAtom = atom( - (get) => get(currentThreadAtom).addingCommentToMessageId, - (get, set, messageId: number | null) => { - set(currentThreadAtom, { ...get(currentThreadAtom), addingCommentToMessageId: messageId }); - } -); - -// Setter atom for updating thread visibility export const setThreadVisibilityAtom = atom(null, (get, set, newVisibility: ChatVisibility) => { set(currentThreadAtom, { ...get(currentThreadAtom), visibility: newVisibility }); }); export const resetCurrentThreadAtom = atom(null, (_, set) => { set(currentThreadAtom, initialState); - // Also close the report panel when resetting the thread set(reportPanelAtom, { isOpen: false, reportId: null, title: null, wordCount: null }); }); -/** Atom to read whether comments panel is collapsed */ -export const commentsCollapsedAtom = atom((get) => get(currentThreadAtom).commentsCollapsed); - -/** Atom to toggle the comments collapsed state */ -export const toggleCommentsCollapsedAtom = atom(null, (get, set) => { - const current = get(currentThreadAtom); - set(currentThreadAtom, { ...current, commentsCollapsed: !current.commentsCollapsed }); -}); - -/** Atom to explicitly set the comments collapsed state */ -export const setCommentsCollapsedAtom = atom(null, (get, set, collapsed: boolean) => { - set(currentThreadAtom, { ...get(currentThreadAtom), commentsCollapsed: collapsed }); -}); - /** Target comment ID to scroll to (from URL navigation or inbox click) */ export const targetCommentIdAtom = atom(null); -/** Setter for target comment ID - also ensures comments are not collapsed */ -export const setTargetCommentIdAtom = atom(null, (get, set, commentId: number | null) => { - // Ensure comments are not collapsed when navigating to a comment - if (commentId !== null) { - set(currentThreadAtom, { ...get(currentThreadAtom), commentsCollapsed: false }); - } +export const setTargetCommentIdAtom = atom(null, (_, set, commentId: number | null) => { set(targetCommentIdAtom, commentId); }); -/** Clear target after navigation completes */ export const clearTargetCommentIdAtom = atom(null, (_, set) => { set(targetCommentIdAtom, null); }); diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index 5cdd287de..b665d44d2 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -6,13 +6,11 @@ import { useAssistantState, useMessage, } from "@assistant-ui/react"; -import { useAtom, useAtomValue } from "jotai"; +import { useAtomValue } from "jotai"; import { CheckIcon, CopyIcon, DownloadIcon, MessageSquare, RefreshCwIcon } from "lucide-react"; import type { FC } from "react"; import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { - addingCommentToMessageIdAtom, - commentsCollapsedAtom, commentsEnabledAtom, targetCommentIdAtom, } from "@/atoms/chat/current-thread.atom"; @@ -26,7 +24,6 @@ import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { CommentPanelContainer } from "@/components/chat-comments/comment-panel-container/comment-panel-container"; import { CommentSheet } from "@/components/chat-comments/comment-sheet/comment-sheet"; -import { CommentTrigger } from "@/components/chat-comments/comment-trigger/comment-trigger"; import { useComments } from "@/hooks/use-comments"; import { useMediaQuery } from "@/hooks/use-media-query"; import { cn } from "@/lib/utils"; @@ -96,20 +93,17 @@ function parseMessageId(assistantUiMessageId: string | undefined): number | null } export const AssistantMessage: FC = () => { - const [messageHeight, setMessageHeight] = useState(undefined); const [isSheetOpen, setIsSheetOpen] = useState(false); + const [isInlineOpen, setIsInlineOpen] = useState(false); const messageRef = useRef(null); + const commentPanelRef = useRef(null); + const commentTriggerRef = useRef(null); const messageId = useAssistantState(({ message }) => message?.id); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const dbMessageId = parseMessageId(messageId); const commentsEnabled = useAtomValue(commentsEnabledAtom); - const commentsCollapsed = useAtomValue(commentsCollapsedAtom); - const [addingCommentToMessageId, setAddingCommentToMessageId] = useAtom( - addingCommentToMessageIdAtom - ); - // Screen size detection for responsive comment UI - // Mobile: < 768px (bottom sheet), Medium: 768px - 1024px (right sheet), Desktop: >= 1024px (inline panel) + // Desktop: >= 1024px (inline expandable), Medium: 768px-1023px (right sheet), Mobile: <768px (bottom sheet) const isMediumScreen = useMediaQuery("(min-width: 768px) and (max-width: 1023px)"); const isDesktop = useMediaQuery("(min-width: 1024px)"); @@ -122,10 +116,8 @@ export const AssistantMessage: FC = () => { enabled: !!dbMessageId, }); - // Target comment navigation - read target from global atom const targetCommentId = useAtomValue(targetCommentIdAtom); - // Check if target comment belongs to this message (including replies) const hasTargetComment = useMemo(() => { if (!targetCommentId || !commentsData?.comments) return false; return commentsData.comments.some( @@ -135,27 +127,35 @@ export const AssistantMessage: FC = () => { const commentCount = commentsData?.total_count ?? 0; const hasComments = commentCount > 0; - const isAddingComment = dbMessageId !== null && addingCommentToMessageId === dbMessageId; - const showCommentPanel = hasComments || isAddingComment; - const handleToggleAddComment = () => { - if (!dbMessageId) return; - setAddingCommentToMessageId(isAddingComment ? null : dbMessageId); - }; - - const handleCommentTriggerClick = () => { - setIsSheetOpen(true); - }; + const showCommentTrigger = searchSpaceId && commentsEnabled && !isMessageStreaming && dbMessageId; + // Close floating panel when clicking outside (but not on portaled popover/dropdown content) useEffect(() => { - if (!messageRef.current) return; - const el = messageRef.current; - const update = () => setMessageHeight(el.offsetHeight); - update(); - const observer = new ResizeObserver(update); - observer.observe(el); - return () => observer.disconnect(); - }, []); + if (!isInlineOpen) return; + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as Element; + if ( + commentPanelRef.current?.contains(target) || + commentTriggerRef.current?.contains(target) || + target.closest?.("[data-radix-popper-content-wrapper]") + ) return; + setIsInlineOpen(false); + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [isInlineOpen]); + + // Auto-open floating panel on desktop when this message has the target comment + useEffect(() => { + if (hasTargetComment && isDesktop && commentsLoaded) { + setIsInlineOpen(true); + const timeoutId = setTimeout(() => { + messageRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); + }, 100); + return () => clearTimeout(timeoutId); + } + }, [hasTargetComment, isDesktop, commentsLoaded]); // Auto-open sheet on mobile/tablet when this message has the target comment useEffect(() => { @@ -164,20 +164,6 @@ export const AssistantMessage: FC = () => { } }, [hasTargetComment, isDesktop, commentsLoaded]); - // Scroll message into view when it contains target comment (desktop) - useEffect(() => { - if (hasTargetComment && isDesktop && commentsLoaded && messageRef.current) { - // Small delay to ensure DOM is ready after comments render - const timeoutId = setTimeout(() => { - messageRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); - }, 100); - return () => clearTimeout(timeoutId); - } - }, [hasTargetComment, isDesktop, commentsLoaded]); - - const showCommentTrigger = searchSpaceId && commentsEnabled && !isMessageStreaming && dbMessageId; - - // Determine sheet side based on screen size const sheetSide = isMediumScreen ? "right" : "bottom"; return ( @@ -186,54 +172,23 @@ export const AssistantMessage: FC = () => { className="aui-assistant-message-root group fade-in slide-in-from-bottom-1 relative mx-auto w-full max-w-(--thread-max-width) animate-in py-3 duration-150" data-role="assistant" > - - - {/* Desktop comment panel - only on lg screens and above, hidden when collapsed */} - {searchSpaceId && commentsEnabled && !isMessageStreaming && !commentsCollapsed && ( -
-
- {!hasComments && ( - - )} - - {showCommentPanel && dbMessageId && ( -
- -
- )} -
-
- )} - - {/* Mobile & Medium screen comment trigger - shown below lg breakpoint */} - {showCommentTrigger && !isDesktop && ( -
+ {/* Comment trigger — right-aligned, just below user query on all screen sizes */} + {showCommentTrigger && ( +
)} - {/* Comment sheet - bottom for mobile, right for medium screens */} + {/* Desktop floating comment panel — overlays on top of chat content */} + {showCommentTrigger && isDesktop && isInlineOpen && dbMessageId && ( +
+ +
+ )} + + + + {/* Comment sheet — bottom for mobile, right for medium screens */} {showCommentTrigger && !isDesktop && ( = ({ messageThinkingSteps = new Map() }) => }; const ThreadContent: FC = () => { - const showGutter = useAtomValue(showCommentsGutterAtom); - return ( { > thread.isEmpty}> diff --git a/surfsense_web/components/chat-comments/comment-panel-container/types.ts b/surfsense_web/components/chat-comments/comment-panel-container/types.ts index e579f8403..40f769ffe 100644 --- a/surfsense_web/components/chat-comments/comment-panel-container/types.ts +++ b/surfsense_web/components/chat-comments/comment-panel-container/types.ts @@ -2,6 +2,6 @@ export interface CommentPanelContainerProps { messageId: number; isOpen: boolean; maxHeight?: number; - /** Variant for responsive styling - desktop shows border/bg, mobile is plain */ - variant?: "desktop" | "mobile"; + /** Variant for responsive styling - desktop shows border/bg, mobile is plain, inline fits within message width */ + variant?: "desktop" | "mobile" | "inline"; } diff --git a/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx index c72c77f65..5408df35c 100644 --- a/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx +++ b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx @@ -40,13 +40,15 @@ export function CommentPanel({ }; const isMobile = variant === "mobile"; + const isInline = variant === "inline"; if (isLoading) { return (
@@ -65,8 +67,13 @@ export function CommentPanel({ return (
{hasThreads && (
diff --git a/surfsense_web/components/chat-comments/comment-panel/types.ts b/surfsense_web/components/chat-comments/comment-panel/types.ts index 5613d6144..946392bd2 100644 --- a/surfsense_web/components/chat-comments/comment-panel/types.ts +++ b/surfsense_web/components/chat-comments/comment-panel/types.ts @@ -12,6 +12,6 @@ export interface CommentPanelProps { onDeleteComment: (commentId: number) => void; isSubmitting?: boolean; maxHeight?: number; - /** Variant for responsive styling - desktop shows border/bg, mobile is plain */ - variant?: "desktop" | "mobile"; + /** Variant for responsive styling - desktop shows border/bg, mobile is plain, inline fits within message width */ + variant?: "desktop" | "mobile" | "inline"; } diff --git a/surfsense_web/components/chat-comments/comment-trigger/comment-trigger.tsx b/surfsense_web/components/chat-comments/comment-trigger/comment-trigger.tsx deleted file mode 100644 index 60ea97ac6..000000000 --- a/surfsense_web/components/chat-comments/comment-trigger/comment-trigger.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { MessageSquarePlus } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import type { CommentTriggerProps } from "./types"; - -export function CommentTrigger({ commentCount, isOpen, onClick, disabled }: CommentTriggerProps) { - const hasComments = commentCount > 0; - - return ( - - ); -} diff --git a/surfsense_web/components/chat-comments/comment-trigger/types.ts b/surfsense_web/components/chat-comments/comment-trigger/types.ts deleted file mode 100644 index 0985da9ed..000000000 --- a/surfsense_web/components/chat-comments/comment-trigger/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface CommentTriggerProps { - commentCount: number; - isOpen: boolean; - onClick: () => void; - disabled?: boolean; -} diff --git a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx index de5218ffa..7c57fdef1 100644 --- a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx @@ -23,7 +23,7 @@ import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon"; -import { setCommentsCollapsedAtom, setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom"; +import { setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom"; import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; @@ -168,7 +168,6 @@ export function InboxSidebar({ const isMobile = !useMediaQuery("(min-width: 640px)"); const searchSpaceId = params?.search_space_id ? Number(params.search_space_id) : null; - const [, setCommentsCollapsed] = useAtom(setCommentsCollapsedAtom); const [, setTargetCommentId] = useAtom(setTargetCommentIdAtom); const [searchQuery, setSearchQuery] = useState(""); @@ -831,11 +830,9 @@ export function InboxSidebar({ className="h-8 w-8 rounded-full" onClick={() => { if (isDocked) { - setCommentsCollapsed(false); onDockedChange(false); onOpenChange(false); } else { - setCommentsCollapsed(true); onDockedChange(true); } }} diff --git a/surfsense_web/components/report-panel/report-panel.tsx b/surfsense_web/components/report-panel/report-panel.tsx index 4c2640582..a9318353d 100644 --- a/surfsense_web/components/report-panel/report-panel.tsx +++ b/surfsense_web/components/report-panel/report-panel.tsx @@ -538,9 +538,6 @@ function MobileReportDrawer() { * * On desktop (lg+): Renders as a right-side split panel (flex sibling to the chat thread) * On mobile/tablet: Renders as a Vaul bottom drawer - * - * When open on desktop, the comments gutter is automatically suppressed - * (handled via showCommentsGutterAtom in current-thread.atom.ts) */ export function ReportPanel() { const panelState = useAtomValue(reportPanelAtom);