mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
feat: add connector tools strip to DocumentsSidebar and implement AvatarGroup component for better UI representation
This commit is contained in:
parent
4ebf2359b5
commit
c8e36cb928
3 changed files with 78 additions and 11 deletions
|
|
@ -549,6 +549,15 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<ConnectorIndicator ref={connectorRef} showTrigger={false} />
|
<ConnectorIndicator ref={connectorRef} showTrigger={false} />
|
||||||
|
{sidebarDocs.length > 0 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setDocumentsSidebarOpen(true)}
|
||||||
|
className="rounded-full border border-border/60 bg-accent/50 px-2.5 py-1 text-xs font-medium text-foreground/80 transition-colors hover:bg-accent"
|
||||||
|
>
|
||||||
|
{sidebarDocs.length} {sidebarDocs.length === 1 ? "source" : "sources"} selected
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!hasModelConfigured && (
|
{!hasModelConfigured && (
|
||||||
|
|
@ -559,15 +568,6 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{sidebarDocs.length > 0 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setDocumentsSidebarOpen(true)}
|
|
||||||
className="rounded-full border border-border/60 bg-accent/50 px-2.5 py-1 text-xs font-medium text-foreground/80 transition-colors hover:bg-accent"
|
|
||||||
>
|
|
||||||
{sidebarDocs.length} {sidebarDocs.length === 1 ? "source" : "sources"} selected
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<AssistantIf condition={({ thread }) => !thread.isRunning}>
|
<AssistantIf condition={({ thread }) => !thread.isRunning}>
|
||||||
<ComposerPrimitive.Send asChild disabled={isSendDisabled}>
|
<ComposerPrimitive.Send asChild disabled={isSendDisabled}>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
@ -12,9 +12,12 @@ import {
|
||||||
type SortKey,
|
type SortKey,
|
||||||
} from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell";
|
} from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell";
|
||||||
import { sidebarSelectedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom";
|
import { sidebarSelectedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom";
|
||||||
|
import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms";
|
||||||
import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
|
import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||||
import { useDebouncedValue } from "@/hooks/use-debounced-value";
|
import { useDebouncedValue } from "@/hooks/use-debounced-value";
|
||||||
import { useDocumentSearch } from "@/hooks/use-document-search";
|
import { useDocumentSearch } from "@/hooks/use-document-search";
|
||||||
|
|
@ -22,6 +25,14 @@ import { useDocuments } from "@/hooks/use-documents";
|
||||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||||
|
|
||||||
|
const SHOWCASE_CONNECTORS = [
|
||||||
|
{ type: "GOOGLE_DRIVE_CONNECTOR", label: "Google Drive" },
|
||||||
|
{ type: "GOOGLE_GMAIL_CONNECTOR", label: "Gmail" },
|
||||||
|
{ type: "NOTION_CONNECTOR", label: "Notion" },
|
||||||
|
{ type: "YOUTUBE_CONNECTOR", label: "YouTube" },
|
||||||
|
{ type: "SLACK_CONNECTOR", label: "Slack" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
interface DocumentsSidebarProps {
|
interface DocumentsSidebarProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
|
|
@ -35,6 +46,7 @@ export function DocumentsSidebar({ open, onOpenChange, isDocked = false, onDocke
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isMobile = !useMediaQuery("(min-width: 640px)");
|
const isMobile = !useMediaQuery("(min-width: 640px)");
|
||||||
const searchSpaceId = Number(params.search_space_id);
|
const searchSpaceId = Number(params.search_space_id);
|
||||||
|
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
|
||||||
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const debouncedSearch = useDebouncedValue(search, 250);
|
const debouncedSearch = useDebouncedValue(search, 250);
|
||||||
|
|
@ -201,6 +213,38 @@ export function DocumentsSidebar({ open, onOpenChange, isDocked = false, onDocke
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Connected tools strip */}
|
||||||
|
<div className="shrink-0 mx-4 mb-3 flex items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setConnectorDialogOpen(true)}
|
||||||
|
className="flex items-center gap-2 min-w-0 flex-1 text-left"
|
||||||
|
>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
Connect your tools
|
||||||
|
</span>
|
||||||
|
<AvatarGroup className="ml-auto shrink-0">
|
||||||
|
{SHOWCASE_CONNECTORS.map(({ type, label }, i) => (
|
||||||
|
<Tooltip key={type}>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Avatar
|
||||||
|
className="size-6"
|
||||||
|
style={{ zIndex: SHOWCASE_CONNECTORS.length - i }}
|
||||||
|
>
|
||||||
|
<AvatarFallback className="bg-muted text-[10px]">
|
||||||
|
{getConnectorIcon(type, "size-3.5")}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" className="text-xs">
|
||||||
|
{label}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 overflow-x-hidden pt-0 flex flex-col">
|
<div className="flex-1 min-h-0 overflow-x-hidden pt-0 flex flex-col">
|
||||||
<div className="px-4 pb-2">
|
<div className="px-4 pb-2">
|
||||||
<DocumentsFilters
|
<DocumentsFilters
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,27 @@ function AvatarFallback({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback };
|
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="avatar-group"
|
||||||
|
className={cn("flex -space-x-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AvatarGroupCount({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="avatar-group-count"
|
||||||
|
className={cn(
|
||||||
|
"relative flex size-8 shrink-0 items-center justify-center rounded-full border-2 border-background bg-muted text-xs font-medium text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue