diff --git a/surfsense_web/app/dashboard/[search_space_id]/researcher/[chat_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/researcher/[chat_id]/page.tsx
index 6513074..a9ebd9c 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/researcher/[chat_id]/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/researcher/[chat_id]/page.tsx
@@ -1,5 +1,5 @@
"use client";
-import React, { useRef, useEffect, useState } from 'react';
+import React, { useRef, useEffect, useState, useMemo, useCallback } from 'react';
import { useChat } from '@ai-sdk/react';
import { useParams } from 'next/navigation';
import {
@@ -16,13 +16,11 @@ import {
SendHorizontal,
FileText,
Grid3x3,
- File,
- Globe,
- Webhook,
FolderOpen,
- Upload
+ Upload,
+ ChevronDown,
+ Filter
} from 'lucide-react';
-import { IconBrandDiscord, IconBrandGithub, IconBrandNotion, IconBrandSlack, IconBrandYoutube, IconLayoutKanban } from "@tabler/icons-react";
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -36,6 +34,16 @@ import {
DialogTrigger,
DialogFooter
} from "@/components/ui/dialog";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { Badge } from "@/components/ui/badge";
+import { Skeleton } from "@/components/ui/skeleton";
import {
ConnectorButton as ConnectorButtonComponent,
getConnectorIcon,
@@ -72,28 +80,75 @@ interface ConnectorSource {
type DocumentType = "EXTENSION" | "CRAWLED_URL" | "SLACK_CONNECTOR" | "NOTION_CONNECTOR" | "FILE" | "YOUTUBE_VIDEO" | "GITHUB_CONNECTOR" | "LINEAR_CONNECTOR" | "DISCORD_CONNECTOR";
-interface Document {
- id: number;
- title: string;
- document_type: DocumentType;
- document_metadata: any;
- content: string;
- created_at: string;
- search_space_id: number;
-}
-// Document type icons mapping
-const documentTypeIcons = {
- EXTENSION: Webhook,
- CRAWLED_URL: Globe,
- SLACK_CONNECTOR: IconBrandSlack,
- NOTION_CONNECTOR: IconBrandNotion,
- FILE: File,
- YOUTUBE_VIDEO: IconBrandYoutube,
- GITHUB_CONNECTOR: IconBrandGithub,
- LINEAR_CONNECTOR: IconLayoutKanban,
- DISCORD_CONNECTOR: IconBrandDiscord,
-} as const;
+/**
+ * Skeleton loader for document items
+ */
+const DocumentSkeleton = () => (
+
+);
+
+/**
+ * Enhanced document type filter dropdown
+ */
+const DocumentTypeFilter = ({
+ value,
+ onChange,
+ counts
+}: {
+ value: DocumentType | "ALL";
+ onChange: (value: DocumentType | "ALL") => void;
+ counts: Record;
+}) => {
+ const getTypeLabel = (type: DocumentType | "ALL") => {
+ if (type === "ALL") return "All Types";
+ return type.replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase());
+ };
+
+ const getTypeIcon = (type: DocumentType | "ALL") => {
+ if (type === "ALL") return ;
+ return getConnectorIcon(type);
+ };
+
+ return (
+
+
+
+
+
+ Document Types
+
+ {Object.entries(counts).map(([type, count]) => (
+ onChange(type as DocumentType | "ALL")}
+ className="flex items-center justify-between"
+ >
+
+ {getTypeIcon(type as DocumentType | "ALL")}
+ {getTypeLabel(type as DocumentType | "ALL")}
+
+
+ {count}
+
+
+ ))}
+
+
+ );
+};
/**
* Button that displays selected connectors and opens connector selection dialog
@@ -327,8 +382,63 @@ const ChatPage = () => {
// Document selection state
const [selectedDocuments, setSelectedDocuments] = useState([]);
const [documentFilter, setDocumentFilter] = useState("");
+ const [debouncedDocumentFilter, setDebouncedDocumentFilter] = useState("");
+ const [documentTypeFilter, setDocumentTypeFilter] = useState("ALL");
+ const [documentsPage, setDocumentsPage] = useState(1);
+ const [documentsPerPage] = useState(10);
const { documents, loading: isLoadingDocuments, error: documentsError } = useDocuments(Number(search_space_id));
+ // Debounced search effect (proper implementation)
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedDocumentFilter(documentFilter);
+ setDocumentsPage(1); // Reset page when search changes
+ }, 300);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [documentFilter]);
+
+ // Memoized filtered and paginated documents
+ const filteredDocuments = useMemo(() => {
+ if (!documents) return [];
+
+ return documents.filter(doc => {
+ const matchesSearch = doc.title.toLowerCase().includes(debouncedDocumentFilter.toLowerCase()) ||
+ doc.content.toLowerCase().includes(debouncedDocumentFilter.toLowerCase());
+ const matchesType = documentTypeFilter === "ALL" || doc.document_type === documentTypeFilter;
+ return matchesSearch && matchesType;
+ });
+ }, [documents, debouncedDocumentFilter, documentTypeFilter]);
+
+ const paginatedDocuments = useMemo(() => {
+ const startIndex = (documentsPage - 1) * documentsPerPage;
+ return filteredDocuments.slice(startIndex, startIndex + documentsPerPage);
+ }, [filteredDocuments, documentsPage, documentsPerPage]);
+
+ const totalPages = Math.ceil(filteredDocuments.length / documentsPerPage);
+
+ // Document type counts for filter dropdown
+ const documentTypeCounts = useMemo(() => {
+ if (!documents) return {};
+
+ const counts: Record = { ALL: documents.length };
+ documents.forEach(doc => {
+ counts[doc.document_type] = (counts[doc.document_type] || 0) + 1;
+ });
+ return counts;
+ }, [documents]);
+
+ // Callback to handle document selection
+ const handleDocumentToggle = useCallback((documentId: number) => {
+ setSelectedDocuments(prev =>
+ prev.includes(documentId)
+ ? prev.filter(id => id !== documentId)
+ : [...prev, documentId]
+ );
+ }, []);
+
// Function to scroll terminal to bottom
const scrollTerminalToBottom = () => {
if (terminalMessagesRef.current) {
@@ -874,7 +984,7 @@ const ChatPage = () => {
// Use these message-specific sources for the Tabs component
return (
0 ? messageConnectorSources[0].type : "CRAWLED_URL"}
+ defaultValue={messageConnectorSources.length > 0 ? messageConnectorSources[0].type : undefined}
className="w-full"
>
@@ -1068,7 +1178,7 @@ const ChatPage = () => {
- {/* Document Selection Dialog */}
+ {/* Enhanced Document Selection Dialog */}