mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-09 07:42:39 +02:00
add delete chat mutation
This commit is contained in:
parent
bd4e5d627d
commit
b866170e6a
11 changed files with 486 additions and 445 deletions
|
|
@ -2,491 +2,511 @@
|
||||||
|
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
MessageCircleMore,
|
MessageCircleMore,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
Search,
|
Search,
|
||||||
Tag,
|
Tag,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { AnimatePresence, motion, type Variants } from "motion/react";
|
import { AnimatePresence, motion, type Variants } from "motion/react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Card,
|
||||||
DialogContent,
|
CardDescription,
|
||||||
DialogDescription,
|
CardFooter,
|
||||||
DialogFooter,
|
CardHeader,
|
||||||
DialogHeader,
|
CardTitle,
|
||||||
DialogTitle,
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
PaginationItem,
|
PaginationItem,
|
||||||
PaginationLink,
|
PaginationLink,
|
||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination";
|
} from "@/components/ui/pagination";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { cn } from "@/lib/utils";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useAtomValue } from "jotai";
|
import { activeSearchSpaceChatsAtom } from "@/atoms/chats/queries/active-search-space-chats.query.atom";
|
||||||
import { activeSearchSpaceChatsAtom } from "@/atoms/chats/queries/active-search-space-chats.atom";
|
import { deleteChatMutationAtom } from "@/atoms/chats/mutations/delete-chat.mutation.atom";
|
||||||
|
|
||||||
export interface Chat {
|
export interface Chat {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
id: number;
|
id: number;
|
||||||
type: "DOCUMENT" | "CHAT";
|
type: "DOCUMENT" | "CHAT";
|
||||||
title: string;
|
title: string;
|
||||||
search_space_id: number;
|
search_space_id: number;
|
||||||
state_version: number;
|
state_version: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatDetails {
|
export interface ChatDetails {
|
||||||
type: "DOCUMENT" | "CHAT";
|
type: "DOCUMENT" | "CHAT";
|
||||||
title: string;
|
title: string;
|
||||||
initial_connectors: string[];
|
initial_connectors: string[];
|
||||||
messages: any[];
|
messages: any[];
|
||||||
created_at: string;
|
created_at: string;
|
||||||
id: number;
|
id: number;
|
||||||
search_space_id: number;
|
search_space_id: number;
|
||||||
state_version: number;
|
state_version: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatsPageClientProps {
|
interface ChatsPageClientProps {
|
||||||
searchSpaceId: string;
|
searchSpaceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageVariants: Variants = {
|
const pageVariants: Variants = {
|
||||||
initial: { opacity: 0 },
|
initial: { opacity: 0 },
|
||||||
enter: { opacity: 1, transition: { duration: 0.3, ease: "easeInOut" } },
|
enter: { opacity: 1, transition: { duration: 0.3, ease: "easeInOut" } },
|
||||||
exit: { opacity: 0, transition: { duration: 0.3, ease: "easeInOut" } },
|
exit: { opacity: 0, transition: { duration: 0.3, ease: "easeInOut" } },
|
||||||
};
|
};
|
||||||
|
|
||||||
const chatCardVariants: Variants = {
|
const chatCardVariants: Variants = {
|
||||||
initial: { y: 20, opacity: 0 },
|
initial: { y: 20, opacity: 0 },
|
||||||
animate: { y: 0, opacity: 1 },
|
animate: { y: 0, opacity: 1 },
|
||||||
exit: { y: -20, opacity: 0 },
|
exit: { y: -20, opacity: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const MotionCard = motion(Card);
|
const MotionCard = motion(Card);
|
||||||
|
|
||||||
export default function ChatsPageClient({ searchSpaceId }: ChatsPageClientProps) {
|
export default function ChatsPageClient({
|
||||||
const router = useRouter();
|
searchSpaceId,
|
||||||
const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
|
}: ChatsPageClientProps) {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const router = useRouter();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedType, setSelectedType] = useState<string>("all");
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [sortOrder, setSortOrder] = useState<string>("newest");
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [selectedType, setSelectedType] = useState<string>("all");
|
||||||
const [chatToDelete, setChatToDelete] = useState<{ id: number; title: string } | null>(null);
|
const [sortOrder, setSortOrder] = useState<string>("newest");
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const {isFetching , data : chats, error} = useAtomValue(activeSearchSpaceChatsAtom);
|
const [chatToDelete, setChatToDelete] = useState<{
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
} | null>(null);
|
||||||
|
const {
|
||||||
|
isFetching: isFetchingChats,
|
||||||
|
data: chats,
|
||||||
|
error: fetchError,
|
||||||
|
} = useAtomValue(activeSearchSpaceChatsAtom);
|
||||||
|
const [
|
||||||
|
{ isPending: isDeletingChat, mutateAsync: deleteChat, error: deleteError }
|
||||||
|
] = useAtom(deleteChatMutationAtom);
|
||||||
|
|
||||||
const chatsPerPage = 9;
|
const chatsPerPage = 9;
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Get initial page from URL params if it exists
|
// Get initial page from URL params if it exists
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageParam = searchParams.get("page");
|
const pageParam = searchParams.get("page");
|
||||||
if (pageParam) {
|
if (pageParam) {
|
||||||
const pageNumber = parseInt(pageParam, 10);
|
const pageNumber = parseInt(pageParam, 10);
|
||||||
if (!Number.isNaN(pageNumber) && pageNumber > 0) {
|
if (!Number.isNaN(pageNumber) && pageNumber > 0) {
|
||||||
setCurrentPage(pageNumber);
|
setCurrentPage(pageNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (fetchError) {
|
||||||
|
console.error("Error fetching chats:", fetchError);
|
||||||
|
}
|
||||||
|
}, [fetchError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (deleteError) {
|
||||||
console.error("Error fetching chats:", error);
|
console.error("Error deleting chat:", deleteError);
|
||||||
}
|
}
|
||||||
}, [error]);
|
}, [deleteError]);
|
||||||
|
|
||||||
// Filter and sort chats based on search query, type, and sort order
|
// Filter and sort chats based on search query, type, and sort order
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let result = [...(chats || [])];
|
let result = [...(chats || [])];
|
||||||
|
|
||||||
// Filter by search term
|
// Filter by search term
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
const query = searchQuery.toLowerCase();
|
const query = searchQuery.toLowerCase();
|
||||||
result = result.filter((chat) => chat.title.toLowerCase().includes(query));
|
result = result.filter((chat) =>
|
||||||
}
|
chat.title.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by type
|
// Filter by type
|
||||||
if (selectedType !== "all") {
|
if (selectedType !== "all") {
|
||||||
result = result.filter((chat) => chat.type === selectedType);
|
result = result.filter((chat) => chat.type === selectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort chats
|
// Sort chats
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
const dateA = new Date(a.created_at).getTime();
|
const dateA = new Date(a.created_at).getTime();
|
||||||
const dateB = new Date(b.created_at).getTime();
|
const dateB = new Date(b.created_at).getTime();
|
||||||
|
|
||||||
return sortOrder === "newest" ? dateB - dateA : dateA - dateB;
|
return sortOrder === "newest" ? dateB - dateA : dateA - dateB;
|
||||||
});
|
});
|
||||||
|
|
||||||
setFilteredChats(result);
|
setFilteredChats(result);
|
||||||
setTotalPages(Math.max(1, Math.ceil(result.length / chatsPerPage)));
|
setTotalPages(Math.max(1, Math.ceil(result.length / chatsPerPage)));
|
||||||
|
|
||||||
// Reset to first page when filters change
|
// Reset to first page when filters change
|
||||||
if (currentPage !== 1 && (searchQuery || selectedType !== "all" || sortOrder !== "newest")) {
|
if (
|
||||||
setCurrentPage(1);
|
currentPage !== 1 &&
|
||||||
}
|
(searchQuery || selectedType !== "all" || sortOrder !== "newest")
|
||||||
}, [chats, searchQuery, selectedType, sortOrder, currentPage]);
|
) {
|
||||||
|
setCurrentPage(1);
|
||||||
|
}
|
||||||
|
}, [chats, searchQuery, selectedType, sortOrder, currentPage]);
|
||||||
|
|
||||||
// Function to handle chat deletion
|
// Function to handle chat deletion
|
||||||
const handleDeleteChat = async () => {
|
const handleDeleteChat = async () => {
|
||||||
// if (!chatToDelete) return;
|
if (!chatToDelete) return;
|
||||||
|
|
||||||
// setIsDeleting(true);
|
await deleteChat(chatToDelete.id);
|
||||||
// try {
|
|
||||||
// const token = localStorage.getItem("surfsense_bearer_token");
|
|
||||||
// if (!token) {
|
|
||||||
// setIsDeleting(false);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const response = await fetch(
|
setDeleteDialogOpen(false);
|
||||||
// `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/${chatToDelete.id}`,
|
setChatToDelete(null);
|
||||||
// {
|
};
|
||||||
// method: "DELETE",
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${token}`,
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (!response.ok) {
|
// Calculate pagination
|
||||||
// throw new Error(`Failed to delete chat: ${response.statusText}`);
|
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);
|
||||||
|
|
||||||
// // Close dialog and refresh chats
|
// Get unique chat types for filter dropdown
|
||||||
// setDeleteDialogOpen(false);
|
const chatTypes = chats
|
||||||
// setChatToDelete(null);
|
? ["all", ...Array.from(new Set(chats.map((chat) => chat.type)))]
|
||||||
|
: [];
|
||||||
|
|
||||||
// // Update local state by removing the deleted chat
|
return (
|
||||||
// setChats((prevChats) => prevChats.filter((chat) => chat.id !== chatToDelete.id));
|
<motion.div
|
||||||
// } catch (error) {
|
className="container p-6 mx-auto"
|
||||||
// console.error("Error deleting chat:", error);
|
initial="initial"
|
||||||
// } finally {
|
animate="enter"
|
||||||
// setIsDeleting(false);
|
exit="exit"
|
||||||
// }
|
variants={pageVariants}
|
||||||
};
|
>
|
||||||
|
<div className="flex flex-col space-y-4 md:space-y-6">
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<h1 className="text-3xl font-bold tracking-tight">All Chats</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
View, search, and manage all your chats.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
// Calculate pagination
|
{/* Filter and Search Bar */}
|
||||||
const indexOfLastChat = currentPage * chatsPerPage; // Index of last chat in the current page
|
<div className="flex flex-col space-y-4 md:flex-row md:items-center md:justify-between md:space-y-0">
|
||||||
const indexOfFirstChat = indexOfLastChat - chatsPerPage; // Index of first chat in the current page
|
<div className="flex flex-1 items-center gap-2">
|
||||||
const currentChats = filteredChats.slice(indexOfFirstChat, indexOfLastChat);
|
<div className="relative w-full md:w-80">
|
||||||
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search chats..."
|
||||||
|
className="pl-8"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
// Get unique chat types for filter dropdown
|
<Select value={selectedType} onValueChange={setSelectedType}>
|
||||||
const chatTypes = chats ? ["all", ...Array.from(new Set(chats.map((chat) => chat.type)))] : [];
|
<SelectTrigger className="w-full md:w-40">
|
||||||
|
<SelectValue placeholder="Filter by type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{chatTypes.map((type) => (
|
||||||
|
<SelectItem key={type} value={type}>
|
||||||
|
{type === "all"
|
||||||
|
? "All Types"
|
||||||
|
: type.charAt(0).toUpperCase() + type.slice(1)}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
<div className="flex items-center gap-2">
|
||||||
<motion.div
|
<Select value={sortOrder} onValueChange={setSortOrder}>
|
||||||
className="container p-6 mx-auto"
|
<SelectTrigger className="w-40">
|
||||||
initial="initial"
|
<SelectValue placeholder="Sort order" />
|
||||||
animate="enter"
|
</SelectTrigger>
|
||||||
exit="exit"
|
<SelectContent>
|
||||||
variants={pageVariants}
|
<SelectGroup>
|
||||||
>
|
<SelectItem value="newest">Newest First</SelectItem>
|
||||||
<div className="flex flex-col space-y-4 md:space-y-6">
|
<SelectItem value="oldest">Oldest First</SelectItem>
|
||||||
<div className="flex flex-col space-y-2">
|
</SelectGroup>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">All Chats</h1>
|
</SelectContent>
|
||||||
<p className="text-muted-foreground">View, search, and manage all your chats.</p>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Filter and Search Bar */}
|
{/* Status Messages */}
|
||||||
<div className="flex flex-col space-y-4 md:flex-row md:items-center md:justify-between md:space-y-0">
|
{isFetchingChats && (
|
||||||
<div className="flex flex-1 items-center gap-2">
|
<div className="flex items-center justify-center h-40">
|
||||||
<div className="relative w-full md:w-80">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
|
||||||
<Input
|
<p className="text-sm text-muted-foreground">Loading chats...</p>
|
||||||
type="text"
|
</div>
|
||||||
placeholder="Search chats..."
|
</div>
|
||||||
className="pl-8"
|
)}
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Select value={selectedType} onValueChange={setSelectedType}>
|
{fetchError && !isFetchingChats && (
|
||||||
<SelectTrigger className="w-full md:w-40">
|
<div className="border border-destructive/50 text-destructive p-4 rounded-md">
|
||||||
<SelectValue placeholder="Filter by type" />
|
<h3 className="font-medium">Error loading chats</h3>
|
||||||
</SelectTrigger>
|
<p className="text-sm">{fetchError.message}</p>
|
||||||
<SelectContent>
|
</div>
|
||||||
<SelectGroup>
|
)}
|
||||||
{chatTypes.map((type) => (
|
|
||||||
<SelectItem key={type} value={type}>
|
|
||||||
{type === "all" ? "All Types" : type.charAt(0).toUpperCase() + type.slice(1)}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
{!isFetchingChats && !fetchError && filteredChats.length === 0 && (
|
||||||
<Select value={sortOrder} onValueChange={setSortOrder}>
|
<div className="flex flex-col items-center justify-center h-40 gap-2 text-center">
|
||||||
<SelectTrigger className="w-40">
|
<MessageCircleMore className="h-8 w-8 text-muted-foreground" />
|
||||||
<SelectValue placeholder="Sort order" />
|
<h3 className="font-medium">No chats found</h3>
|
||||||
</SelectTrigger>
|
<p className="text-sm text-muted-foreground">
|
||||||
<SelectContent>
|
{searchQuery || selectedType !== "all"
|
||||||
<SelectGroup>
|
? "Try adjusting your search filters"
|
||||||
<SelectItem value="newest">Newest First</SelectItem>
|
: "Start a new chat to get started"}
|
||||||
<SelectItem value="oldest">Oldest First</SelectItem>
|
</p>
|
||||||
</SelectGroup>
|
</div>
|
||||||
</SelectContent>
|
)}
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Status Messages */}
|
{/* Chat Grid */}
|
||||||
{isFetching && (
|
{!isFetchingChats && !fetchError && filteredChats.length > 0 && (
|
||||||
<div className="flex items-center justify-center h-40">
|
<AnimatePresence mode="wait">
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
|
{currentChats.map((chat, index) => (
|
||||||
<p className="text-sm text-muted-foreground">Loading chats...</p>
|
<MotionCard
|
||||||
</div>
|
key={chat.id}
|
||||||
</div>
|
variants={chatCardVariants}
|
||||||
)}
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.2, delay: index * 0.05 }}
|
||||||
|
className="overflow-hidden hover:shadow-md transition-shadow"
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<CardTitle className="line-clamp-1">
|
||||||
|
{chat.title || `Chat ${chat.id}`}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Calendar className="h-3.5 w-3.5" />
|
||||||
|
<span>
|
||||||
|
{format(new Date(chat.created_at), "MMM d, yyyy")}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8"
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/dashboard/${chat.search_space_id}/researcher/${chat.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ExternalLink className="mr-2 h-4 w-4" />
|
||||||
|
<span>View Chat</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="text-destructive focus:text-destructive"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setChatToDelete({
|
||||||
|
id: chat.id,
|
||||||
|
title: chat.title || `Chat ${chat.id}`,
|
||||||
|
});
|
||||||
|
setDeleteDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
<span>Delete Chat</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
{error && !isFetching && (
|
<CardFooter className="flex items-center justify-between gap-2 w-full">
|
||||||
<div className="border border-destructive/50 text-destructive p-4 rounded-md">
|
<Badge variant="secondary" className="text-xs">
|
||||||
<h3 className="font-medium">Error loading chats</h3>
|
<Tag className="mr-1 h-3 w-3" />
|
||||||
<p className="text-sm">{error.message}</p>
|
{chat.type || "Unknown"}
|
||||||
</div>
|
</Badge>
|
||||||
)}
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/dashboard/${chat.search_space_id}/researcher/${chat.id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MessageCircleMore className="h-4 w-4" />
|
||||||
|
<span>View Chat</span>
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</MotionCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AnimatePresence>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isFetching && !error && filteredChats.length === 0 && (
|
{/* Pagination */}
|
||||||
<div className="flex flex-col items-center justify-center h-40 gap-2 text-center">
|
{!isFetchingChats && !fetchError && totalPages > 1 && (
|
||||||
<MessageCircleMore className="h-8 w-8 text-muted-foreground" />
|
<Pagination className="mt-8">
|
||||||
<h3 className="font-medium">No chats found</h3>
|
<PaginationContent>
|
||||||
<p className="text-sm text-muted-foreground">
|
<PaginationItem>
|
||||||
{searchQuery || selectedType !== "all"
|
<PaginationPrevious
|
||||||
? "Try adjusting your search filters"
|
href={`?page=${Math.max(1, currentPage - 1)}`}
|
||||||
: "Start a new chat to get started"}
|
onClick={(e) => {
|
||||||
</p>
|
e.preventDefault();
|
||||||
</div>
|
if (currentPage > 1) setCurrentPage(currentPage - 1);
|
||||||
)}
|
}}
|
||||||
|
className={
|
||||||
|
currentPage <= 1 ? "pointer-events-none opacity-50" : ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
|
||||||
{/* Chat Grid */}
|
{Array.from({ length: totalPages }).map((_, index) => {
|
||||||
{!isFetching && !error && filteredChats.length > 0 && (
|
const pageNumber = index + 1;
|
||||||
<AnimatePresence mode="wait">
|
const isVisible =
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
pageNumber === 1 ||
|
||||||
{currentChats.map((chat, index) => (
|
pageNumber === totalPages ||
|
||||||
<MotionCard
|
(pageNumber >= currentPage - 1 &&
|
||||||
key={chat.id}
|
pageNumber <= currentPage + 1);
|
||||||
variants={chatCardVariants}
|
|
||||||
initial="initial"
|
|
||||||
animate="animate"
|
|
||||||
exit="exit"
|
|
||||||
transition={{ duration: 0.2, delay: index * 0.05 }}
|
|
||||||
className="overflow-hidden hover:shadow-md transition-shadow"
|
|
||||||
>
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<CardTitle className="line-clamp-1">
|
|
||||||
{chat.title || `Chat ${chat.id}`}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<Calendar className="h-3.5 w-3.5" />
|
|
||||||
<span>{format(new Date(chat.created_at), "MMM d, yyyy")}</span>
|
|
||||||
</span>
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() =>
|
|
||||||
router.push(
|
|
||||||
`/dashboard/${chat.search_space_id}/researcher/${chat.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ExternalLink className="mr-2 h-4 w-4" />
|
|
||||||
<span>View Chat</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="text-destructive focus:text-destructive"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setChatToDelete({
|
|
||||||
id: chat.id,
|
|
||||||
title: chat.title || `Chat ${chat.id}`,
|
|
||||||
});
|
|
||||||
setDeleteDialogOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
<span>Delete Chat</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardFooter className="flex items-center justify-between gap-2 w-full">
|
if (!isVisible) {
|
||||||
<Badge variant="secondary" className="text-xs">
|
// Show ellipsis at appropriate positions
|
||||||
<Tag className="mr-1 h-3 w-3" />
|
if (pageNumber === 2 || pageNumber === totalPages - 1) {
|
||||||
{chat.type || "Unknown"}
|
return (
|
||||||
</Badge>
|
<PaginationItem key={pageNumber}>
|
||||||
<Button
|
<span className="flex h-9 w-9 items-center justify-center">
|
||||||
size="sm"
|
...
|
||||||
onClick={() =>
|
</span>
|
||||||
router.push(`/dashboard/${chat.search_space_id}/researcher/${chat.id}`)
|
</PaginationItem>
|
||||||
}
|
);
|
||||||
>
|
}
|
||||||
<MessageCircleMore className="h-4 w-4" />
|
return null;
|
||||||
<span>View Chat</span>
|
}
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</MotionCard>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</AnimatePresence>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Pagination */}
|
return (
|
||||||
{!isFetching && !error && totalPages > 1 && (
|
<PaginationItem key={pageNumber}>
|
||||||
<Pagination className="mt-8">
|
<PaginationLink
|
||||||
<PaginationContent>
|
href={`?page=${pageNumber}`}
|
||||||
<PaginationItem>
|
onClick={(e) => {
|
||||||
<PaginationPrevious
|
e.preventDefault();
|
||||||
href={`?page=${Math.max(1, currentPage - 1)}`}
|
setCurrentPage(pageNumber);
|
||||||
onClick={(e) => {
|
}}
|
||||||
e.preventDefault();
|
isActive={pageNumber === currentPage}
|
||||||
if (currentPage > 1) setCurrentPage(currentPage - 1);
|
>
|
||||||
}}
|
{pageNumber}
|
||||||
className={currentPage <= 1 ? "pointer-events-none opacity-50" : ""}
|
</PaginationLink>
|
||||||
/>
|
</PaginationItem>
|
||||||
</PaginationItem>
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
{Array.from({ length: totalPages }).map((_, index) => {
|
<PaginationItem>
|
||||||
const pageNumber = index + 1;
|
<PaginationNext
|
||||||
const isVisible =
|
href={`?page=${Math.min(totalPages, currentPage + 1)}`}
|
||||||
pageNumber === 1 ||
|
onClick={(e) => {
|
||||||
pageNumber === totalPages ||
|
e.preventDefault();
|
||||||
(pageNumber >= currentPage - 1 && pageNumber <= currentPage + 1);
|
if (currentPage < totalPages)
|
||||||
|
setCurrentPage(currentPage + 1);
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
currentPage >= totalPages
|
||||||
|
? "pointer-events-none opacity-50"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
if (!isVisible) {
|
{/* Delete Confirmation Dialog */}
|
||||||
// Show ellipsis at appropriate positions
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
if (pageNumber === 2 || pageNumber === totalPages - 1) {
|
<DialogContent className="sm:max-w-md">
|
||||||
return (
|
<DialogHeader>
|
||||||
<PaginationItem key={pageNumber}>
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<span className="flex h-9 w-9 items-center justify-center">...</span>
|
<Trash2 className="h-5 w-5 text-destructive" />
|
||||||
</PaginationItem>
|
<span>Delete Chat</span>
|
||||||
);
|
</DialogTitle>
|
||||||
}
|
<DialogDescription>
|
||||||
return null;
|
Are you sure you want to delete{" "}
|
||||||
}
|
<span className="font-medium">{chatToDelete?.title}</span>? This
|
||||||
|
action cannot be undone.
|
||||||
return (
|
</DialogDescription>
|
||||||
<PaginationItem key={pageNumber}>
|
</DialogHeader>
|
||||||
<PaginationLink
|
<DialogFooter className="flex gap-2 sm:justify-end">
|
||||||
href={`?page=${pageNumber}`}
|
<Button
|
||||||
onClick={(e) => {
|
variant="outline"
|
||||||
e.preventDefault();
|
onClick={() => setDeleteDialogOpen(false)}
|
||||||
setCurrentPage(pageNumber);
|
disabled={isDeletingChat}
|
||||||
}}
|
>
|
||||||
isActive={pageNumber === currentPage}
|
Cancel
|
||||||
>
|
</Button>
|
||||||
{pageNumber}
|
<Button
|
||||||
</PaginationLink>
|
variant="destructive"
|
||||||
</PaginationItem>
|
onClick={handleDeleteChat}
|
||||||
);
|
disabled={isDeletingChat}
|
||||||
})}
|
className="gap-2"
|
||||||
|
>
|
||||||
<PaginationItem>
|
{isDeletingChat ? (
|
||||||
<PaginationNext
|
<>
|
||||||
href={`?page=${Math.min(totalPages, currentPage + 1)}`}
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||||
onClick={(e) => {
|
Deleting...
|
||||||
e.preventDefault();
|
</>
|
||||||
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
|
) : (
|
||||||
}}
|
<>
|
||||||
className={currentPage >= totalPages ? "pointer-events-none opacity-50" : ""}
|
<Trash2 className="h-4 w-4" />
|
||||||
/>
|
Delete
|
||||||
</PaginationItem>
|
</>
|
||||||
</PaginationContent>
|
)}
|
||||||
</Pagination>
|
</Button>
|
||||||
)}
|
</DialogFooter>
|
||||||
</div>
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
{/* Delete Confirmation Dialog */}
|
</motion.div>
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
);
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="flex items-center gap-2">
|
|
||||||
<Trash2 className="h-5 w-5 text-destructive" />
|
|
||||||
<span>Delete Chat</span>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete{" "}
|
|
||||||
<span className="font-medium">{chatToDelete?.title}</span>? This action cannot be
|
|
||||||
undone.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2 sm:justify-end">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
|
||||||
disabled={isDeleting}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={handleDeleteChat}
|
|
||||||
disabled={isDeleting}
|
|
||||||
className="gap-2"
|
|
||||||
>
|
|
||||||
{isDeleting ? (
|
|
||||||
<>
|
|
||||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
||||||
Deleting...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
Delete
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ import {
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useLLMPreferences } from "@/hooks/use-llm-configs";
|
import { useLLMPreferences } from "@/hooks/use-llm-configs";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { activeChatIdAtom } from "@/atoms/chats/queries/active-chat.atom";
|
import { activeChatIdAtom } from "@/atoms/chats/queries/active-chat.query.atom";
|
||||||
import { chatUIAtom } from "@/atoms/chats/active-chat-ui.atom";
|
import { chatUIAtom } from "@/atoms/chats/active-chat.atom";
|
||||||
import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/active-seach-space.atom";
|
import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/active-seach-space.atom";
|
||||||
|
|
||||||
export function DashboardClientLayout({
|
export function DashboardClientLayout({
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { atomWithMutation } from "jotai-tanstack-query";
|
||||||
|
import { deleteChat } from "@/lib/apis/chat-apis";
|
||||||
|
import { activeSearchSpaceIdAtom } from "../../seach-spaces/active-seach-space.atom";
|
||||||
|
import { queryClient } from "@/lib/query-client/client";
|
||||||
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
export const deleteChatMutationAtom = atomWithMutation((get) => {
|
||||||
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
|
const authToken = localStorage.getItem("surfsense_bearer_token");
|
||||||
|
|
||||||
|
return {
|
||||||
|
mutationKey: cacheKeys.activeSearchSpace.chats(searchSpaceId ?? ""),
|
||||||
|
enabled: !!searchSpaceId && !!authToken,
|
||||||
|
mutationFn: async (chatId: number) => {
|
||||||
|
if (!authToken) {
|
||||||
|
throw new Error("No authentication token found");
|
||||||
|
}
|
||||||
|
if (!searchSpaceId) {
|
||||||
|
throw new Error("No search space id found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleteChat(chatId, authToken);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Chat deleted successfully");
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: cacheKeys.activeSearchSpace.chats(searchSpaceId!),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -18,18 +18,8 @@ export const activeChatAtom = atomWithQuery<ActiveChatState>((get) => {
|
||||||
const activeChatId = get(activeChatIdAtom);
|
const activeChatId = get(activeChatIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = localStorage.getItem("surfsense_bearer_token");
|
||||||
|
|
||||||
if (!activeChatId) {
|
|
||||||
return {
|
|
||||||
queryKey: [],
|
|
||||||
enabled: false,
|
|
||||||
queryFn: async () => {
|
|
||||||
return { chatId: null, chatDetails: null, podcast: null };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: cacheKeys.activeSearchSpace.activeChat(activeChatId),
|
queryKey: cacheKeys.activeSearchSpace.activeChat(activeChatId ?? ""),
|
||||||
enabled: !!activeChatId && !!authToken,
|
enabled: !!activeChatId && !!authToken,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!authToken) {
|
if (!authToken) {
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { atomWithQuery } from "jotai-tanstack-query";
|
import { atomWithQuery } from "jotai-tanstack-query";
|
||||||
import { fetchChatsBySearchSpace } from "@/lib/apis/chat-apis";
|
import { fetchChatsBySearchSpace } from "@/lib/apis/chat-apis";
|
||||||
import { activeSearchSpaceIdAtom } from "../../seach-spaces/active-seach-space.atom";
|
import { activeSearchSpaceIdAtom } from "../../seach-spaces/active-seach-space.atom";
|
||||||
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
|
|
||||||
export const activeSearchSpaceChatsAtom = atomWithQuery((get) => {
|
export const activeSearchSpaceChatsAtom = atomWithQuery((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = localStorage.getItem("surfsense_bearer_token");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: ["chatsBySearchSpace", searchSpaceId],
|
queryKey: cacheKeys.activeSearchSpace.chats(searchSpaceId ?? ""),
|
||||||
enabled: !!searchSpaceId && !!authToken,
|
enabled: !!searchSpaceId && !!authToken,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!authToken) {
|
if (!authToken) {
|
||||||
|
|
@ -4,8 +4,8 @@ import { LoaderIcon, PanelRight, TriangleAlert } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { generatePodcast } from "@/lib/apis/podcast-apis";
|
import { generatePodcast } from "@/lib/apis/podcast-apis";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { activeChatAtom, activeChatIdAtom } from "@/atoms/chats/queries/active-chat.atom";
|
import { activeChatAtom, activeChatIdAtom } from "@/atoms/chats/queries/active-chat.query.atom";
|
||||||
import { chatUIAtom } from "@/atoms/chats/active-chat-ui.atom";
|
import { chatUIAtom } from "@/atoms/chats/active-chat.atom";
|
||||||
import { ChatPanelView } from "./ChatPanelView";
|
import { ChatPanelView } from "./ChatPanelView";
|
||||||
|
|
||||||
export interface GeneratePodcastRequest {
|
export interface GeneratePodcastRequest {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import { AlertCircle, Play, RefreshCw, Sparkles } from "lucide-react";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { activeChatAtom } from "@/atoms/chats/queries/active-chat.atom";
|
import { activeChatAtom } from "@/atoms/chats/queries/active-chat.query.atom";
|
||||||
import { chatUIAtom } from "@/atoms/chats/active-chat-ui.atom";
|
import { chatUIAtom } from "@/atoms/chats/active-chat.atom";
|
||||||
import { getPodcastStalenessMessage, isPodcastStale } from "../PodcastUtils";
|
import { getPodcastStalenessMessage, isPodcastStale } from "../PodcastUtils";
|
||||||
import type { GeneratePodcastRequest } from "./ChatPanelContainer";
|
import type { GeneratePodcastRequest } from "./ChatPanelContainer";
|
||||||
import { ConfigModal } from "./ConfigModal";
|
import { ConfigModal } from "./ConfigModal";
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useAtomValue } from "jotai";
|
||||||
import { Pencil } from "lucide-react";
|
import { Pencil } from "lucide-react";
|
||||||
import { useCallback, useContext, useState } from "react";
|
import { useCallback, useContext, useState } from "react";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { activeChatAtom } from "@/atoms/chats/queries/active-chat.atom";
|
import { activeChatAtom } from "@/atoms/chats/queries/active-chat.query.atom";
|
||||||
import type { GeneratePodcastRequest } from "./ChatPanelContainer";
|
import type { GeneratePodcastRequest } from "./ChatPanelContainer";
|
||||||
|
|
||||||
interface ConfigModalProps {
|
interface ConfigModalProps {
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,7 @@ export const deleteChat = async (chatId: number, authToken: string) => {
|
||||||
throw new Error(`Failed to delete chat: ${response.statusText}`);
|
throw new Error(`Failed to delete chat: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error deleting chat:", err);
|
console.error("Error deleting chat:", err);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,5 @@ export const cacheKeys = {
|
||||||
activeSearchSpace: {
|
activeSearchSpace: {
|
||||||
chats : (searchSpaceId: string) => ["active-search-space", "chats", searchSpaceId] as const,
|
chats : (searchSpaceId: string) => ["active-search-space", "chats", searchSpaceId] as const,
|
||||||
activeChat : (chatId: string) => ["active-search-space", "active-chat", chatId] as const,
|
activeChat : (chatId: string) => ["active-search-space", "active-chat", chatId] as const,
|
||||||
deleteChat : ( searchSpaceId: string, chatId: string) => ["active-search-space", "chats", searchSpaceId, "delete", chatId] as const,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue