diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 9e3f55c97..86d7082ee 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -7,6 +7,7 @@ import { useParams, usePathname, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useTheme } from "next-themes"; import { useCallback, useMemo, useState } from "react"; +import { toast } from "sonner"; import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; @@ -21,7 +22,7 @@ import { } from "@/components/ui/dialog"; import { useInbox } from "@/hooks/use-inbox"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; -import { deleteThread, fetchThreads } from "@/lib/chat/thread-persistence"; +import { deleteThread, fetchThreads, updateThread } from "@/lib/chat/thread-persistence"; import { cleanupElectric } from "@/lib/electric/client"; import { resetUser, trackLogout } from "@/lib/posthog/events"; import { cacheKeys } from "@/lib/query-client/cache-keys"; @@ -56,6 +57,7 @@ export function LayoutDataProvider({ }: LayoutDataProviderProps) { const t = useTranslations("dashboard"); const tCommon = useTranslations("common"); + const tSidebar = useTranslations("sidebar"); const router = useRouter(); const params = useParams(); const pathname = usePathname(); @@ -154,6 +156,7 @@ export function LayoutDataProvider({ url: `/dashboard/${searchSpaceId}/new-chat/${thread.id}`, visibility: thread.visibility, isOwnThread: thread.is_own_thread, + archived: thread.archived, }; // Split based on visibility, not ownership: @@ -304,6 +307,28 @@ export function LayoutDataProvider({ setShowDeleteChatDialog(true); }, []); + const handleChatArchive = useCallback( + async (chat: ChatItem) => { + const newArchivedState = !chat.archived; + const successMessage = newArchivedState + ? tSidebar("chat_archived") || "Chat archived" + : tSidebar("chat_unarchived") || "Chat restored"; + + try { + await updateThread(chat.id, { archived: newArchivedState }); + toast.success(successMessage); + // Invalidate queries to refresh UI (React Query will only refetch active queries) + queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] }); + queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] }); + queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] }); + } catch (error) { + console.error("Error archiving thread:", error); + toast.error(tSidebar("error_archiving_chat") || "Failed to archive chat"); + } + }, + [queryClient, searchSpaceId, tSidebar] + ); + const handleSettings = useCallback(() => { router.push(`/dashboard/${searchSpaceId}/settings`); }, [router, searchSpaceId]); @@ -391,6 +416,7 @@ export function LayoutDataProvider({ onNewChat={handleNewChat} onChatSelect={handleChatSelect} onChatDelete={handleChatDelete} + onChatArchive={handleChatArchive} onViewAllSharedChats={handleViewAllSharedChats} onViewAllPrivateChats={handleViewAllPrivateChats} user={{ diff --git a/surfsense_web/components/layout/types/layout.types.ts b/surfsense_web/components/layout/types/layout.types.ts index 3eac64e60..c58fb5b54 100644 --- a/surfsense_web/components/layout/types/layout.types.ts +++ b/surfsense_web/components/layout/types/layout.types.ts @@ -30,6 +30,7 @@ export interface ChatItem { isActive?: boolean; visibility?: "PRIVATE" | "SEARCH_SPACE"; isOwnThread?: boolean; + archived?: boolean; } export interface PageUsage { diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index b68719cde..37e4d8ada 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -26,6 +26,7 @@ interface LayoutShellProps { onNewChat: () => void; onChatSelect: (chat: ChatItem) => void; onChatDelete?: (chat: ChatItem) => void; + onChatArchive?: (chat: ChatItem) => void; onViewAllSharedChats?: () => void; onViewAllPrivateChats?: () => void; user: User; @@ -59,6 +60,7 @@ export function LayoutShell({ onNewChat, onChatSelect, onChatDelete, + onChatArchive, onViewAllSharedChats, onViewAllPrivateChats, user, @@ -107,6 +109,7 @@ export function LayoutShell({ onNewChat={onNewChat} onChatSelect={onChatSelect} onChatDelete={onChatDelete} + onChatArchive={onChatArchive} onViewAllSharedChats={onViewAllSharedChats} onViewAllPrivateChats={onViewAllPrivateChats} user={user} @@ -155,6 +158,7 @@ export function LayoutShell({ onNewChat={onNewChat} onChatSelect={onChatSelect} onChatDelete={onChatDelete} + onChatArchive={onChatArchive} onViewAllSharedChats={onViewAllSharedChats} onViewAllPrivateChats={onViewAllPrivateChats} user={user} diff --git a/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx b/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx index 5dd9c2cfa..12def741b 100644 --- a/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx +++ b/surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx @@ -1,12 +1,13 @@ "use client"; -import { MessageSquare, MoreHorizontal } from "lucide-react"; +import { ArchiveIcon, MessageSquare, MoreHorizontal, RotateCcwIcon, Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; @@ -14,11 +15,13 @@ import { cn } from "@/lib/utils"; interface ChatListItemProps { name: string; isActive?: boolean; + archived?: boolean; onClick?: () => void; + onArchive?: () => void; onDelete?: () => void; } -export function ChatListItem({ name, isActive, onClick, onDelete }: ChatListItemProps) { +export function ChatListItem({ name, isActive, archived, onClick, onArchive, onDelete }: ChatListItemProps) { const t = useTranslations("sidebar"); return ( @@ -48,15 +51,39 @@ export function ChatListItem({ name, isActive, onClick, onDelete }: ChatListItem - { - e.stopPropagation(); - onDelete?.(); - }} - className="text-destructive focus:text-destructive" - > - {t("delete")} - + {onArchive && ( + { + e.stopPropagation(); + onArchive(); + }} + > + {archived ? ( + <> + + {t("unarchive") || "Restore"} + + ) : ( + <> + + {t("archive") || "Archive"} + + )} + + )} + {onArchive && onDelete && } + {onDelete && ( + { + e.stopPropagation(); + onDelete(); + }} + className="text-destructive focus:text-destructive" + > + + {t("delete")} + + )} diff --git a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx index 7dd01d75a..85f907611 100644 --- a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx @@ -25,6 +25,7 @@ interface MobileSidebarProps { onNewChat: () => void; onChatSelect: (chat: ChatItem) => void; onChatDelete?: (chat: ChatItem) => void; + onChatArchive?: (chat: ChatItem) => void; onViewAllSharedChats?: () => void; onViewAllPrivateChats?: () => void; user: User; @@ -64,6 +65,7 @@ export function MobileSidebar({ onNewChat, onChatSelect, onChatDelete, + onChatArchive, onViewAllSharedChats, onViewAllPrivateChats, user, @@ -141,6 +143,7 @@ export function MobileSidebar({ }} onChatSelect={handleChatSelect} onChatDelete={onChatDelete} + onChatArchive={onChatArchive} onViewAllSharedChats={onViewAllSharedChats} onViewAllPrivateChats={onViewAllPrivateChats} user={user} diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index f3452749f..d05f21096 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -27,6 +27,7 @@ interface SidebarProps { onNewChat: () => void; onChatSelect: (chat: ChatItem) => void; onChatDelete?: (chat: ChatItem) => void; + onChatArchive?: (chat: ChatItem) => void; onViewAllSharedChats?: () => void; onViewAllPrivateChats?: () => void; user: User; @@ -52,6 +53,7 @@ export function Sidebar({ onNewChat, onChatSelect, onChatDelete, + onChatArchive, onViewAllSharedChats, onViewAllPrivateChats, user, @@ -175,7 +177,9 @@ export function Sidebar({ key={chat.id} name={chat.name} isActive={chat.id === activeChatId} + archived={chat.archived} onClick={() => onChatSelect(chat)} + onArchive={() => onChatArchive?.(chat)} onDelete={() => onChatDelete?.(chat)} /> ))} @@ -216,7 +220,9 @@ export function Sidebar({ key={chat.id} name={chat.name} isActive={chat.id === activeChatId} + archived={chat.archived} onClick={() => onChatSelect(chat)} + onArchive={() => onChatArchive?.(chat)} onDelete={() => onChatDelete?.(chat)} /> ))} diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx index 5c8c89152..28c359e64 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarHeader.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronsUpDown, ScrollText, Settings, Users } from "lucide-react"; +import { ChevronsUpDown, Logs, Settings, Users } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; @@ -57,7 +57,7 @@ export function SidebarHeader({ {t("manage_members")} router.push(`/dashboard/${searchSpaceId}/logs`)}> - + {t("logs")}