mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-06-25 11:18:43 +00:00
Made enhancements to the library interface!
1. Search Functionality: -Added a search box with search icon and "Search your threads..." placeholder -Real-time filtering of threads as you type -Clear button (X) when text is entered 2. Thread Count Display: -Added "You have X threads in Perplexica" below the search box -Only shows in normal mode (hidden during selection) 3. Multiple delete functionality: -"Select" button in the top right below Search Box -Checkboxes that appear on hover and when in selection mode -Selection mode header showing count and actions -When in selection mode, shows "X selected thread(s)" on the left -Action buttons (Select all, Cancel, Delete Selected) on the right -Disabled Delete Selected button when no threads are selected -Confirmation dialog using the new BatchDeleteChats component 4. Terminology Update: -Changed all instances of "chats" to "threads" throughout the interface
This commit is contained in:
@ -1,10 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import DeleteChat from '@/components/DeleteChat';
|
||||
import BatchDeleteChats from '@/components/BatchDeleteChats';
|
||||
import { cn, formatTimeDifference } from '@/lib/utils';
|
||||
import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react';
|
||||
import { BookOpenText, Check, ClockIcon, Search, Trash, X } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export interface Chat {
|
||||
id: string;
|
||||
@ -15,7 +17,13 @@ export interface Chat {
|
||||
|
||||
const Page = () => {
|
||||
const [chats, setChats] = useState<Chat[]>([]);
|
||||
const [filteredChats, setFilteredChats] = useState<Chat[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectionMode, setSelectionMode] = useState(false);
|
||||
const [selectedChats, setSelectedChats] = useState<string[]>([]);
|
||||
const [hoveredChatId, setHoveredChatId] = useState<string | null>(null);
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchChats = async () => {
|
||||
@ -31,12 +39,71 @@ const Page = () => {
|
||||
const data = await res.json();
|
||||
|
||||
setChats(data.chats);
|
||||
setFilteredChats(data.chats);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
fetchChats();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery.trim() === '') {
|
||||
setFilteredChats(chats);
|
||||
} else {
|
||||
const filtered = chats.filter((chat) =>
|
||||
chat.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
setFilteredChats(filtered);
|
||||
}
|
||||
}, [searchQuery, chats]);
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
setSearchQuery('');
|
||||
};
|
||||
|
||||
const toggleSelectionMode = () => {
|
||||
setSelectionMode(!selectionMode);
|
||||
setSelectedChats([]);
|
||||
};
|
||||
|
||||
const toggleChatSelection = (chatId: string) => {
|
||||
if (selectedChats.includes(chatId)) {
|
||||
setSelectedChats(selectedChats.filter(id => id !== chatId));
|
||||
} else {
|
||||
setSelectedChats([...selectedChats, chatId]);
|
||||
}
|
||||
};
|
||||
|
||||
const selectAllChats = () => {
|
||||
if (selectedChats.length === filteredChats.length) {
|
||||
setSelectedChats([]);
|
||||
} else {
|
||||
setSelectedChats(filteredChats.map(chat => chat.id));
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSelectedChats = () => {
|
||||
if (selectedChats.length === 0) return;
|
||||
setIsDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleBatchDeleteComplete = () => {
|
||||
setSelectedChats([]);
|
||||
setSelectionMode(false);
|
||||
};
|
||||
|
||||
const updateChatsAfterDelete = (newChats: Chat[]) => {
|
||||
setChats(newChats);
|
||||
setFilteredChats(newChats.filter(chat =>
|
||||
searchQuery.trim() === '' ||
|
||||
chat.title.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
));
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<div className="flex flex-row items-center justify-center min-h-screen">
|
||||
<svg
|
||||
@ -64,32 +131,145 @@ const Page = () => {
|
||||
<h1 className="text-3xl font-medium p-2">Library</h1>
|
||||
</div>
|
||||
<hr className="border-t border-[#2B2C2C] my-4 w-full" />
|
||||
|
||||
{/* Search Box */}
|
||||
<div className="relative mt-6 mb-6">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<Search className="w-5 h-5 text-black/50 dark:text-white/50" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
className="block w-full p-2 pl-10 pr-10 bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 rounded-md text-black dark:text-white focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
placeholder="Search your threads..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={clearSearch}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
>
|
||||
<X className="w-5 h-5 text-black/50 dark:text-white/50 hover:text-black dark:hover:text-white" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Thread Count and Selection Controls */}
|
||||
<div className="mb-4">
|
||||
{!selectionMode ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-black/70 dark:text-white/70">
|
||||
You have {chats.length} threads in Perplexica
|
||||
</div>
|
||||
<button
|
||||
onClick={toggleSelectionMode}
|
||||
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-black/70 dark:text-white/70">
|
||||
{selectedChats.length} selected thread{selectedChats.length !== 1 ? 's' : ''}
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<button
|
||||
onClick={selectAllChats}
|
||||
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||
>
|
||||
{selectedChats.length === filteredChats.length ? 'Deselect all' : 'Select all'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={toggleSelectionMode}
|
||||
className="text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white text-sm transition duration-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={deleteSelectedChats}
|
||||
disabled={selectedChats.length === 0}
|
||||
className={cn(
|
||||
"text-sm transition duration-200",
|
||||
selectedChats.length === 0
|
||||
? "text-red-400/50 hover:text-red-500/50 cursor-not-allowed"
|
||||
: "text-red-400 hover:text-red-500 cursor-pointer"
|
||||
)}
|
||||
>
|
||||
Delete Selected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{chats.length === 0 && (
|
||||
<div className="flex flex-row items-center justify-center min-h-screen">
|
||||
|
||||
{filteredChats.length === 0 && (
|
||||
<div className="flex flex-row items-center justify-center min-h-[50vh]">
|
||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||
No chats found.
|
||||
{searchQuery ? 'No threads found matching your search.' : 'No threads found.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{chats.length > 0 && (
|
||||
|
||||
{filteredChats.length > 0 && (
|
||||
<div className="flex flex-col pb-20 lg:pb-2">
|
||||
{chats.map((chat, i) => (
|
||||
{filteredChats.map((chat, i) => (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-4 py-6',
|
||||
i !== chats.length - 1
|
||||
i !== filteredChats.length - 1
|
||||
? 'border-b border-white-200 dark:border-dark-200'
|
||||
: '',
|
||||
)}
|
||||
key={i}
|
||||
onMouseEnter={() => setHoveredChatId(chat.id)}
|
||||
onMouseLeave={() => setHoveredChatId(null)}
|
||||
>
|
||||
<Link
|
||||
href={`/c/${chat.id}`}
|
||||
className="text-black dark:text-white lg:text-xl font-medium truncate transition duration-200 hover:text-[#24A0ED] dark:hover:text-[#24A0ED] cursor-pointer"
|
||||
>
|
||||
{chat.title}
|
||||
</Link>
|
||||
<div className="flex items-center">
|
||||
{/* Checkbox - visible when in selection mode or when hovering */}
|
||||
{(selectionMode || hoveredChatId === chat.id) && (
|
||||
<div
|
||||
className="mr-3 cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (!selectionMode) setSelectionMode(true);
|
||||
toggleChatSelection(chat.id);
|
||||
}}
|
||||
>
|
||||
<div className={cn(
|
||||
"w-5 h-5 border rounded flex items-center justify-center transition-colors",
|
||||
selectedChats.includes(chat.id)
|
||||
? "bg-blue-500 border-blue-500"
|
||||
: "border-gray-400 dark:border-gray-600"
|
||||
)}>
|
||||
{selectedChats.includes(chat.id) && (
|
||||
<Check className="w-4 h-4 text-white" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat Title */}
|
||||
<Link
|
||||
href={`/c/${chat.id}`}
|
||||
className={cn(
|
||||
"text-black dark:text-white lg:text-xl font-medium truncate transition duration-200 hover:text-[#24A0ED] dark:hover:text-[#24A0ED] cursor-pointer",
|
||||
selectionMode && "pointer-events-none text-black dark:text-white hover:text-black dark:hover:text-white"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
if (selectionMode) {
|
||||
e.preventDefault();
|
||||
toggleChatSelection(chat.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{chat.title}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-center justify-between w-full">
|
||||
<div className="flex flex-row items-center space-x-1 lg:space-x-1.5 text-black/70 dark:text-white/70">
|
||||
<ClockIcon size={15} />
|
||||
@ -97,16 +277,30 @@ const Page = () => {
|
||||
{formatTimeDifference(new Date(), chat.createdAt)} Ago
|
||||
</p>
|
||||
</div>
|
||||
<DeleteChat
|
||||
chatId={chat.id}
|
||||
chats={chats}
|
||||
setChats={setChats}
|
||||
/>
|
||||
|
||||
{/* Delete button - only visible when not in selection mode */}
|
||||
{!selectionMode && (
|
||||
<DeleteChat
|
||||
chatId={chat.id}
|
||||
chats={chats}
|
||||
setChats={updateChatsAfterDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Batch Delete Confirmation Dialog */}
|
||||
<BatchDeleteChats
|
||||
chatIds={selectedChats}
|
||||
chats={chats}
|
||||
setChats={updateChatsAfterDelete}
|
||||
onComplete={handleBatchDeleteComplete}
|
||||
isOpen={isDeleteDialogOpen}
|
||||
setIsOpen={setIsDeleteDialogOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user