mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
feat: implement RightPanel component for tabbed navigation between Sources and Report; update report panel handling in dashboard chat page for improved user experience
This commit is contained in:
parent
8b468e06da
commit
414dceff2f
9 changed files with 259 additions and 87 deletions
|
|
@ -32,7 +32,7 @@ import { closeReportPanelAtom } from "@/atoms/chat/report-panel.atom";
|
|||
import { membersAtom } from "@/atoms/members/members-query.atoms";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import { Thread } from "@/components/assistant-ui/thread";
|
||||
import { ReportPanel } from "@/components/report-panel/report-panel";
|
||||
import { MobileReportPanel } from "@/components/report-panel/report-panel";
|
||||
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
|
||||
import { DisplayImageToolUI } from "@/components/tool-ui/display-image";
|
||||
import { GeneratePodcastToolUI } from "@/components/tool-ui/generate-podcast";
|
||||
|
|
@ -1668,7 +1668,7 @@ export default function NewChatPage() {
|
|||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<Thread messageThinkingSteps={messageThinkingSteps} />
|
||||
</div>
|
||||
<ReportPanel />
|
||||
<MobileReportPanel />
|
||||
</div>
|
||||
</AssistantRuntimeProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { atom } from "jotai";
|
||||
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
||||
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
|
||||
|
||||
interface ReportPanelState {
|
||||
isOpen: boolean;
|
||||
|
|
@ -44,15 +45,14 @@ export const openReportPanelAtom = atom(
|
|||
wordCount: wordCount ?? null,
|
||||
shareToken: shareToken ?? null,
|
||||
});
|
||||
set(documentsSidebarOpenAtom, false);
|
||||
set(rightPanelTabAtom, "report");
|
||||
set(rightPanelCollapsedAtom, false);
|
||||
set(documentsSidebarOpenAtom, true);
|
||||
}
|
||||
);
|
||||
|
||||
/** Action atom to close the report panel */
|
||||
export const closeReportPanelAtom = atom(null, (get, set) => {
|
||||
const wasOpen = get(reportPanelAtom).isOpen;
|
||||
export const closeReportPanelAtom = atom(null, (_get, set) => {
|
||||
set(reportPanelAtom, initialState);
|
||||
if (wasOpen) {
|
||||
set(documentsSidebarOpenAtom, true);
|
||||
}
|
||||
set(rightPanelTabAtom, "sources");
|
||||
});
|
||||
|
|
|
|||
8
surfsense_web/atoms/layout/right-panel.atom.ts
Normal file
8
surfsense_web/atoms/layout/right-panel.atom.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { atom } from "jotai";
|
||||
|
||||
export type RightPanelTab = "sources" | "report";
|
||||
|
||||
export const rightPanelTabAtom = atom<RightPanelTab>("sources");
|
||||
|
||||
/** Whether the right panel is collapsed (hidden but state preserved) */
|
||||
export const rightPanelCollapsedAtom = atom(false);
|
||||
|
|
@ -22,7 +22,6 @@ import {
|
|||
PlusIcon,
|
||||
RefreshCwIcon,
|
||||
SquareIcon,
|
||||
SquareLibrary,
|
||||
Unplug,
|
||||
Upload,
|
||||
X,
|
||||
|
|
@ -37,7 +36,6 @@ import {
|
|||
} from "@/atoms/chat/mentioned-documents.atom";
|
||||
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
|
||||
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
|
||||
import { documentTypeCountsAtom } from "@/atoms/documents/document-query.atoms";
|
||||
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
||||
import { membersAtom } from "@/atoms/members/members-query.atoms";
|
||||
import {
|
||||
|
|
@ -77,7 +75,6 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
|
|
@ -570,12 +567,6 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
const { openDialog: openUploadDialog } = useDocumentUploadDialog();
|
||||
const { data: connectors } = useAtomValue(connectorsAtom);
|
||||
const connectorCount = connectors?.length ?? 0;
|
||||
const { data: typeCounts } = useAtomValue(documentTypeCountsAtom);
|
||||
const totalDocuments = useMemo(
|
||||
() => (typeCounts ? Object.values(typeCounts).reduce((sum, n) => sum + n, 0) : 0),
|
||||
[typeCounts]
|
||||
);
|
||||
|
||||
const isComposerTextEmpty = useAssistantState(({ composer }) => {
|
||||
const text = composer.text?.trim() || "";
|
||||
return text.length === 0;
|
||||
|
|
@ -631,18 +622,6 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
<Upload className="size-4 shrink-0" />
|
||||
Upload files
|
||||
</DropdownMenuItem>
|
||||
{totalDocuments > 0 && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setAddMenuOpen(false);
|
||||
setDocumentsSidebarOpen(true);
|
||||
}}
|
||||
>
|
||||
<SquareLibrary className="size-4 shrink-0" />
|
||||
Manage Documents
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setAddMenuOpen(false);
|
||||
|
|
@ -650,7 +629,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
|||
}}
|
||||
>
|
||||
<Unplug className="size-4 shrink-0" />
|
||||
{connectorCount > 0 ? "Manage connectors" : "Connect your tools"}
|
||||
{connectorCount > 0 ? "Manage tools" : "Connect your tools"}
|
||||
{connectorCount > 0 && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">{connectorCount}</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
"use client";
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { PanelRight } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
|
||||
import { reportPanelAtom } from "@/atoms/chat/report-panel.atom";
|
||||
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
||||
import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
|
||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { ChatHeader } from "@/components/new-chat/chat-header";
|
||||
import { ChatShareButton } from "@/components/new-chat/chat-share-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence";
|
||||
|
||||
interface HeaderProps {
|
||||
|
|
@ -15,6 +22,7 @@ interface HeaderProps {
|
|||
export function Header({ mobileMenuTrigger }: HeaderProps) {
|
||||
const pathname = usePathname();
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isChatPage = pathname?.includes("/new-chat") ?? false;
|
||||
|
||||
|
|
@ -38,6 +46,13 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
|
|||
|
||||
const handleVisibilityChange = (_visibility: ChatVisibility) => {};
|
||||
|
||||
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
|
||||
const documentsOpen = useAtomValue(documentsSidebarOpenAtom);
|
||||
const reportState = useAtomValue(reportPanelAtom);
|
||||
const reportOpen = reportState.isOpen && !!reportState.reportId;
|
||||
const hasRightPanelContent = documentsOpen || reportOpen;
|
||||
const showExpandButton = !isMobile && collapsed && hasRightPanelContent;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60 px-4">
|
||||
{/* Left side - Mobile menu trigger + Model selector */}
|
||||
|
|
@ -49,10 +64,26 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
|
|||
</div>
|
||||
|
||||
{/* Right side - Actions */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{hasThread && (
|
||||
<ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} />
|
||||
)}
|
||||
{showExpandButton && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setCollapsed(false)}
|
||||
className="h-8 w-8 shrink-0"
|
||||
>
|
||||
<PanelRight className="h-4 w-4" />
|
||||
<span className="sr-only">Expand panel</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">Expand panel</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
|||
129
surfsense_web/components/layout/ui/right-panel/RightPanel.tsx
Normal file
129
surfsense_web/components/layout/ui/right-panel/RightPanel.tsx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
"use client";
|
||||
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { PanelRight, PanelRightClose } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom";
|
||||
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
|
||||
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
|
||||
import { ReportPanelContent } from "@/components/report-panel/report-panel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { DocumentsSidebar } from "../sidebar";
|
||||
|
||||
interface RightPanelProps {
|
||||
documentsPanel?: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
};
|
||||
}
|
||||
|
||||
function CollapseButton({ onClick }: { onClick: () => void }) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon" onClick={onClick} className="h-8 w-8 shrink-0">
|
||||
<PanelRightClose className="h-4 w-4" />
|
||||
<span className="sr-only">Collapse panel</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">Collapse panel</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolutely positioned expand button — renders at top-right of the main
|
||||
* container so it occupies the same screen position as the collapse button
|
||||
* inside the Documents header.
|
||||
*/
|
||||
export function RightPanelExpandButton() {
|
||||
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
|
||||
const documentsOpen = useAtomValue(documentsSidebarOpenAtom);
|
||||
const reportState = useAtomValue(reportPanelAtom);
|
||||
const reportOpen = reportState.isOpen && !!reportState.reportId;
|
||||
const hasContent = documentsOpen || reportOpen;
|
||||
|
||||
if (!collapsed || !hasContent) return null;
|
||||
|
||||
return (
|
||||
<div className="absolute top-4 right-4 z-20">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setCollapsed(false)}
|
||||
className="h-8 w-8 shrink-0"
|
||||
>
|
||||
<PanelRight className="h-4 w-4" />
|
||||
<span className="sr-only">Expand panel</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">Expand panel</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RightPanel({ documentsPanel }: RightPanelProps) {
|
||||
const [activeTab] = useAtom(rightPanelTabAtom);
|
||||
const reportState = useAtomValue(reportPanelAtom);
|
||||
const closeReport = useSetAtom(closeReportPanelAtom);
|
||||
const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom);
|
||||
|
||||
const documentsOpen = documentsPanel?.open ?? false;
|
||||
const reportOpen = reportState.isOpen && !!reportState.reportId;
|
||||
|
||||
// Close report on Escape key (works on Windows, macOS, and Linux)
|
||||
// Must be called before early returns to satisfy Rules of Hooks.
|
||||
useEffect(() => {
|
||||
if (!reportOpen) return;
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
closeReport();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [reportOpen, closeReport]);
|
||||
|
||||
if (!documentsOpen && !reportOpen) return null;
|
||||
if (collapsed) return null;
|
||||
|
||||
const effectiveTab =
|
||||
activeTab === "report" && !reportOpen
|
||||
? "sources"
|
||||
: activeTab === "sources" && !documentsOpen
|
||||
? "report"
|
||||
: activeTab;
|
||||
|
||||
const collapseButton = <CollapseButton onClick={() => setCollapsed(true)} />;
|
||||
|
||||
const panelWidth = effectiveTab === "sources" ? "w-[420px]" : "w-[640px]";
|
||||
|
||||
return (
|
||||
<aside className={`flex h-full ${panelWidth} shrink-0 flex-col border-l bg-background animate-in slide-in-from-right-4 duration-200 ease-out transition-[width] `}>
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
{effectiveTab === "sources" && documentsOpen && documentsPanel && (
|
||||
<DocumentsSidebar
|
||||
open={documentsPanel.open}
|
||||
onOpenChange={documentsPanel.onOpenChange}
|
||||
embedded
|
||||
headerAction={collapseButton}
|
||||
/>
|
||||
)}
|
||||
{effectiveTab === "report" && reportOpen && (
|
||||
<div className="flex h-full flex-col">
|
||||
<ReportPanelContent
|
||||
reportId={reportState.reportId!}
|
||||
title={reportState.title || "Report"}
|
||||
onClose={closeReport}
|
||||
shareToken={reportState.shareToken}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { useSidebarResize } from "../../hooks/useSidebarResize";
|
|||
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
|
||||
import { Header } from "../header";
|
||||
import { IconRail } from "../icon-rail";
|
||||
import { RightPanel } from "../right-panel/RightPanel";
|
||||
import {
|
||||
AllPrivateChatsSidebar,
|
||||
AllSharedChatsSidebar,
|
||||
|
|
@ -275,46 +276,36 @@ export function LayoutShell({
|
|||
|
||||
{/* Main container with sidebar and content - relative for inbox positioning */}
|
||||
<div className="relative flex flex-1 rounded-xl border bg-background overflow-hidden">
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggleCollapse={toggleCollapsed}
|
||||
navItems={navItems}
|
||||
onNavItemClick={onNavItemClick}
|
||||
chats={chats}
|
||||
sharedChats={sharedChats}
|
||||
activeChatId={activeChatId}
|
||||
onNewChat={onNewChat}
|
||||
onChatSelect={onChatSelect}
|
||||
onChatRename={onChatRename}
|
||||
onChatDelete={onChatDelete}
|
||||
onChatArchive={onChatArchive}
|
||||
onViewAllSharedChats={onViewAllSharedChats}
|
||||
onViewAllPrivateChats={onViewAllPrivateChats}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
onUserSettings={onUserSettings}
|
||||
onLogout={onLogout}
|
||||
pageUsage={pageUsage}
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
className="hidden md:flex border-r shrink-0"
|
||||
isLoadingChats={isLoadingChats}
|
||||
sidebarWidth={sidebarWidth}
|
||||
onResizeMouseDown={onResizeMouseDown}
|
||||
isResizing={isResizing}
|
||||
/>
|
||||
|
||||
{/* Docked Documents Sidebar - renders as flex sibling between sidebar and content */}
|
||||
{documentsPanel?.isDocked && (
|
||||
<DocumentsSidebar
|
||||
open={documentsPanel.open}
|
||||
onOpenChange={documentsPanel.onOpenChange}
|
||||
isDocked={documentsPanel.isDocked}
|
||||
onDockedChange={documentsPanel.onDockedChange}
|
||||
/>
|
||||
)}
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggleCollapse={toggleCollapsed}
|
||||
navItems={navItems}
|
||||
onNavItemClick={onNavItemClick}
|
||||
chats={chats}
|
||||
sharedChats={sharedChats}
|
||||
activeChatId={activeChatId}
|
||||
onNewChat={onNewChat}
|
||||
onChatSelect={onChatSelect}
|
||||
onChatRename={onChatRename}
|
||||
onChatDelete={onChatDelete}
|
||||
onChatArchive={onChatArchive}
|
||||
onViewAllSharedChats={onViewAllSharedChats}
|
||||
onViewAllPrivateChats={onViewAllPrivateChats}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
onUserSettings={onUserSettings}
|
||||
onLogout={onLogout}
|
||||
pageUsage={pageUsage}
|
||||
theme={theme}
|
||||
setTheme={setTheme}
|
||||
className="hidden md:flex border-r shrink-0"
|
||||
isLoadingChats={isLoadingChats}
|
||||
sidebarWidth={sidebarWidth}
|
||||
onResizeMouseDown={onResizeMouseDown}
|
||||
isResizing={isResizing}
|
||||
/>
|
||||
|
||||
<main className="flex-1 flex flex-col min-w-0">
|
||||
<Header />
|
||||
|
|
@ -324,6 +315,16 @@ export function LayoutShell({
|
|||
</div>
|
||||
</main>
|
||||
|
||||
{/* Right panel — tabbed Sources/Report (desktop only) */}
|
||||
{documentsPanel && (
|
||||
<RightPanel
|
||||
documentsPanel={{
|
||||
open: documentsPanel.open,
|
||||
onOpenChange: documentsPanel.onOpenChange,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Inbox Sidebar - slide-out panel */}
|
||||
{inbox && (
|
||||
<InboxSidebar
|
||||
|
|
@ -335,16 +336,6 @@ export function LayoutShell({
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* Documents Sidebar - floating slide-out panel (non-docked mode) */}
|
||||
{documentsPanel && !documentsPanel.isDocked && (
|
||||
<DocumentsSidebar
|
||||
open={documentsPanel.open}
|
||||
onOpenChange={documentsPanel.onOpenChange}
|
||||
isDocked={false}
|
||||
onDockedChange={documentsPanel.onDockedChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Announcements Sidebar */}
|
||||
{announcementsPanel && (
|
||||
<AnnouncementsSidebar
|
||||
|
|
|
|||
|
|
@ -30,7 +30,11 @@ const SHOWCASE_CONNECTORS = [
|
|||
{ type: "GOOGLE_GMAIL_CONNECTOR", label: "Gmail" },
|
||||
{ type: "NOTION_CONNECTOR", label: "Notion" },
|
||||
{ type: "YOUTUBE_CONNECTOR", label: "YouTube" },
|
||||
{ type: "GOOGLE_CALENDAR_CONNECTOR", label: "Google Calendar" },
|
||||
{ type: "SLACK_CONNECTOR", label: "Slack" },
|
||||
{ type: "LINEAR_CONNECTOR", label: "Linear" },
|
||||
{ type: "JIRA_CONNECTOR", label: "Jira" },
|
||||
{ type: "GITHUB_CONNECTOR", label: "GitHub" },
|
||||
] as const;
|
||||
|
||||
interface DocumentsSidebarProps {
|
||||
|
|
@ -38,6 +42,10 @@ interface DocumentsSidebarProps {
|
|||
onOpenChange: (open: boolean) => void;
|
||||
isDocked?: boolean;
|
||||
onDockedChange?: (docked: boolean) => void;
|
||||
/** When true, renders content without any wrapper — parent provides the container */
|
||||
embedded?: boolean;
|
||||
/** Optional action element rendered in the header row (e.g. collapse button) */
|
||||
headerAction?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DocumentsSidebar({
|
||||
|
|
@ -45,6 +53,8 @@ export function DocumentsSidebar({
|
|||
onOpenChange,
|
||||
isDocked = false,
|
||||
onDockedChange,
|
||||
embedded = false,
|
||||
headerAction,
|
||||
}: DocumentsSidebarProps) {
|
||||
const t = useTranslations("documents");
|
||||
const tSidebar = useTranslations("sidebar");
|
||||
|
|
@ -168,8 +178,8 @@ export function DocumentsSidebar({
|
|||
|
||||
const documentsContent = (
|
||||
<>
|
||||
<div className="shrink-0 p-4 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="shrink-0 flex h-14 items-center px-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{isMobile && (
|
||||
<Button
|
||||
|
|
@ -214,12 +224,13 @@ export function DocumentsSidebar({
|
|||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{headerAction}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connected tools strip */}
|
||||
<div className="shrink-0 mx-4 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2">
|
||||
<div className="shrink-0 mx-4 mt-2 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConnectorDialogOpen(true)}
|
||||
|
|
@ -278,6 +289,14 @@ export function DocumentsSidebar({
|
|||
</>
|
||||
);
|
||||
|
||||
if (embedded) {
|
||||
return (
|
||||
<div className="flex h-full flex-col bg-sidebar text-sidebar-foreground">
|
||||
{documentsContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDocked && open && !isMobile) {
|
||||
return (
|
||||
<aside
|
||||
|
|
|
|||
|
|
@ -95,9 +95,9 @@ function ReportPanelSkeleton() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Inner content component used by both desktop panel and mobile drawer
|
||||
* Inner content component used by desktop panel, mobile drawer, and the layout right panel
|
||||
*/
|
||||
function ReportPanelContent({
|
||||
export function ReportPanelContent({
|
||||
reportId,
|
||||
title,
|
||||
onClose,
|
||||
|
|
@ -579,3 +579,18 @@ export function ReportPanel() {
|
|||
|
||||
return <MobileReportDrawer />;
|
||||
}
|
||||
|
||||
/**
|
||||
* MobileReportPanel — mobile-only report drawer
|
||||
*
|
||||
* Used in the dashboard chat page where the desktop report is handled
|
||||
* by the layout-level RightPanel instead.
|
||||
*/
|
||||
export function MobileReportPanel() {
|
||||
const panelState = useAtomValue(reportPanelAtom);
|
||||
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
||||
|
||||
if (isDesktop || !panelState.isOpen || !panelState.reportId) return null;
|
||||
|
||||
return <MobileReportDrawer />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue