->(({ className, ...props }, ref) => (
-
-));
-TableCaption.displayName = "TableCaption";
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
-export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/surfsense_web/hooks/use-documents.ts b/surfsense_web/hooks/use-documents.ts
index cfe2b05..d38b374 100644
--- a/surfsense_web/hooks/use-documents.ts
+++ b/surfsense_web/hooks/use-documents.ts
@@ -1,115 +1,121 @@
-"use client"
-import { useState, useEffect } from 'react';
-import { toast } from 'sonner';
+"use client";
+import { useState, useEffect, useCallback } from "react";
+import { toast } from "sonner";
export interface Document {
- id: number;
- title: string;
- document_type: "EXTENSION" | "CRAWLED_URL" | "SLACK_CONNECTOR" | "NOTION_CONNECTOR" | "FILE";
- document_metadata: any;
- content: string;
- created_at: string;
- search_space_id: number;
+ id: number;
+ title: string;
+ document_type: DocumentType;
+ document_metadata: any;
+ content: string;
+ created_at: string;
+ search_space_id: number;
}
-export function useDocuments(searchSpaceId: number) {
- const [documents, setDocuments] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
+export type DocumentType =
+ | "EXTENSION"
+ | "CRAWLED_URL"
+ | "SLACK_CONNECTOR"
+ | "NOTION_CONNECTOR"
+ | "FILE"
+ | "YOUTUBE_VIDEO"
+ | "GITHUB_CONNECTOR"
+ | "LINEAR_CONNECTOR"
+ | "DISCORD_CONNECTOR";
- useEffect(() => {
- const fetchDocuments = async () => {
- try {
- setLoading(true);
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?search_space_id=${searchSpaceId}`,
- {
- headers: {
- Authorization: `Bearer ${localStorage.getItem('surfsense_bearer_token')}`,
- },
- method: "GET",
- }
- );
-
- if (!response.ok) {
- toast.error("Failed to fetch documents");
- throw new Error("Failed to fetch documents");
+export function useDocuments(searchSpaceId: number, lazy: boolean = true) {
+ const [documents, setDocuments] = useState([]);
+ const [loading, setLoading] = useState(!lazy); // Don't show loading initially for lazy mode
+ const [error, setError] = useState(null);
+ const [isLoaded, setIsLoaded] = useState(false); // Memoization flag
+
+ const fetchDocuments = useCallback(async () => {
+ if (isLoaded && lazy) return; // Avoid redundant calls in lazy mode
+
+ try {
+ setLoading(true);
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?search_space_id=${searchSpaceId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem(
+ "surfsense_bearer_token"
+ )}`,
+ },
+ method: "GET",
+ }
+ );
+
+ if (!response.ok) {
+ toast.error("Failed to fetch documents");
+ throw new Error("Failed to fetch documents");
+ }
+
+ const data = await response.json();
+ setDocuments(data);
+ setError(null);
+ setIsLoaded(true);
+ } catch (err: any) {
+ setError(err.message || "Failed to fetch documents");
+ console.error("Error fetching documents:", err);
+ } finally {
+ setLoading(false);
}
-
- const data = await response.json();
- setDocuments(data);
- setError(null);
- } catch (err: any) {
- setError(err.message || 'Failed to fetch documents');
- console.error('Error fetching documents:', err);
- } finally {
- setLoading(false);
- }
+ }, [searchSpaceId, isLoaded, lazy]);
+
+ useEffect(() => {
+ if (!lazy && searchSpaceId) {
+ fetchDocuments();
+ }
+ }, [searchSpaceId, lazy, fetchDocuments]);
+
+ // Function to refresh the documents list
+ const refreshDocuments = useCallback(async () => {
+ setIsLoaded(false); // Reset memoization flag to allow refetch
+ await fetchDocuments();
+ }, [fetchDocuments]);
+
+ // Function to delete a document
+ const deleteDocument = useCallback(
+ async (documentId: number) => {
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem(
+ "surfsense_bearer_token"
+ )}`,
+ },
+ method: "DELETE",
+ }
+ );
+
+ if (!response.ok) {
+ toast.error("Failed to delete document");
+ throw new Error("Failed to delete document");
+ }
+
+ toast.success("Document deleted successfully");
+ // Update the local state after successful deletion
+ setDocuments(documents.filter((doc) => doc.id !== documentId));
+ return true;
+ } catch (err: any) {
+ toast.error(err.message || "Failed to delete document");
+ console.error("Error deleting document:", err);
+ return false;
+ }
+ },
+ [documents]
+ );
+
+ return {
+ documents,
+ loading,
+ error,
+ isLoaded,
+ fetchDocuments, // Manual fetch function for lazy mode
+ refreshDocuments,
+ deleteDocument,
};
-
- if (searchSpaceId) {
- fetchDocuments();
- }
- }, [searchSpaceId]);
-
- // Function to refresh the documents list
- const refreshDocuments = async () => {
- setLoading(true);
- try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?search_space_id=${searchSpaceId}`,
- {
- headers: {
- Authorization: `Bearer ${localStorage.getItem('surfsense_bearer_token')}`,
- },
- method: "GET",
- }
- );
-
- if (!response.ok) {
- toast.error("Failed to fetch documents");
- throw new Error("Failed to fetch documents");
- }
-
- const data = await response.json();
- setDocuments(data);
- setError(null);
- } catch (err: any) {
- setError(err.message || 'Failed to fetch documents');
- console.error('Error fetching documents:', err);
- } finally {
- setLoading(false);
- }
- };
-
- // Function to delete a document
- const deleteDocument = async (documentId: number) => {
- try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}`,
- {
- headers: {
- Authorization: `Bearer ${localStorage.getItem('surfsense_bearer_token')}`,
- },
- method: "DELETE",
- }
- );
-
- if (!response.ok) {
- toast.error("Failed to delete document");
- throw new Error("Failed to delete document");
- }
-
- toast.success("Document deleted successfully");
- // Update the local state after successful deletion
- setDocuments(documents.filter(doc => doc.id !== documentId));
- return true;
- } catch (err: any) {
- toast.error(err.message || 'Failed to delete document');
- console.error('Error deleting document:', err);
- return false;
- }
- };
-
- return { documents, loading, error, refreshDocuments, deleteDocument };
-}
\ No newline at end of file
+}
diff --git a/surfsense_web/hooks/useChat.ts b/surfsense_web/hooks/useChat.ts
index 023ec4b..0bedcbf 100644
--- a/surfsense_web/hooks/useChat.ts
+++ b/surfsense_web/hooks/useChat.ts
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from "react";
import { Message } from "@ai-sdk/react";
import { ResearchMode } from "@/components/chat";
+import { Document } from "@/hooks/use-documents";
interface UseChatStateProps {
search_space_id: string;
@@ -10,12 +11,17 @@ interface UseChatStateProps {
export function useChatState({ search_space_id, chat_id }: UseChatStateProps) {
const [token, setToken] = useState(null);
const [isLoading, setIsLoading] = useState(false);
- const [currentChatId, setCurrentChatId] = useState(chat_id || null);
-
+ const [currentChatId, setCurrentChatId] = useState(
+ chat_id || null
+ );
+
// Chat configuration state
- const [searchMode, setSearchMode] = useState<"DOCUMENTS" | "CHUNKS">("DOCUMENTS");
+ const [searchMode, setSearchMode] = useState<"DOCUMENTS" | "CHUNKS">(
+ "DOCUMENTS"
+ );
const [researchMode, setResearchMode] = useState("QNA");
const [selectedConnectors, setSelectedConnectors] = useState([]);
+ const [selectedDocuments, setSelectedDocuments] = useState([]);
useEffect(() => {
const bearerToken = localStorage.getItem("surfsense_bearer_token");
@@ -35,6 +41,8 @@ export function useChatState({ search_space_id, chat_id }: UseChatStateProps) {
setResearchMode,
selectedConnectors,
setSelectedConnectors,
+ selectedDocuments,
+ setSelectedDocuments,
};
}
@@ -51,112 +59,133 @@ export function useChatAPI({
researchMode,
selectedConnectors,
}: UseChatAPIProps) {
- const fetchChatDetails = useCallback(async (chatId: string) => {
- if (!token) return null;
+ const fetchChatDetails = useCallback(
+ async (chatId: string) => {
+ if (!token) return null;
- try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/${Number(chatId)}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
+ try {
+ const response = await fetch(
+ `${
+ process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL
+ }/api/v1/chats/${Number(chatId)}`,
+ {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch chat details: ${response.statusText}`
+ );
}
- );
- if (!response.ok) {
- throw new Error(`Failed to fetch chat details: ${response.statusText}`);
+ return await response.json();
+ } catch (err) {
+ console.error("Error fetching chat details:", err);
+ return null;
+ }
+ },
+ [token]
+ );
+
+ const createChat = useCallback(
+ async (initialMessage: string): Promise => {
+ if (!token) {
+ console.error("Authentication token not found");
+ return null;
}
- return await response.json();
- } catch (err) {
- console.error("Error fetching chat details:", err);
- return null;
- }
- }, [token]);
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ type: researchMode,
+ title: "Untitled Chat",
+ initial_connectors: selectedConnectors,
+ messages: [
+ {
+ role: "user",
+ content: initialMessage,
+ },
+ ],
+ search_space_id: Number(search_space_id),
+ }),
+ }
+ );
- const createChat = useCallback(async (initialMessage: string): Promise => {
- if (!token) {
- console.error("Authentication token not found");
- return null;
- }
-
- try {
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify({
- type: researchMode,
- title: "Untitled Chat",
- initial_connectors: selectedConnectors,
- messages: [
- {
- role: "user",
- content: initialMessage,
- },
- ],
- search_space_id: Number(search_space_id),
- }),
+ if (!response.ok) {
+ throw new Error(
+ `Failed to create chat: ${response.statusText}`
+ );
}
- );
- if (!response.ok) {
- throw new Error(`Failed to create chat: ${response.statusText}`);
+ const data = await response.json();
+ return data.id;
+ } catch (err) {
+ console.error("Error creating chat:", err);
+ return null;
}
+ },
+ [token, researchMode, selectedConnectors, search_space_id]
+ );
- const data = await response.json();
- return data.id;
- } catch (err) {
- console.error("Error creating chat:", err);
- return null;
- }
- }, [token, researchMode, selectedConnectors, search_space_id]);
+ const updateChat = useCallback(
+ async (chatId: string, messages: Message[]) => {
+ if (!token) return;
- const updateChat = useCallback(async (chatId: string, messages: Message[]) => {
- if (!token) return;
+ try {
+ const userMessages = messages.filter(
+ (msg) => msg.role === "user"
+ );
+ if (userMessages.length === 0) return;
- try {
- const userMessages = messages.filter(msg => msg.role === "user");
- if (userMessages.length === 0) return;
+ const title = userMessages[0].content;
- const title = userMessages[0].content;
+ const response = await fetch(
+ `${
+ process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL
+ }/api/v1/chats/${Number(chatId)}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ type: researchMode,
+ title: title,
+ initial_connectors: selectedConnectors,
+ messages: messages,
+ search_space_id: Number(search_space_id),
+ }),
+ }
+ );
- const response = await fetch(
- `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/${Number(chatId)}`,
- {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- body: JSON.stringify({
- type: researchMode,
- title: title,
- initial_connectors: selectedConnectors,
- messages: messages,
- search_space_id: Number(search_space_id),
- }),
+ if (!response.ok) {
+ throw new Error(
+ `Failed to update chat: ${response.statusText}`
+ );
}
- );
-
- if (!response.ok) {
- throw new Error(`Failed to update chat: ${response.statusText}`);
+ } catch (err) {
+ console.error("Error updating chat:", err);
}
- } catch (err) {
- console.error("Error updating chat:", err);
- }
- }, [token, researchMode, selectedConnectors, search_space_id]);
+ },
+ [token, researchMode, selectedConnectors, search_space_id]
+ );
return {
fetchChatDetails,
createChat,
updateChat,
};
-}
\ No newline at end of file
+}
|