mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 08:46:22 +02:00
Merge upstream/dev
This commit is contained in:
commit
440762fb07
92 changed files with 3227 additions and 2502 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { format } from "date-fns";
|
||||
import { useSetAtom } from "jotai";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ChevronLeft,
|
||||
|
|
@ -18,6 +19,7 @@ import { useParams, useRouter } from "next/navigation";
|
|||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { removeChatTabAtom } from "@/atoms/tabs/tabs.atom";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/animated-tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -70,6 +72,7 @@ export function AllPrivateChatsSidebarContent({
|
|||
const params = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
const isMobile = useIsMobile();
|
||||
const removeChatTab = useSetAtom(removeChatTabAtom);
|
||||
|
||||
const currentChatId = Array.isArray(params.chat_id)
|
||||
? Number(params.chat_id[0])
|
||||
|
|
@ -158,6 +161,7 @@ export function AllPrivateChatsSidebarContent({
|
|||
setDeletingThreadId(threadId);
|
||||
try {
|
||||
await deleteThread(threadId);
|
||||
const fallbackTab = removeChatTab(threadId);
|
||||
toast.success(t("chat_deleted") || "Chat deleted successfully");
|
||||
queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] });
|
||||
|
|
@ -166,6 +170,10 @@ export function AllPrivateChatsSidebarContent({
|
|||
if (currentChatId === threadId) {
|
||||
onOpenChange(false);
|
||||
setTimeout(() => {
|
||||
if (fallbackTab?.type === "chat" && fallbackTab.chatUrl) {
|
||||
router.push(fallbackTab.chatUrl);
|
||||
return;
|
||||
}
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
}, 250);
|
||||
}
|
||||
|
|
@ -176,7 +184,7 @@ export function AllPrivateChatsSidebarContent({
|
|||
setDeletingThreadId(null);
|
||||
}
|
||||
},
|
||||
[queryClient, searchSpaceId, t, currentChatId, router, onOpenChange]
|
||||
[queryClient, searchSpaceId, t, currentChatId, router, onOpenChange, removeChatTab]
|
||||
);
|
||||
|
||||
const handleToggleArchive = useCallback(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { format } from "date-fns";
|
||||
import { useSetAtom } from "jotai";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ChevronLeft,
|
||||
|
|
@ -18,6 +19,7 @@ import { useParams, useRouter } from "next/navigation";
|
|||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { removeChatTabAtom } from "@/atoms/tabs/tabs.atom";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/animated-tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -70,6 +72,7 @@ export function AllSharedChatsSidebarContent({
|
|||
const params = useParams();
|
||||
const queryClient = useQueryClient();
|
||||
const isMobile = useIsMobile();
|
||||
const removeChatTab = useSetAtom(removeChatTabAtom);
|
||||
|
||||
const currentChatId = Array.isArray(params.chat_id)
|
||||
? Number(params.chat_id[0])
|
||||
|
|
@ -158,6 +161,7 @@ export function AllSharedChatsSidebarContent({
|
|||
setDeletingThreadId(threadId);
|
||||
try {
|
||||
await deleteThread(threadId);
|
||||
const fallbackTab = removeChatTab(threadId);
|
||||
toast.success(t("chat_deleted") || "Chat deleted successfully");
|
||||
queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] });
|
||||
|
|
@ -166,6 +170,10 @@ export function AllSharedChatsSidebarContent({
|
|||
if (currentChatId === threadId) {
|
||||
onOpenChange(false);
|
||||
setTimeout(() => {
|
||||
if (fallbackTab?.type === "chat" && fallbackTab.chatUrl) {
|
||||
router.push(fallbackTab.chatUrl);
|
||||
return;
|
||||
}
|
||||
router.push(`/dashboard/${searchSpaceId}/new-chat`);
|
||||
}, 250);
|
||||
}
|
||||
|
|
@ -176,7 +184,7 @@ export function AllSharedChatsSidebarContent({
|
|||
setDeletingThreadId(null);
|
||||
}
|
||||
},
|
||||
[queryClient, searchSpaceId, t, currentChatId, router, onOpenChange]
|
||||
[queryClient, searchSpaceId, t, currentChatId, router, onOpenChange, removeChatTab]
|
||||
);
|
||||
|
||||
const handleToggleArchive = useCallback(
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
|
|||
import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
|
||||
import { expandedFolderIdsAtom } from "@/atoms/documents/folder.atoms";
|
||||
import { agentCreatedDocumentsAtom } from "@/atoms/documents/ui.atoms";
|
||||
import { openEditorPanelAtom } from "@/atoms/editor/editor-panel.atom";
|
||||
import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom";
|
||||
import { openDocumentTabAtom } from "@/atoms/tabs/tabs.atom";
|
||||
import { CreateFolderDialog } from "@/components/documents/CreateFolderDialog";
|
||||
import type { DocumentNodeDoc } from "@/components/documents/DocumentNode";
|
||||
import type { FolderDisplay } from "@/components/documents/FolderNode";
|
||||
|
|
@ -35,21 +35,12 @@ import {
|
|||
} from "@/components/ui/alert-dialog";
|
||||
import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerHandle,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from "@/components/ui/drawer";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||
import { useDebouncedValue } from "@/hooks/use-debounced-value";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||
import { foldersApiService } from "@/lib/apis/folders-api.service";
|
||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { queries } from "@/zero/queries/index";
|
||||
|
|
@ -95,12 +86,10 @@ export function DocumentsSidebar({
|
|||
const searchSpaceId = Number(params.search_space_id);
|
||||
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
|
||||
const setRightPanelCollapsed = useSetAtom(rightPanelCollapsedAtom);
|
||||
const openDocumentTab = useSetAtom(openDocumentTabAtom);
|
||||
const openEditorPanel = useSetAtom(openEditorPanelAtom);
|
||||
const { data: connectors } = useAtomValue(connectorsAtom);
|
||||
const connectorCount = connectors?.length ?? 0;
|
||||
|
||||
const isMobileLayout = useIsMobile();
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const debouncedSearch = useDebouncedValue(search, 250);
|
||||
const [activeTypes, setActiveTypes] = useState<DocumentTypeEnum[]>([]);
|
||||
|
|
@ -374,31 +363,6 @@ export function DocumentsSidebar({
|
|||
[]
|
||||
);
|
||||
|
||||
// Document popup viewer state (for tree view "Open" and mobile preview)
|
||||
const [viewingDoc, setViewingDoc] = useState<DocumentNodeDoc | null>(null);
|
||||
const [viewingContent, setViewingContent] = useState<string>("");
|
||||
const [viewingLoading, setViewingLoading] = useState(false);
|
||||
|
||||
const handleViewDocumentPopup = useCallback(async (doc: DocumentNodeDoc) => {
|
||||
setViewingDoc(doc);
|
||||
setViewingLoading(true);
|
||||
try {
|
||||
const fullDoc = await documentsApiService.getDocument({ id: doc.id });
|
||||
setViewingContent(fullDoc.content);
|
||||
} catch (err) {
|
||||
console.error("[DocumentsSidebar] Failed to fetch document content:", err);
|
||||
setViewingContent("Failed to load document content.");
|
||||
} finally {
|
||||
setViewingLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleCloseViewer = useCallback(() => {
|
||||
setViewingDoc(null);
|
||||
setViewingContent("");
|
||||
setViewingLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleToggleChatMention = useCallback(
|
||||
(doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => {
|
||||
if (isMentioned) {
|
||||
|
|
@ -557,7 +521,7 @@ export function DocumentsSidebar({
|
|||
|
||||
const documentsContent = (
|
||||
<>
|
||||
<div className="shrink-0 flex h-14 items-center px-4">
|
||||
<div className="shrink-0 flex h-12 items-center px-4">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{isMobile && (
|
||||
|
|
@ -609,7 +573,7 @@ export function DocumentsSidebar({
|
|||
</div>
|
||||
|
||||
{/* Connected tools strip */}
|
||||
<div className="shrink-0 mx-4 mt-2 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 transition-colors hover:bg-muted/80">
|
||||
<div className="shrink-0 mx-4 mt-4 mb-4 flex select-none items-center gap-2 rounded-lg border bg-muted/50 transition-colors hover:bg-muted/80">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConnectorDialogOpen(true)}
|
||||
|
|
@ -716,24 +680,18 @@ export function DocumentsSidebar({
|
|||
onCreateFolder={handleCreateFolder}
|
||||
searchQuery={debouncedSearch.trim() || undefined}
|
||||
onPreviewDocument={(doc) => {
|
||||
if (isMobileLayout) {
|
||||
handleViewDocumentPopup(doc);
|
||||
} else {
|
||||
openDocumentTab({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}}
|
||||
onEditDocument={(doc) => {
|
||||
if (!isMobileLayout) {
|
||||
openDocumentTab({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
title: doc.title,
|
||||
});
|
||||
}}
|
||||
onDeleteDocument={(doc) => handleDeleteDocument(doc.id)}
|
||||
onMoveDocument={handleMoveDocument}
|
||||
|
|
@ -761,26 +719,6 @@ export function DocumentsSidebar({
|
|||
onConfirm={handleCreateFolderConfirm}
|
||||
/>
|
||||
|
||||
<Drawer open={!!viewingDoc} onOpenChange={(open) => !open && handleCloseViewer()}>
|
||||
<DrawerContent className="max-h-[85vh] flex flex-col">
|
||||
<DrawerHandle />
|
||||
<DrawerHeader className="text-left shrink-0">
|
||||
<DrawerTitle className="text-base leading-tight break-words">
|
||||
{viewingDoc?.title}
|
||||
</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<div className="overflow-y-auto flex-1 min-h-0 px-4 pb-6 select-text text-xs [&_h1]:text-base! [&_h1]:mt-3! [&_h2]:text-sm! [&_h2]:mt-2! [&_h3]:text-xs! [&_h3]:mt-2!">
|
||||
{viewingLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Spinner size="lg" className="text-muted-foreground" />
|
||||
</div>
|
||||
) : (
|
||||
<MarkdownViewer content={viewingContent} />
|
||||
)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<AlertDialog
|
||||
open={bulkDeleteConfirmOpen}
|
||||
onOpenChange={(open) => !open && !isBulkDeleting && setBulkDeleteConfirmOpen(false)}
|
||||
|
|
@ -807,9 +745,10 @@ export function DocumentsSidebar({
|
|||
handleBulkDeleteSelected();
|
||||
}}
|
||||
disabled={isBulkDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
className="relative bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isBulkDeleting ? <Spinner size="sm" /> : "Delete"}
|
||||
<span className={isBulkDeleting ? "opacity-0" : ""}>Delete</span>
|
||||
{isBulkDeleting && <Spinner size="sm" className="absolute" />}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export function Sidebar({
|
|||
>
|
||||
{/* Header - search space name or collapse button when collapsed */}
|
||||
{isCollapsed ? (
|
||||
<div className="flex h-14 shrink-0 items-center justify-center border-b">
|
||||
<div className="flex h-12 shrink-0 items-center justify-center border-b">
|
||||
<SidebarCollapseButton
|
||||
isCollapsed={isCollapsed}
|
||||
onToggle={onToggleCollapse ?? (() => {})}
|
||||
|
|
@ -113,7 +113,7 @@ export function Sidebar({
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-14 shrink-0 items-center gap-0 px-1 border-b">
|
||||
<div className="flex h-12 shrink-0 items-center gap-0 px-1 border-b">
|
||||
<SidebarHeader
|
||||
searchSpace={searchSpace}
|
||||
isCollapsed={isCollapsed}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue