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")}