diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx index 903d6a46e..7cb1da9d5 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx @@ -76,10 +76,10 @@ export function DocumentsFilters({ )} - +
{/* Search input */} -
+
= 2) { + return (parts[0][0] + parts[1][0]).toUpperCase(); + } + return name.slice(0, 2).toUpperCase(); +} + function StatusIndicator({ status }: { status?: DocumentStatus }) { const state = status?.state ?? "ready"; @@ -145,45 +157,6 @@ function formatAbsoluteDate(dateStr: string): string { }); } -function DocumentNameTooltip({ doc, className }: { doc: Document; className?: string }) { - const textRef = useRef(null); - const [isTruncated, setIsTruncated] = useState(false); - - useEffect(() => { - const checkTruncation = () => { - if (textRef.current) { - setIsTruncated(textRef.current.scrollWidth > textRef.current.clientWidth); - } - }; - checkTruncation(); - window.addEventListener("resize", checkTruncation); - return () => window.removeEventListener("resize", checkTruncation); - }, []); - - return ( - - - - {doc.title} - - - -
- {isTruncated &&

{doc.title}

} -

- Owner:{" "} - {doc.created_by_name || doc.created_by_email || "—"} -

-

- Created:{" "} - {formatAbsoluteDate(doc.created_at)} -

-
-
-
- ); -} - function SortableHeader({ children, sortKey, @@ -217,71 +190,6 @@ function SortableHeader({ ); } -function RowContextMenu({ - doc, - children, - onPreview, - onDelete, - searchSpaceId, -}: { - doc: Document; - children: React.ReactNode; - onPreview: (doc: Document) => void; - onDelete: (doc: Document) => void; - searchSpaceId: string; -}) { - const openEditor = useSetAtom(openEditorPanelAtom); - - const isEditable = EDITABLE_DOCUMENT_TYPES.includes( - doc.document_type as (typeof EDITABLE_DOCUMENT_TYPES)[number] - ); - const isBeingProcessed = doc.status?.state === "pending" || doc.status?.state === "processing"; - const isFileFailed = doc.document_type === "FILE" && doc.status?.state === "failed"; - const shouldShowDelete = !NON_DELETABLE_DOCUMENT_TYPES.includes( - doc.document_type as (typeof NON_DELETABLE_DOCUMENT_TYPES)[number] - ); - const isEditDisabled = isBeingProcessed || isFileFailed; - const isDeleteDisabled = isBeingProcessed; - - return ( - - {children} - - onPreview(doc)}> - - Preview - - {isEditable && ( - { - if (!isEditDisabled) { - openEditor({ - documentId: doc.id, - searchSpaceId: Number(searchSpaceId), - title: doc.title, - }); - } - }} - disabled={isEditDisabled} - > - - Edit - - )} - {shouldShowDelete && ( - !isDeleteDisabled && onDelete(doc)} - disabled={isDeleteDisabled} - > - - Delete - - )} - - - ); -} - function MobileCardWrapper({ onLongPress, children, @@ -370,6 +278,22 @@ export function DocumentsTableShell({ const [bulkDeleteConfirmOpen, setBulkDeleteConfirmOpen] = useState(false); const [isBulkDeleting, setIsBulkDeleting] = useState(false); const openEditor = useSetAtom(openEditorPanelAtom); + const [openMenuDocId, setOpenMenuDocId] = useState(null); + + const { data: members } = useAtomValue(membersAtom); + const memberMap = useMemo(() => { + const map = new Map(); + if (members) { + for (const m of members) { + map.set(m.user_id, { + name: m.user_display_name || m.user_email || "Unknown", + email: m.user_email || undefined, + avatarUrl: m.user_avatar_url || undefined, + }); + } + } + return map; + }, [members]); const desktopSentinelRef = useRef(null); const mobileSentinelRef = useRef(null); @@ -549,7 +473,20 @@ export function DocumentsTableShell({ }, [deletableSelectedIds, bulkDeleteDocuments, deleteDocument]); return ( -
+
+ {/* Floating bulk delete pill */} + {hasDeletableSelection && ( +
+ +
+ )} {/* Desktop Table View */}
@@ -581,23 +518,10 @@ export function DocumentsTableShell({ - - {hasDeletableSelection ? ( - - - - - Delete {deletableSelectedIds.length} selected - - ) : ( - Status - )} + + + + @@ -622,7 +546,7 @@ export function DocumentsTableShell({ - + @@ -675,6 +599,17 @@ export function DocumentsTableShell({ {sorted.map((doc) => { const isMentioned = mentionedDocIds?.has(doc.id) ?? false; const canInteract = isSelectable(doc); + const isBeingProcessed = + doc.status?.state === "pending" || doc.status?.state === "processing"; + const isFileFailed = + doc.document_type === "FILE" && doc.status?.state === "failed"; + const isEditable = EDITABLE_DOCUMENT_TYPES.includes( + doc.document_type as (typeof EDITABLE_DOCUMENT_TYPES)[number] + ); + const shouldShowDelete = !NON_DELETABLE_DOCUMENT_TYPES.includes( + doc.document_type as (typeof NON_DELETABLE_DOCUMENT_TYPES)[number] + ); + const isMenuOpen = openMenuDocId === doc.id; const handleRowToggle = () => { if (canInteract && onToggleChatMention) { onToggleChatMention(doc, isMentioned); @@ -690,56 +625,149 @@ export function DocumentsTableShell({ handleRowToggle(); }; return ( - - e.stopPropagation()} > - e.stopPropagation()} - > -
- handleRowToggle()} - disabled={!canInteract} - aria-label={isMentioned ? "Remove from chat" : "Add to chat"} - className={`border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary ${!canInteract ? "opacity-40 cursor-not-allowed" : ""}`} - /> +
+ {(() => { + const state = doc.status?.state ?? "ready"; + if (state === "pending" || state === "processing") { + return ; + } + if (state === "failed") { + return ( + <> + + + + + handleRowToggle()} + aria-label={isMentioned ? "Remove from chat" : "Add to chat"} + className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary" + /> + + + ); + } + return ( + handleRowToggle()} + aria-label={isMentioned ? "Remove from chat" : "Add to chat"} + className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary" + /> + ); + })()} +
+ + + + {doc.title} + + + + + {getDocumentTypeIcon(doc.document_type, "h-4 w-4")} + + + e.stopPropagation()} + > +
+ {(() => { + const member = doc.created_by_id + ? memberMap.get(doc.created_by_id) + : null; + const displayName = + member?.name || + doc.created_by_name || + doc.created_by_email || + "Unknown"; + const avatarUrl = member?.avatarUrl; + const email = member?.email || doc.created_by_email || displayName; + return ( + + + + + {avatarUrl && ( + + )} + + {getInitials(displayName)} + + + + + {email} + + ); + })()} +
+ setOpenMenuDocId(open ? doc.id : null)} + > + + + + + handleViewDocument(doc)}> + + Preview + + {isEditable && ( + { + if (!(isBeingProcessed || isFileFailed)) { + openEditor({ + documentId: doc.id, + searchSpaceId: Number(searchSpaceId), + title: doc.title, + }); + } + }} + disabled={isBeingProcessed || isFileFailed} + > + + Edit + + )} + {shouldShowDelete && ( + !isBeingProcessed && setDeleteDoc(doc)} + disabled={isBeingProcessed} + className="" + > + + Delete + + )} + +
- - - - - - - - - {getDocumentTypeIcon(doc.document_type, "h-4 w-4")} - - - - {getDocumentTypeLabel(doc.document_type)} - - - - - - -
- + + + ); })} @@ -759,10 +787,7 @@ export function DocumentsTableShell({
-
- - -
+ ))} @@ -808,25 +833,11 @@ export function DocumentsTableShell({ ref={mobileScrollRef} className="md:hidden divide-y divide-border/50 flex-1 overflow-auto" > - {hasDeletableSelection && ( -
- - {deletableSelectedIds.length} selected - - -
- )} {sorted.map((doc) => { const isMentioned = mentionedDocIds?.has(doc.id) ?? false; - const canInteract = isSelectable(doc); + const statusState = doc.status?.state ?? "ready"; + const showCheckbox = statusState === "ready"; + const canInteract = showCheckbox; const handleCardClick = (e?: React.MouseEvent) => { if (e && (e.ctrlKey || e.metaKey)) { e.preventDefault(); @@ -856,24 +867,24 @@ export function DocumentsTableShell({ /> )}
- - handleCardClick()} - disabled={!canInteract} - aria-label={isMentioned ? "Remove from chat" : "Add to chat"} - className={`border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary shrink-0 ${!canInteract ? "opacity-40 cursor-not-allowed" : ""}`} - /> + + {showCheckbox ? ( + handleCardClick()} + aria-label={isMentioned ? "Remove from chat" : "Add to chat"} + className="border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary" + /> + ) : ( + + )}
{doc.title}
-
- - {getDocumentTypeIcon(doc.document_type, "h-4 w-4")} - - -
+ + {getDocumentTypeIcon(doc.document_type, "h-4 w-4")} +
diff --git a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx index d58f7422d..ddf4746aa 100644 --- a/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx +++ b/surfsense_web/components/public-chat-snapshots/public-chat-snapshot-row.tsx @@ -1,8 +1,8 @@ "use client"; import { Check, Copy, ExternalLink, MessageSquare, Trash2 } from "lucide-react"; -import Image from "next/image"; import { useCallback, useRef, useState } from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -158,21 +158,14 @@ export function PublicChatSnapshotRow({
- {member.avatarUrl ? ( - - ) : ( -
- - {getInitials(member.name)} - -
- )} + + {member.avatarUrl && ( + + )} + + {getInitials(member.name)} + + {member.name} diff --git a/surfsense_web/components/settings/image-model-manager.tsx b/surfsense_web/components/settings/image-model-manager.tsx index db7bf5f60..0b28c9a7f 100644 --- a/surfsense_web/components/settings/image-model-manager.tsx +++ b/surfsense_web/components/settings/image-model-manager.tsx @@ -13,9 +13,9 @@ import { Trash2, Wand2, } from "lucide-react"; -import Image from "next/image"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { createImageGenConfigMutationAtom, deleteImageGenConfigMutationAtom, @@ -498,21 +498,14 @@ export function ImageModelManager({ searchSpaceId }: ImageModelManagerProps) {
- {member.avatarUrl ? ( - - ) : ( -
- - {getInitials(member.name)} - -
- )} + + {member.avatarUrl && ( + + )} + + {getInitials(member.name)} + + {member.name} diff --git a/surfsense_web/components/settings/model-config-manager.tsx b/surfsense_web/components/settings/model-config-manager.tsx index b23e2dded..f4a584942 100644 --- a/surfsense_web/components/settings/model-config-manager.tsx +++ b/surfsense_web/components/settings/model-config-manager.tsx @@ -12,8 +12,8 @@ import { Trash2, Wand2, } from "lucide-react"; -import Image from "next/image"; import { useCallback, useMemo, useState } from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; import { createNewLLMConfigMutationAtom, @@ -431,21 +431,14 @@ export function ModelConfigManager({ searchSpaceId }: ModelConfigManagerProps) {
- {member.avatarUrl ? ( - - ) : ( -
- - {getInitials(member.name)} - -
- )} + + {member.avatarUrl && ( + + )} + + {getInitials(member.name)} + + {member.name}