mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
feat(chats): implement ChatsPage component and update layout for all chats functionality
This commit is contained in:
parent
248e3aae57
commit
420ce3e153
7 changed files with 57 additions and 119 deletions
11
surfsense_web/app/dashboard/[workspace_id]/chats/page.tsx
Normal file
11
surfsense_web/app/dashboard/[workspace_id]/chats/page.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { AllChatsWorkspaceContent } from "@/components/layout/ui/sidebar";
|
||||
|
||||
export default async function ChatsPage({ params }: { params: Promise<{ workspace_id: string }> }) {
|
||||
const { workspace_id } = await params;
|
||||
|
||||
return (
|
||||
<div className="w-full select-none">
|
||||
<AllChatsWorkspaceContent searchSpaceId={workspace_id} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
});
|
||||
|
||||
// Unified slide-out panel state (only one can be open at a time)
|
||||
type SlideoutPanel = "inbox" | "chats" | null;
|
||||
type SlideoutPanel = "inbox" | null;
|
||||
const [activeSlideoutPanel, setActiveSlideoutPanel] = useState<SlideoutPanel>(null);
|
||||
|
||||
const isInboxSidebarOpen = activeSlideoutPanel === "inbox";
|
||||
|
|
@ -631,10 +631,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
}
|
||||
}, [router]);
|
||||
|
||||
const handleViewAllChats = useCallback(() => {
|
||||
setActiveSlideoutPanel((prev) => (prev === "chats" ? null : "chats"));
|
||||
}, []);
|
||||
|
||||
// Delete handlers
|
||||
const confirmDeleteChat = useCallback(async () => {
|
||||
if (!chatToDelete) return;
|
||||
|
|
@ -713,6 +709,13 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true;
|
||||
const isTeamPage = pathname?.endsWith("/team") === true;
|
||||
const isAutomationsPage = pathname?.includes("/automations") === true;
|
||||
const isAllChatsPage = pathname?.endsWith("/chats") === true;
|
||||
const handleViewAllChats = useCallback(() => {
|
||||
setActiveSlideoutPanel(null);
|
||||
router.push(
|
||||
isAllChatsPage ? `/dashboard/${searchSpaceId}/new-chat` : `/dashboard/${searchSpaceId}/chats`
|
||||
);
|
||||
}, [isAllChatsPage, router, searchSpaceId]);
|
||||
const useWorkspacePanel =
|
||||
pathname?.endsWith("/buy-more") === true ||
|
||||
pathname?.endsWith("/earn-credits") === true ||
|
||||
|
|
@ -720,7 +723,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
isUserSettingsPage ||
|
||||
isSearchSpaceSettingsPage ||
|
||||
isTeamPage ||
|
||||
isAutomationsPage;
|
||||
isAutomationsPage ||
|
||||
isAllChatsPage;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -757,18 +761,25 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
isChatPage={isChatPage}
|
||||
isAllChatsPage={isAllChatsPage}
|
||||
useWorkspacePanel={useWorkspacePanel}
|
||||
workspacePanelViewportClassName={
|
||||
isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage
|
||||
isUserSettingsPage ||
|
||||
isSearchSpaceSettingsPage ||
|
||||
isTeamPage ||
|
||||
isAutomationsPage ||
|
||||
isAllChatsPage
|
||||
? "items-start justify-center px-6 py-8 md:px-10 md:py-10"
|
||||
: undefined
|
||||
}
|
||||
workspacePanelContentClassName={
|
||||
isAutomationsPage
|
||||
? "max-w-none select-none"
|
||||
: isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage
|
||||
? "max-w-5xl"
|
||||
: undefined
|
||||
: isAllChatsPage
|
||||
? "max-w-3xl"
|
||||
: isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage
|
||||
? "max-w-5xl"
|
||||
: undefined
|
||||
}
|
||||
isLoadingChats={isLoadingThreads}
|
||||
activeSlideoutPanel={activeSlideoutPanel}
|
||||
|
|
@ -797,9 +808,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
|
|||
markAllAsRead: statusInbox.markAllAsRead,
|
||||
},
|
||||
}}
|
||||
allChatsPanel={{
|
||||
searchSpaceId,
|
||||
}}
|
||||
documentsPanel={{
|
||||
open: isDocumentsSidebarOpen,
|
||||
onOpenChange: setIsDocumentsSidebarOpen,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import {
|
|||
RightPanelToggleButton,
|
||||
} from "../right-panel/RightPanel";
|
||||
import {
|
||||
AllChatsSidebarContent,
|
||||
DocumentsSidebar,
|
||||
InboxSidebarContent,
|
||||
MobileSidebar,
|
||||
|
|
@ -93,7 +92,7 @@ interface TabDataSource {
|
|||
markAllAsRead: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export type ActiveSlideoutPanel = "inbox" | "chats" | null;
|
||||
export type ActiveSlideoutPanel = "inbox" | null;
|
||||
|
||||
// Inbox-related props — per-tab data sources with independent loading/pagination
|
||||
interface InboxProps {
|
||||
|
|
@ -134,6 +133,7 @@ interface LayoutShellProps {
|
|||
setTheme?: (theme: "light" | "dark" | "system") => void;
|
||||
defaultCollapsed?: boolean;
|
||||
isChatPage?: boolean;
|
||||
isAllChatsPage?: boolean;
|
||||
useWorkspacePanel?: boolean;
|
||||
workspacePanelViewportClassName?: string;
|
||||
workspacePanelContentClassName?: string;
|
||||
|
|
@ -145,10 +145,6 @@ interface LayoutShellProps {
|
|||
// Inbox props
|
||||
inbox?: InboxProps;
|
||||
isLoadingChats?: boolean;
|
||||
// All chats panel props
|
||||
allChatsPanel?: {
|
||||
searchSpaceId: string;
|
||||
};
|
||||
documentsPanel?: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
|
|
@ -245,6 +241,7 @@ export function LayoutShell({
|
|||
setTheme,
|
||||
defaultCollapsed = false,
|
||||
isChatPage = false,
|
||||
isAllChatsPage = false,
|
||||
useWorkspacePanel = false,
|
||||
workspacePanelViewportClassName,
|
||||
workspacePanelContentClassName,
|
||||
|
|
@ -254,7 +251,6 @@ export function LayoutShell({
|
|||
onSlideoutPanelChange,
|
||||
inbox,
|
||||
isLoadingChats = false,
|
||||
allChatsPanel,
|
||||
documentsPanel,
|
||||
onTabSwitch,
|
||||
onTabPrefetch,
|
||||
|
|
@ -285,8 +281,7 @@ export function LayoutShell({
|
|||
|
||||
const anySlideOutOpen = activeSlideoutPanel !== null;
|
||||
|
||||
const panelAriaLabel =
|
||||
activeSlideoutPanel === "inbox" ? "Inbox" : activeSlideoutPanel === "chats" ? "Chats" : "Panel";
|
||||
const panelAriaLabel = activeSlideoutPanel === "inbox" ? "Inbox" : "Panel";
|
||||
|
||||
// Mobile layout
|
||||
if (isMobile) {
|
||||
|
|
@ -317,7 +312,7 @@ export function LayoutShell({
|
|||
onChatDelete={onChatDelete}
|
||||
onChatArchive={onChatArchive}
|
||||
onViewAllChats={onViewAllChats}
|
||||
isChatsPanelOpen={activeSlideoutPanel === "chats"}
|
||||
isAllChatsActive={isAllChatsPage}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
|
|
@ -369,22 +364,6 @@ export function LayoutShell({
|
|||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "chats" && allChatsPanel && (
|
||||
<motion.div
|
||||
key="chats"
|
||||
className="h-full flex flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
<AllChatsSidebarContent
|
||||
onOpenChange={(open) => closeSlideout(open)}
|
||||
searchSpaceId={allChatsPanel.searchSpaceId}
|
||||
onCloseMobileSidebar={() => setMobileMenuOpen(false)}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</SidebarSlideOutPanel>
|
||||
|
||||
|
|
@ -460,7 +439,7 @@ export function LayoutShell({
|
|||
onChatDelete={onChatDelete}
|
||||
onChatArchive={onChatArchive}
|
||||
onViewAllChats={onViewAllChats}
|
||||
isChatsPanelOpen={activeSlideoutPanel === "chats"}
|
||||
isAllChatsActive={isAllChatsPage}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
|
|
@ -526,21 +505,6 @@ export function LayoutShell({
|
|||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
{activeSlideoutPanel === "chats" && allChatsPanel && (
|
||||
<motion.div
|
||||
key="chats"
|
||||
className="h-full flex flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
>
|
||||
<AllChatsSidebarContent
|
||||
onOpenChange={(open) => closeSlideout(open)}
|
||||
searchSpaceId={allChatsPanel.searchSpaceId}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</SidebarSlideOutPanel>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|||
import { useSetAtom } from "jotai";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ChevronLeft,
|
||||
MessageCircleMore,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
|
|
@ -48,23 +47,13 @@ import { useArchiveThread, useDeleteThread, useRenameThread } from "@/hooks/use-
|
|||
import { fetchThreads, searchThreads, type ThreadListItem } from "@/lib/chat/thread-persistence";
|
||||
import { formatThreadTimestamp } from "@/lib/format-date";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
|
||||
export interface AllChatsSidebarContentProps {
|
||||
onOpenChange: (open: boolean) => void;
|
||||
interface AllChatsContentProps {
|
||||
searchSpaceId: string;
|
||||
onCloseMobileSidebar?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface AllChatsSidebarProps extends AllChatsSidebarContentProps {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
export function AllChatsSidebarContent({
|
||||
onOpenChange,
|
||||
searchSpaceId,
|
||||
onCloseMobileSidebar,
|
||||
}: AllChatsSidebarContentProps) {
|
||||
function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
|
||||
const t = useTranslations("sidebar");
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
|
@ -150,10 +139,8 @@ export function AllChatsSidebarContent({
|
|||
searchSpaceId,
|
||||
visibility: thread.visibility,
|
||||
});
|
||||
onOpenChange(false);
|
||||
onCloseMobileSidebar?.();
|
||||
},
|
||||
[activateChatThread, onOpenChange, searchSpaceId, onCloseMobileSidebar]
|
||||
[activateChatThread, searchSpaceId]
|
||||
);
|
||||
|
||||
const handleDeleteThread = useCallback(
|
||||
|
|
@ -165,7 +152,6 @@ export function AllChatsSidebarContent({
|
|||
toast.success(t("chat_deleted") || "Chat deleted successfully");
|
||||
|
||||
if (currentChatId === threadId) {
|
||||
onOpenChange(false);
|
||||
setTimeout(() => {
|
||||
if (
|
||||
fallbackTab?.type === "chat" &&
|
||||
|
|
@ -196,16 +182,7 @@ export function AllChatsSidebarContent({
|
|||
setDeletingThreadId(null);
|
||||
}
|
||||
},
|
||||
[
|
||||
activateChatThread,
|
||||
deleteThread,
|
||||
t,
|
||||
currentChatId,
|
||||
router,
|
||||
onOpenChange,
|
||||
removeChatTab,
|
||||
searchSpaceId,
|
||||
]
|
||||
[activateChatThread, deleteThread, t, currentChatId, router, removeChatTab, searchSpaceId]
|
||||
);
|
||||
|
||||
const handleToggleArchive = useCallback(
|
||||
|
|
@ -266,20 +243,9 @@ export function AllChatsSidebarContent({
|
|||
const archivedCount = archivedChats.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("flex h-full min-h-0 flex-col", className)}>
|
||||
<div className="shrink-0 p-3 pb-1.5 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full text-muted-foreground hover:text-accent-foreground"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span className="sr-only">{t("close") || "Close"}</span>
|
||||
</Button>
|
||||
)}
|
||||
<h2 className="text-lg font-semibold">{t("chats") || "Chats"}</h2>
|
||||
</div>
|
||||
|
||||
|
|
@ -584,25 +550,14 @@ export function AllChatsSidebarContent({
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AllChatsSidebar({
|
||||
open,
|
||||
onOpenChange,
|
||||
searchSpaceId,
|
||||
onCloseMobileSidebar,
|
||||
}: AllChatsSidebarProps) {
|
||||
const t = useTranslations("sidebar");
|
||||
|
||||
export function AllChatsWorkspaceContent({ searchSpaceId }: { searchSpaceId: string }) {
|
||||
return (
|
||||
<SidebarSlideOutPanel open={open} onOpenChange={onOpenChange} ariaLabel={t("chats") || "Chats"}>
|
||||
<AllChatsSidebarContent
|
||||
onOpenChange={onOpenChange}
|
||||
searchSpaceId={searchSpaceId}
|
||||
onCloseMobileSidebar={onCloseMobileSidebar}
|
||||
/>
|
||||
</SidebarSlideOutPanel>
|
||||
<div className="flex h-[min(760px,calc(100vh-8rem))] w-full overflow-hidden rounded-xl border bg-sidebar text-sidebar-foreground shadow-sm">
|
||||
<AllChatsContent searchSpaceId={searchSpaceId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ interface MobileSidebarProps {
|
|||
onChatDelete?: (chat: ChatItem) => void;
|
||||
onChatArchive?: (chat: ChatItem) => void;
|
||||
onViewAllChats?: () => void;
|
||||
isChatsPanelOpen?: boolean;
|
||||
isAllChatsActive?: boolean;
|
||||
user: User;
|
||||
onSettings?: () => void;
|
||||
onManageMembers?: () => void;
|
||||
|
|
@ -75,7 +75,7 @@ export function MobileSidebar({
|
|||
onChatDelete,
|
||||
onChatArchive,
|
||||
onViewAllChats,
|
||||
isChatsPanelOpen = false,
|
||||
isAllChatsActive = false,
|
||||
user,
|
||||
onSettings,
|
||||
onManageMembers,
|
||||
|
|
@ -166,7 +166,7 @@ export function MobileSidebar({
|
|||
}
|
||||
: undefined
|
||||
}
|
||||
isChatsPanelOpen={isChatsPanelOpen}
|
||||
isAllChatsActive={isAllChatsActive}
|
||||
user={user}
|
||||
onSettings={
|
||||
onSettings
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ interface SidebarProps {
|
|||
onChatDelete?: (chat: ChatItem) => void;
|
||||
onChatArchive?: (chat: ChatItem) => void;
|
||||
onViewAllChats?: () => void;
|
||||
isChatsPanelOpen?: boolean;
|
||||
isAllChatsActive?: boolean;
|
||||
user: User;
|
||||
onSettings?: () => void;
|
||||
onManageMembers?: () => void;
|
||||
|
|
@ -112,7 +112,7 @@ export function Sidebar({
|
|||
onChatDelete,
|
||||
onChatArchive,
|
||||
onViewAllChats,
|
||||
isChatsPanelOpen = false,
|
||||
isAllChatsActive = false,
|
||||
user,
|
||||
onSettings,
|
||||
onManageMembers,
|
||||
|
|
@ -281,7 +281,7 @@ export function Sidebar({
|
|||
title={t("recents")}
|
||||
defaultOpen={true}
|
||||
fillHeight={true}
|
||||
alwaysShowAction={!disableTooltips && isChatsPanelOpen}
|
||||
alwaysShowAction={!disableTooltips && isAllChatsActive}
|
||||
action={
|
||||
onViewAllChats ? (
|
||||
<Button
|
||||
|
|
@ -290,7 +290,7 @@ export function Sidebar({
|
|||
onClick={onViewAllChats}
|
||||
className="h-auto cursor-pointer whitespace-nowrap bg-transparent p-0 text-xs font-medium text-muted-foreground/60 transition-colors hover:bg-transparent hover:text-muted-foreground"
|
||||
>
|
||||
{!disableTooltips && isChatsPanelOpen ? t("hide") : t("show_all")}
|
||||
{!disableTooltips && isAllChatsActive ? t("hide") : t("show_all")}
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export { AllChatsSidebar, AllChatsSidebarContent } from "./AllChatsSidebar";
|
||||
export { AllChatsWorkspaceContent } from "./AllChatsSidebar";
|
||||
export { ChatListItem } from "./ChatListItem";
|
||||
export { CreditBalanceDisplay } from "./CreditBalanceDisplay";
|
||||
export { DocumentsSidebar } from "./DocumentsSidebar";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue