diff --git a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx b/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx deleted file mode 100644 index 1d00ef01a..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/chats/chats-client.tsx +++ /dev/null @@ -1,458 +0,0 @@ -"use client"; - -import { format } from "date-fns"; -import { useAtom, useAtomValue } from "jotai"; -import { - Calendar, - ExternalLink, - MessageCircleMore, - MoreHorizontal, - Search, - Tag, - Trash2, -} from "lucide-react"; -import { AnimatePresence, motion, type Variants } from "motion/react"; -import { useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; -import { deleteChatMutationAtom } from "@/atoms/chats/chat-mutation.atoms"; -import { chatsAtom } from "@/atoms/chats/chat-query.atoms"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Input } from "@/components/ui/input"; -import { - Pagination, - PaginationContent, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/ui/pagination"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; - -export interface Chat { - created_at: string; - id: number; - type: "QNA"; - title: string; - search_space_id: number; - state_version: number; -} - -export interface ChatDetails { - type: "QNA"; - title: string; - initial_connectors: string[]; - messages: any[]; - created_at: string; - id: number; - search_space_id: number; - state_version: number; -} - -interface ChatsPageClientProps { - searchSpaceId: string; -} - -const pageVariants: Variants = { - initial: { opacity: 0 }, - enter: { opacity: 1, transition: { duration: 0.3, ease: "easeInOut" } }, - exit: { opacity: 0, transition: { duration: 0.3, ease: "easeInOut" } }, -}; - -const chatCardVariants: Variants = { - initial: { y: 20, opacity: 0 }, - animate: { y: 0, opacity: 1 }, - exit: { y: -20, opacity: 0 }, -}; - -const MotionCard = motion(Card); - -export default function ChatsPageClient({ searchSpaceId }: ChatsPageClientProps) { - const router = useRouter(); - const [filteredChats, setFilteredChats] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [currentPage, setCurrentPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); - const [selectedType, setSelectedType] = useState("all"); - const [sortOrder, setSortOrder] = useState("newest"); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [chatToDelete, setChatToDelete] = useState<{ - id: number; - title: string; - } | null>(null); - const { isFetching: isFetchingChats, data: chats, error: fetchError } = useAtomValue(chatsAtom); - const [{ isPending: isDeletingChat, mutateAsync: deleteChat, error: deleteError }] = - useAtom(deleteChatMutationAtom); - - const chatsPerPage = 9; - const searchParams = useSearchParams(); - - // Get initial page from URL params if it exists - useEffect(() => { - const pageParam = searchParams.get("page"); - if (pageParam) { - const pageNumber = parseInt(pageParam, 10); - if (!Number.isNaN(pageNumber) && pageNumber > 0) { - setCurrentPage(pageNumber); - } - } - }, [searchParams]); - - // Filter and sort chats based on search query, type, and sort order - useEffect(() => { - let result = [...(chats || [])]; - - // Filter by search term - if (searchQuery) { - const query = searchQuery.toLowerCase(); - result = result.filter((chat) => chat.title.toLowerCase().includes(query)); - } - - // Filter by type - if (selectedType !== "all") { - result = result.filter((chat) => chat.type === selectedType); - } - - // Sort chats - result.sort((a, b) => { - const dateA = new Date(a.created_at).getTime(); - const dateB = new Date(b.created_at).getTime(); - - return sortOrder === "newest" ? dateB - dateA : dateA - dateB; - }); - - setFilteredChats(result); - setTotalPages(Math.max(1, Math.ceil(result.length / chatsPerPage))); - - // Reset to first page when filters change - if (currentPage !== 1 && (searchQuery || selectedType !== "all" || sortOrder !== "newest")) { - setCurrentPage(1); - } - }, [chats, searchQuery, selectedType, sortOrder, currentPage]); - - // Function to handle chat deletion - const handleDeleteChat = async () => { - if (!chatToDelete) return; - - await deleteChat({ id: chatToDelete.id }); - - setDeleteDialogOpen(false); - setChatToDelete(null); - }; - - // Calculate pagination - const indexOfLastChat = currentPage * chatsPerPage; // Index of last chat in the current page - const indexOfFirstChat = indexOfLastChat - chatsPerPage; // Index of first chat in the current page - const currentChats = filteredChats.slice(indexOfFirstChat, indexOfLastChat); - - // Get unique chat types for filter dropdown - const chatTypes = chats ? ["all", ...Array.from(new Set(chats.map((chat) => chat.type)))] : []; - - return ( - -
-
-

All Chats

-

View, search, and manage all your chats.

-
- - {/* Filter and Search Bar */} -
-
-
- - setSearchQuery(e.target.value)} - /> -
- - -
- -
- -
-
- - {/* Status Messages */} - {isFetchingChats && ( -
-
-
-

Loading chats...

-
-
- )} - - {fetchError && !isFetchingChats && ( -
-

Error loading chats

-

{fetchError.message}

-
- )} - - {!isFetchingChats && !fetchError && filteredChats.length === 0 && ( -
- -

No chats found

-

- {searchQuery || selectedType !== "all" - ? "Try adjusting your search filters" - : "Start a new chat to get started"} -

-
- )} - - {/* Chat Grid */} - {!isFetchingChats && !fetchError && filteredChats.length > 0 && ( - -
- {currentChats.map((chat, index) => ( - - -
-
- - {chat.title || `Chat ${chat.id}`} - - - - - {format(new Date(chat.created_at), "MMM d, yyyy")} - - -
- - - - - - - router.push( - `/dashboard/${chat.search_space_id}/researcher/${chat.id}` - ) - } - > - - View Chat - - - { - e.stopPropagation(); - setChatToDelete({ - id: chat.id, - title: chat.title || `Chat ${chat.id}`, - }); - setDeleteDialogOpen(true); - }} - > - - Delete Chat - - - -
-
- - - - - {chat.type || "Unknown"} - - - -
- ))} -
-
- )} - - {/* Pagination */} - {!isFetchingChats && !fetchError && totalPages > 1 && ( - - - - { - e.preventDefault(); - if (currentPage > 1) setCurrentPage(currentPage - 1); - }} - className={currentPage <= 1 ? "pointer-events-none opacity-50" : ""} - /> - - - {Array.from({ length: totalPages }).map((_, index) => { - const pageNumber = index + 1; - const isVisible = - pageNumber === 1 || - pageNumber === totalPages || - (pageNumber >= currentPage - 1 && pageNumber <= currentPage + 1); - - if (!isVisible) { - // Show ellipsis at appropriate positions - if (pageNumber === 2 || pageNumber === totalPages - 1) { - return ( - - ... - - ); - } - return null; - } - - return ( - - { - e.preventDefault(); - setCurrentPage(pageNumber); - }} - isActive={pageNumber === currentPage} - > - {pageNumber} - - - ); - })} - - - { - e.preventDefault(); - if (currentPage < totalPages) setCurrentPage(currentPage + 1); - }} - className={currentPage >= totalPages ? "pointer-events-none opacity-50" : ""} - /> - - - - )} -
- - {/* Delete Confirmation Dialog */} - - - - - - Delete Chat - - - Are you sure you want to delete{" "} - {chatToDelete?.title}? This action cannot be - undone. - - - - - - - - -
- ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/chats/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/chats/page.tsx deleted file mode 100644 index 9a2bb0f48..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/chats/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Suspense } from "react"; -import ChatsPageClient from "./chats-client"; - -interface PageProps { - params: { - search_space_id: string; - }; -} - -export default async function ChatsPage({ params }: PageProps) { - // Get search space ID from the route parameter - const { search_space_id: searchSpaceId } = await Promise.resolve(params); - - return ( - -
- - } - > - -
- ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx index 196f565a9..70b9505e9 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/researcher/[[...chat_id]]/page.tsx @@ -134,9 +134,13 @@ export default function ResearcherPage() { message: Message | CreateMessage, chatRequestOptions?: { data?: any } ) => { + // Use the first message content as the chat title (truncated to 100 chars) + const messageContent = typeof message.content === 'string' ? message.content : ''; + const chatTitle = messageContent.slice(0, 100) || "Untitled Chat"; + const newChat = await createChat({ type: researchMode, - title: "Untitled Chat", + title: chatTitle, initial_connectors: selectedConnectors, messages: [ { diff --git a/surfsense_web/atoms/chats/chat-mutation.atoms.ts b/surfsense_web/atoms/chats/chat-mutation.atoms.ts index a08dcd21f..798bfcd2f 100644 --- a/surfsense_web/atoms/chats/chat-mutation.atoms.ts +++ b/surfsense_web/atoms/chats/chat-mutation.atoms.ts @@ -1,7 +1,7 @@ import { atomWithMutation } from "jotai-tanstack-query"; import { toast } from "sonner"; -import type { Chat } from "@/app/dashboard/[search_space_id]/chats/chats-client"; import type { + ChatSummary, CreateChatRequest, DeleteChatRequest, UpdateChatRequest, @@ -29,7 +29,7 @@ export const deleteChatMutationAtom = atomWithMutation((get) => { toast.success("Chat deleted successfully"); queryClient.setQueryData( cacheKeys.chats.globalQueryParams(chatsQueryParams), - (oldData: Chat[]) => { + (oldData: ChatSummary[]) => { return oldData.filter((chat) => chat.id !== request.id); } ); diff --git a/surfsense_web/components/sidebar/all-chats-sidebar.tsx b/surfsense_web/components/sidebar/all-chats-sidebar.tsx new file mode 100644 index 000000000..6b491cb24 --- /dev/null +++ b/surfsense_web/components/sidebar/all-chats-sidebar.tsx @@ -0,0 +1,241 @@ +"use client"; + +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { Loader2, MessageCircleMore, MoreHorizontal, Search, Trash2, X } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { useCallback, useMemo, useState } from "react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { useDebouncedValue } from "@/hooks/use-debounced-value"; +import { chatsApiService } from "@/lib/apis/chats-api.service"; +import { cn } from "@/lib/utils"; + +interface AllChatsSidebarProps { + open: boolean; + onOpenChange: (open: boolean) => void; + searchSpaceId: string; +} + +export function AllChatsSidebar({ + open, + onOpenChange, + searchSpaceId, +}: AllChatsSidebarProps) { + const t = useTranslations("sidebar"); + const router = useRouter(); + const queryClient = useQueryClient(); + const [deletingChatId, setDeletingChatId] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); + const debouncedSearchQuery = useDebouncedValue(searchQuery, 300); + + // Fetch all chats + const { + data: chatsData, + error: chatsError, + isLoading: isLoadingChats, + } = useQuery({ + queryKey: ["all-chats", searchSpaceId], + queryFn: () => + chatsApiService.getChats({ + queryParams: { + search_space_id: Number(searchSpaceId), + }, + }), + enabled: !!searchSpaceId && open, + }); + + // Handle chat navigation + const handleChatClick = useCallback( + (chatId: number, chatSearchSpaceId: number) => { + router.push(`/dashboard/${chatSearchSpaceId}/researcher/${chatId}`); + onOpenChange(false); + }, + [router, onOpenChange] + ); + + // Handle chat deletion + const handleDeleteChat = useCallback( + async (chatId: number) => { + setDeletingChatId(chatId); + try { + await chatsApiService.deleteChat({ id: chatId }); + toast.success(t("chat_deleted") || "Chat deleted successfully"); + // Invalidate queries to refresh the list + queryClient.invalidateQueries({ queryKey: ["all-chats", searchSpaceId] }); + queryClient.invalidateQueries({ queryKey: ["chats"] }); + } catch (error) { + console.error("Error deleting chat:", error); + toast.error(t("error_deleting_chat") || "Failed to delete chat"); + } finally { + setDeletingChatId(null); + } + }, + [queryClient, searchSpaceId, t] + ); + + // Clear search + const handleClearSearch = useCallback(() => { + setSearchQuery(""); + }, []); + + // Filter chats based on search query (client-side filtering) + const chats = useMemo(() => { + const allChats = chatsData ?? []; + if (!debouncedSearchQuery) { + return allChats; + } + const query = debouncedSearchQuery.toLowerCase(); + return allChats.filter((chat) => + chat.title.toLowerCase().includes(query) + ); + }, [chatsData, debouncedSearchQuery]); + + const isSearchMode = !!debouncedSearchQuery; + + return ( + + + + {t("all_chats") || "All Chats"} + + {t("all_chats_description") || "Browse and manage all your chats"} + + + {/* Search Input */} +
+ + setSearchQuery(e.target.value)} + className="pl-9 pr-8 h-9" + /> + {searchQuery && ( + + )} +
+
+ + +
+ {isLoadingChats ? ( +
+ +
+ ) : chatsError ? ( +
+ {t("error_loading_chats") || "Error loading chats"} +
+ ) : chats.length > 0 ? ( +
+ {chats.map((chat) => { + const isDeleting = deletingChatId === chat.id; + + return ( +
+ {/* Main clickable area for navigation */} + + + {/* Actions dropdown - separate from main click area */} + + + + + + handleDeleteChat(chat.id)} + className="text-destructive focus:text-destructive" + > + + Delete + + + +
+ ); + })} +
+ ) : isSearchMode ? ( +
+ +

+ {t("no_chats_found") || "No chats found"} +

+

+ {t("try_different_search") || "Try a different search term"} +

+
+ ) : ( +
+ +

+ {t("no_chats") || "No chats yet"} +

+

+ {t("start_new_chat_hint") || "Start a new chat from the researcher"} +

+
+ )} +
+
+
+
+ ); +} diff --git a/surfsense_web/components/sidebar/app-sidebar.tsx b/surfsense_web/components/sidebar/app-sidebar.tsx index 48e7c35b8..78864da2d 100644 --- a/surfsense_web/components/sidebar/app-sidebar.tsx +++ b/surfsense_web/components/sidebar/app-sidebar.tsx @@ -113,9 +113,9 @@ function UserAvatar({ email, size = 32 }: { email: string; size?: number }) { ); } +import { NavChats } from "@/components/sidebar/nav-chats"; import { NavMain } from "@/components/sidebar/nav-main"; import { NavNotes } from "@/components/sidebar/nav-notes"; -import { NavProjects } from "@/components/sidebar/nav-projects"; import { NavSecondary } from "@/components/sidebar/nav-secondary"; import { PageUsageDisplay } from "@/components/sidebar/page-usage-display"; import { @@ -446,11 +446,12 @@ export const AppSidebar = memo(function AppSidebar({ - {processedRecentChats.length > 0 && ( -
- -
- )} +
+ +
void; +} + +interface ChatItem { + name: string; + url: string; + icon: LucideIcon; + id?: number; + search_space_id?: number; + actions?: ChatAction[]; +} + +interface NavChatsProps { + chats: ChatItem[]; + defaultOpen?: boolean; + searchSpaceId?: string; +} + +// Map of icon names to their components +const actionIconMap: Record = { + MessageCircleMore, + Trash2, + MoreHorizontal, +}; + +export function NavChats({ chats, defaultOpen = true, searchSpaceId }: NavChatsProps) { + const t = useTranslations("sidebar"); + const router = useRouter(); + const [isDeleting, setIsDeleting] = useState(null); + const [isOpen, setIsOpen] = useState(defaultOpen); + const [isAllChatsSidebarOpen, setIsAllChatsSidebarOpen] = useState(false); + + // Handle chat deletion with loading state + const handleDeleteChat = useCallback(async (chatId: number, deleteAction: () => void) => { + setIsDeleting(chatId); + try { + await deleteAction(); + } finally { + setIsDeleting(null); + } + }, []); + + // Handle chat navigation + const handleChatClick = useCallback( + (url: string) => { + router.push(url); + }, + [router] + ); + + return ( + + +
+ + + + {t("recent_chats") || "Recent Chats"} + + + + {/* Action buttons - always visible on hover */} +
+ {searchSpaceId && chats.length > 0 && ( + + )} +
+
+ + + + + {chats.length > 0 ? ( + chats.map((chat) => { + const isDeletingChat = isDeleting === chat.id; + + return ( + + {/* Main navigation button */} + handleChatClick(chat.url)} + disabled={isDeletingChat} + className={cn( + "pr-8", // Make room for the action button + isDeletingChat && "opacity-50" + )} + > + + {chat.name} + + + {/* Actions dropdown - positioned absolutely */} + {chat.actions && chat.actions.length > 0 && ( +
+ + + + + + {chat.actions.map((action, actionIndex) => { + const ActionIcon = actionIconMap[action.icon] || MessageCircleMore; + const isDeleteAction = action.name.toLowerCase().includes("delete"); + + return ( + { + if (isDeleteAction) { + handleDeleteChat(chat.id || 0, action.onClick); + } else { + action.onClick(); + } + }} + disabled={isDeletingChat} + className={ + isDeleteAction + ? "text-destructive focus:text-destructive" + : "" + } + > + + + {isDeletingChat && isDeleteAction + ? "Deleting..." + : action.name} + + + ); + })} + + +
+ )} +
+ ); + }) + ) : ( + + + + {t("no_recent_chats") || "No recent chats"} + + + )} +
+
+
+
+ + {/* All Chats Sheet */} + {searchSpaceId && ( + + )} +
+ ); +} diff --git a/surfsense_web/components/sidebar/nav-projects.tsx b/surfsense_web/components/sidebar/nav-projects.tsx deleted file mode 100644 index 3862ce75d..000000000 --- a/surfsense_web/components/sidebar/nav-projects.tsx +++ /dev/null @@ -1,177 +0,0 @@ -"use client"; - -import { - ExternalLink, - Folder, - type LucideIcon, - MoreHorizontal, - RefreshCw, - Search, - Share, - Trash2, -} from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useTranslations } from "next-intl"; -import { useCallback, useMemo, useState } from "react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - SidebarGroup, - SidebarGroupLabel, - SidebarInput, - SidebarMenu, - SidebarMenuAction, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "@/components/ui/sidebar"; - -// Map of icon names to their components -const actionIconMap: Record = { - ExternalLink, - Folder, - Share, - Trash2, - MoreHorizontal, - Search, - RefreshCw, -}; - -interface ChatAction { - name: string; - icon: string; - onClick: () => void; -} - -interface ChatItem { - name: string; - url: string; - icon: LucideIcon; - id?: number; - search_space_id?: number; - actions?: ChatAction[]; -} - -export function NavProjects({ chats }: { chats: ChatItem[] }) { - const t = useTranslations("sidebar"); - const { isMobile } = useSidebar(); - const router = useRouter(); - const [searchQuery, setSearchQuery] = useState(""); - const [isDeleting, setIsDeleting] = useState(null); - - const searchSpaceId = chats[0]?.search_space_id || ""; - - // Memoized filtered chats - const filteredChats = useMemo(() => { - if (!searchQuery.trim()) return chats; - - return chats.filter((chat) => chat.name.toLowerCase().includes(searchQuery.toLowerCase())); - }, [chats, searchQuery]); - - // Handle chat deletion with loading state - const handleDeleteChat = useCallback(async (chatId: number, deleteAction: () => void) => { - setIsDeleting(chatId); - try { - await deleteAction(); - } finally { - setIsDeleting(null); - } - }, []); - - // Enhanced chat item component - const ChatItemComponent = useCallback( - ({ chat }: { chat: ChatItem }) => { - const isDeletingChat = isDeleting === chat.id; - - return ( - - router.push(chat.url)} - disabled={isDeletingChat} - className={isDeletingChat ? "opacity-50" : ""} - > - - {chat.name} - - - {chat.actions && chat.actions.length > 0 && ( - - - - - More - - - - {chat.actions.map((action, actionIndex) => { - const ActionIcon = actionIconMap[action.icon] || Folder; - const isDeleteAction = action.name.toLowerCase().includes("delete"); - - return ( - { - if (isDeleteAction) { - handleDeleteChat(chat.id || 0, action.onClick); - } else { - action.onClick(); - } - }} - disabled={isDeletingChat} - className={isDeleteAction ? "text-destructive" : ""} - > - - {isDeletingChat && isDeleteAction ? "Deleting..." : action.name} - - ); - })} - - - )} - - ); - }, - [isDeleting, router, isMobile, handleDeleteChat] - ); - - // Show search input if there are chats - const showSearch = chats.length > 0; - - return ( - - {t("recent_chats")} - - {/* Chat Items */} - {filteredChats.length > 0 ? ( - filteredChats.map((chat) => ) - ) : ( - /* No results state */ - - - - {searchQuery ? t("no_chats_found") : t("no_recent_chats")} - - - )} - - {/* View All Chats */} - {chats.length > 0 && ( - - router.push(`/dashboard/${searchSpaceId}/chats`)}> - - {t("view_all_chats")} - - - )} - - - ); -} diff --git a/surfsense_web/hooks/use-chat.ts b/surfsense_web/hooks/use-chat.ts index 108165bbf..c31097e11 100644 --- a/surfsense_web/hooks/use-chat.ts +++ b/surfsense_web/hooks/use-chat.ts @@ -1,6 +1,4 @@ -import type { Message } from "@ai-sdk/react"; -import { useCallback, useEffect, useState } from "react"; -import type { ChatDetails } from "@/app/dashboard/[search_space_id]/chats/chats-client"; +import { useEffect, useState } from "react"; import type { ResearchMode } from "@/components/chat"; import type { Document } from "@/contracts/types/document.types"; import { getBearerToken } from "@/lib/auth-utils"; diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index e2cf89b5f..478264889 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -642,6 +642,13 @@ "no_chats_found": "No chats found", "no_recent_chats": "No recent chats", "view_all_chats": "View All Chats", + "all_chats": "All Chats", + "all_chats_description": "Browse and manage all your chats", + "no_chats": "No chats yet", + "start_new_chat_hint": "Start a new chat from the researcher", + "error_loading_chats": "Error loading chats", + "chat_deleted": "Chat deleted successfully", + "error_deleting_chat": "Failed to delete chat", "search_space": "Search Space", "notes": "Notes", "all_notes": "All Notes", diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index 38546bb87..24b37c61f 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -642,6 +642,13 @@ "no_chats_found": "未找到对话", "no_recent_chats": "暂无最近对话", "view_all_chats": "查看所有对话", + "all_chats": "所有对话", + "all_chats_description": "浏览和管理您的所有对话", + "no_chats": "暂无对话", + "start_new_chat_hint": "从研究员开始新对话", + "error_loading_chats": "加载对话时出错", + "chat_deleted": "对话删除成功", + "error_deleting_chat": "删除对话失败", "search_space": "搜索空间", "notes": "笔记", "all_notes": "所有笔记",