+ );
+});
Citation.displayName = "Citation";
@@ -85,10 +83,10 @@ export const renderTextWithCitations = (
const citationRegex = /\[(\d+)\]/g;
const parts = [];
let lastIndex = 0;
- let match;
+ let match: RegExpExecArray | null = citationRegex.exec(text);
let position = 0;
- while ((match = citationRegex.exec(text)) !== null) {
+ while (match !== null) {
// Add text before the citation
if (match.index > lastIndex) {
parts.push(text.substring(lastIndex, match.index));
@@ -108,6 +106,7 @@ export const renderTextWithCitations = (
lastIndex = match.index + match[0].length;
position++;
+ match = citationRegex.exec(text);
}
// Add any remaining text after the last citation
diff --git a/surfsense_web/components/chat/CodeBlock.tsx b/surfsense_web/components/chat/CodeBlock.tsx
index 5abcfa52f..7641a8b82 100644
--- a/surfsense_web/components/chat/CodeBlock.tsx
+++ b/surfsense_web/components/chat/CodeBlock.tsx
@@ -1,10 +1,10 @@
"use client";
-import React, { useState, useEffect, useMemo, useCallback } from "react";
-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
-import { oneLight, oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { Check, Copy } from "lucide-react";
import { useTheme } from "next-themes";
+import { memo, useCallback, useEffect, useMemo, useState } from "react";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { oneDark, oneLight } from "react-syntax-highlighter/dist/cjs/styles/prism";
// Constants for styling and configuration
const COPY_TIMEOUT = 2000;
@@ -41,14 +41,14 @@ interface CodeBlockProps {
language: string;
}
-type LanguageRenderer = (props: { code: string }) => React.JSX.Element
+type LanguageRenderer = (props: { code: string }) => React.JSX.Element;
interface SyntaxStyle {
[key: string]: React.CSSProperties;
}
// Memoized fallback component for SSR/hydration
-const FallbackCodeBlock = React.memo(({ children }: { children: string }) => (
+const FallbackCodeBlock = memo(({ children }: { children: string }) => (
{children}
@@ -59,7 +59,7 @@ const FallbackCodeBlock = React.memo(({ children }: { children: string }) => (
FallbackCodeBlock.displayName = "FallbackCodeBlock";
// Code block component with syntax highlighting and copy functionality
-export const CodeBlock = React.memo(({ children, language }) => {
+export const CodeBlock = memo(({ children, language }) => {
const [copied, setCopied] = useState(false);
const { resolvedTheme, theme } = useTheme();
const [mounted, setMounted] = useState(false);
diff --git a/surfsense_web/components/chat/ConnectorComponents.tsx b/surfsense_web/components/chat/ConnectorComponents.tsx
index 5a373c717..f946b88e4 100644
--- a/surfsense_web/components/chat/ConnectorComponents.tsx
+++ b/surfsense_web/components/chat/ConnectorComponents.tsx
@@ -1,28 +1,28 @@
-import type React from "react";
-import {
- ChevronDown,
- Plus,
- Search,
- Globe,
- Sparkles,
- Microscope,
- Telescope,
- File,
- Link,
- Webhook,
- MessageCircle,
- FileText,
-} from "lucide-react";
import {
+ IconBrandDiscord,
+ IconBrandGithub,
IconBrandNotion,
IconBrandSlack,
IconBrandYoutube,
- IconBrandGithub,
IconLayoutKanban,
IconLinkPlus,
- IconBrandDiscord,
IconTicket,
} from "@tabler/icons-react";
+import {
+ ChevronDown,
+ File,
+ FileText,
+ Globe,
+ Link,
+ MessageCircle,
+ Microscope,
+ Plus,
+ Search,
+ Sparkles,
+ Telescope,
+ Webhook,
+} from "lucide-react";
+import type React from "react";
import { Button } from "@/components/ui/button";
import type { Connector, ResearchMode } from "./types";
@@ -238,7 +238,7 @@ export const ResearchModeControl = ({ value, onChange }: ResearchModeControlProp
{/* Main Q/A vs Report Toggle */}
-
-
{/* Report Sub-options (only show when in Report mode) */}
{isReportMode && (
{reportSubOptions.map((option) => (
-
{option.icon}
{option.label}
-
+
))}
)}
diff --git a/surfsense_web/components/chat/DocumentsDataTable.tsx b/surfsense_web/components/chat/DocumentsDataTable.tsx
index f93e72c96..c574c2777 100644
--- a/surfsense_web/components/chat/DocumentsDataTable.tsx
+++ b/surfsense_web/components/chat/DocumentsDataTable.tsx
@@ -1,6 +1,5 @@
"use client";
-import * as React from "react";
import {
type ColumnDef,
type ColumnFiltersState,
@@ -14,9 +13,11 @@ import {
type VisibilityState,
} from "@tanstack/react-table";
import { ArrowUpDown, Calendar, FileText, Search } from "lucide-react";
-
+import { useEffect, useMemo, useState } from "react";
+import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
+import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -24,7 +25,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { Input } from "@/components/ui/input";
import {
Table,
TableBody,
@@ -33,7 +33,6 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
-import { Badge } from "@/components/ui/badge";
import type { Document, DocumentType } from "@/hooks/use-documents";
interface DocumentsDataTableProps {
@@ -206,13 +205,13 @@ export function DocumentsDataTable({
onDone,
initialSelectedDocuments = [],
}: DocumentsDataTableProps) {
- const [sorting, setSorting] = React.useState
([]);
- const [columnFilters, setColumnFilters] = React.useState([]);
- const [columnVisibility, setColumnVisibility] = React.useState({});
- const [documentTypeFilter, setDocumentTypeFilter] = React.useState("ALL");
+ const [sorting, setSorting] = useState([]);
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [columnVisibility, setColumnVisibility] = useState({});
+ const [documentTypeFilter, setDocumentTypeFilter] = useState("ALL");
// Memoize initial row selection to prevent infinite loops
- const initialRowSelection = React.useMemo(() => {
+ const initialRowSelection = useMemo(() => {
if (!documents.length || !initialSelectedDocuments.length) return {};
const selection: Record = {};
@@ -222,24 +221,24 @@ export function DocumentsDataTable({
return selection;
}, [documents, initialSelectedDocuments]);
- const [rowSelection, setRowSelection] = React.useState>({});
+ const [rowSelection, setRowSelection] = useState>({});
// Only update row selection when initialRowSelection actually changes and is not empty
- React.useEffect(() => {
+ useEffect(() => {
const hasChanges = JSON.stringify(rowSelection) !== JSON.stringify(initialRowSelection);
if (hasChanges && Object.keys(initialRowSelection).length > 0) {
setRowSelection(initialRowSelection);
}
- }, [initialRowSelection]);
+ }, [initialRowSelection, rowSelection]);
// Initialize row selection on mount
- React.useEffect(() => {
+ useEffect(() => {
if (Object.keys(rowSelection).length === 0 && Object.keys(initialRowSelection).length > 0) {
setRowSelection(initialRowSelection);
}
- }, []);
+ }, [initialRowSelection, rowSelection]);
- const filteredDocuments = React.useMemo(() => {
+ const filteredDocuments = useMemo(() => {
if (documentTypeFilter === "ALL") return documents;
return documents.filter((doc) => doc.document_type === documentTypeFilter);
}, [documents, documentTypeFilter]);
@@ -260,11 +259,11 @@ export function DocumentsDataTable({
state: { sorting, columnFilters, columnVisibility, rowSelection },
});
- React.useEffect(() => {
+ useEffect(() => {
const selectedRows = table.getFilteredSelectedRowModel().rows;
const selectedDocuments = selectedRows.map((row) => row.original);
onSelectionChange(selectedDocuments);
- }, [rowSelection, onSelectionChange, table]);
+ }, [onSelectionChange, table]);
const handleClearAll = () => setRowSelection({});
diff --git a/surfsense_web/components/chat/ScrollUtils.tsx b/surfsense_web/components/chat/ScrollUtils.tsx
index 922d836af..3984246ae 100644
--- a/surfsense_web/components/chat/ScrollUtils.tsx
+++ b/surfsense_web/components/chat/ScrollUtils.tsx
@@ -47,7 +47,7 @@ export const useScrollIndicators = (
// Add resize listener to update indicators when window size changes
window.addEventListener("resize", updateIndicators);
return () => window.removeEventListener("resize", updateIndicators);
- }, []);
+ }, [updateIndicators]);
return updateIndicators;
};
diff --git a/surfsense_web/components/chat/SegmentedControl.tsx b/surfsense_web/components/chat/SegmentedControl.tsx
index 11cab6fae..91314ae18 100644
--- a/surfsense_web/components/chat/SegmentedControl.tsx
+++ b/surfsense_web/components/chat/SegmentedControl.tsx
@@ -1,4 +1,5 @@
import type React from "react";
+import { Button } from "@/components/ui/button";
type SegmentedControlProps = {
value: T;
@@ -21,7 +22,7 @@ function SegmentedControl({
return (
{options.map((option) => (
- ({
>
{option.icon}
{option.label}
-
+
))}
);
diff --git a/surfsense_web/components/chat/SourceUtils.tsx b/surfsense_web/components/chat/SourceUtils.tsx
index bd83c4b42..a384a7b56 100644
--- a/surfsense_web/components/chat/SourceUtils.tsx
+++ b/surfsense_web/components/chat/SourceUtils.tsx
@@ -1,4 +1,4 @@
-import type { Source, Connector } from "./types";
+import type { Connector, Source } from "./types";
/**
* Function to get sources for the main view
diff --git a/surfsense_web/components/chat/index.ts b/surfsense_web/components/chat/index.ts
index 9ce695909..74bd5eea0 100644
--- a/surfsense_web/components/chat/index.ts
+++ b/surfsense_web/components/chat/index.ts
@@ -1,8 +1,9 @@
// Export all components and utilities from the chat folder
-export { default as SegmentedControl } from "./SegmentedControl";
-export * from "./ConnectorComponents";
+
export * from "./Citation";
-export * from "./SourceUtils";
-export * from "./ScrollUtils";
export * from "./CodeBlock";
+export * from "./ConnectorComponents";
+export * from "./ScrollUtils";
+export { default as SegmentedControl } from "./SegmentedControl";
+export * from "./SourceUtils";
export * from "./types";
diff --git a/surfsense_web/components/copy-button.tsx b/surfsense_web/components/copy-button.tsx
index b8466753e..c1a752997 100644
--- a/surfsense_web/components/copy-button.tsx
+++ b/surfsense_web/components/copy-button.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useEffect, useRef, useState } from "react";
-import type { RefObject } from "react";
-import { Button } from "./ui/button";
import { Copy, CopyCheck } from "lucide-react";
+import type { RefObject } from "react";
+import { useEffect, useRef, useState } from "react";
+import { Button } from "./ui/button";
export default function CopyButton({ ref }: { ref: RefObject }) {
const [copy, setCopy] = useState(false);
diff --git a/surfsense_web/components/document-viewer.tsx b/surfsense_web/components/document-viewer.tsx
index 07efddd24..0f283e567 100644
--- a/surfsense_web/components/document-viewer.tsx
+++ b/surfsense_web/components/document-viewer.tsx
@@ -1,4 +1,7 @@
+import { FileText } from "lucide-react";
import type React from "react";
+import { MarkdownViewer } from "@/components/markdown-viewer";
+import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -6,9 +9,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { MarkdownViewer } from "@/components/markdown-viewer";
-import { FileText } from "lucide-react";
interface DocumentViewerProps {
title: string;
diff --git a/surfsense_web/components/editConnector/EditConnectorLoadingSkeleton.tsx b/surfsense_web/components/editConnector/EditConnectorLoadingSkeleton.tsx
index aa8c0032e..4b9965632 100644
--- a/surfsense_web/components/editConnector/EditConnectorLoadingSkeleton.tsx
+++ b/surfsense_web/components/editConnector/EditConnectorLoadingSkeleton.tsx
@@ -1,6 +1,7 @@
-import React from "react";
-import { Skeleton } from "@/components/ui/skeleton";
+"use client";
+
import { Card, CardContent, CardHeader } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
export function EditConnectorLoadingSkeleton() {
return (
diff --git a/surfsense_web/components/editConnector/EditConnectorNameForm.tsx b/surfsense_web/components/editConnector/EditConnectorNameForm.tsx
index e0e60e06a..0dae174db 100644
--- a/surfsense_web/components/editConnector/EditConnectorNameForm.tsx
+++ b/surfsense_web/components/editConnector/EditConnectorNameForm.tsx
@@ -1,6 +1,7 @@
-import React from "react";
+"use client";
+
import type { Control } from "react-hook-form";
-import { FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
+import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
// Assuming EditConnectorFormValues is defined elsewhere or passed as generic
diff --git a/surfsense_web/components/editConnector/EditGitHubConnectorConfig.tsx b/surfsense_web/components/editConnector/EditGitHubConnectorConfig.tsx
index 30721751b..aa3eb1404 100644
--- a/surfsense_web/components/editConnector/EditGitHubConnectorConfig.tsx
+++ b/surfsense_web/components/editConnector/EditGitHubConnectorConfig.tsx
@@ -1,19 +1,19 @@
+import { CircleAlert, Edit, KeyRound, Loader2 } from "lucide-react";
import type React from "react";
import type { UseFormReturn } from "react-hook-form";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
import {
+ FormControl,
+ FormDescription,
FormField,
FormItem,
FormLabel,
- FormControl,
- FormDescription,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
-import { Button } from "@/components/ui/button";
-import { Checkbox } from "@/components/ui/checkbox";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Skeleton } from "@/components/ui/skeleton";
-import { Edit, KeyRound, Loader2, CircleAlert } from "lucide-react";
// Types needed from parent
interface GithubRepo {
diff --git a/surfsense_web/components/editConnector/EditSimpleTokenForm.tsx b/surfsense_web/components/editConnector/EditSimpleTokenForm.tsx
index 5e2a92eb4..4ad654045 100644
--- a/surfsense_web/components/editConnector/EditSimpleTokenForm.tsx
+++ b/surfsense_web/components/editConnector/EditSimpleTokenForm.tsx
@@ -1,15 +1,16 @@
-import React from "react";
+"use client";
+
+import { KeyRound } from "lucide-react";
import type { Control } from "react-hook-form";
import {
+ FormControl,
+ FormDescription,
FormField,
FormItem,
FormLabel,
- FormControl,
- FormDescription,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
-import { KeyRound } from "lucide-react";
// Assuming EditConnectorFormValues is defined elsewhere or passed as generic
interface EditSimpleTokenFormProps {
diff --git a/surfsense_web/components/json-metadata-viewer.tsx b/surfsense_web/components/json-metadata-viewer.tsx
index 33c7bce10..11dd71581 100644
--- a/surfsense_web/components/json-metadata-viewer.tsx
+++ b/surfsense_web/components/json-metadata-viewer.tsx
@@ -1,4 +1,7 @@
+import { FileJson } from "lucide-react";
import React from "react";
+import { defaultStyles, JsonView } from "react-json-view-lite";
+import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
@@ -6,9 +9,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { FileJson } from "lucide-react";
-import { JsonView, defaultStyles } from "react-json-view-lite";
import "react-json-view-lite/dist/index.css";
interface JsonMetadataViewerProps {
diff --git a/surfsense_web/components/markdown-viewer.tsx b/surfsense_web/components/markdown-viewer.tsx
index 48b12b19a..1fb060597 100644
--- a/surfsense_web/components/markdown-viewer.tsx
+++ b/surfsense_web/components/markdown-viewer.tsx
@@ -1,15 +1,17 @@
-import React, { useMemo, useState, useEffect, useRef } from "react";
+import { Check, Copy } from "lucide-react";
+import Image from "next/image";
+import { useTheme } from "next-themes";
+import React, { useEffect, useMemo, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { oneDark, oneLight } from "react-syntax-highlighter/dist/cjs/styles/prism";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import remarkGfm from "remark-gfm";
+import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Citation } from "./chat/Citation";
import type { Source } from "./chat/types";
-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
-import { oneLight, oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
-import { Check, Copy } from "lucide-react";
-import { useTheme } from "next-themes";
import CopyButton from "./copy-button";
interface MarkdownViewerProps {
@@ -112,7 +114,13 @@ export function MarkdownViewer({
),
hr: ({ node, ...props }: any) =>
,
img: ({ node, ...props }: any) => (
-
+
),
table: ({ node, ...props }: any) => (
@@ -186,7 +194,8 @@ const CodeBlock = ({ children, language }: { children: string; language: string
return (
-
)}
-
+
{mounted ? (
lastIndex) {
parts.push(text.substring(lastIndex, match.index));
@@ -322,6 +331,7 @@ const processCitationsInText = (
lastIndex = match.index + match[0].length;
position++;
+ match = citationRegex.exec(text);
}
// Add any remaining text after the last citation
diff --git a/surfsense_web/components/onboard/add-provider-step.tsx b/surfsense_web/components/onboard/add-provider-step.tsx
index 6c2065de8..db8c75b78 100644
--- a/surfsense_web/components/onboard/add-provider-step.tsx
+++ b/surfsense_web/components/onboard/add-provider-step.tsx
@@ -1,8 +1,11 @@
"use client";
-import type React from "react";
-import { useState } from "react";
import { motion } from "framer-motion";
+import { AlertCircle, Bot, Plus, Trash2 } from "lucide-react";
+import { useState } from "react";
+import { toast } from "sonner";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@@ -14,12 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-
-import { Badge } from "@/components/ui/badge";
-import { Plus, Trash2, Bot, AlertCircle } from "lucide-react";
-import { useLLMConfigs, type CreateLLMConfig } from "@/hooks/use-llm-configs";
-import { toast } from "sonner";
-import { Alert, AlertDescription } from "@/components/ui/alert";
+import { type CreateLLMConfig, useLLMConfigs } from "@/hooks/use-llm-configs";
const LLM_PROVIDERS = [
{ value: "OPENAI", label: "OpenAI", example: "gpt-4o, gpt-4, gpt-3.5-turbo" },
diff --git a/surfsense_web/components/onboard/assign-roles-step.tsx b/surfsense_web/components/onboard/assign-roles-step.tsx
index c32cc82d4..2a7b25288 100644
--- a/surfsense_web/components/onboard/assign-roles-step.tsx
+++ b/surfsense_web/components/onboard/assign-roles-step.tsx
@@ -1,8 +1,12 @@
"use client";
-import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
+import { AlertCircle, Bot, Brain, CheckCircle, Zap } from "lucide-react";
+import { useEffect, useState } from "react";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@@ -10,10 +14,7 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
-import { Badge } from "@/components/ui/badge";
-import { Brain, Zap, Bot, AlertCircle, CheckCircle } from "lucide-react";
import { useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
-import { Alert, AlertDescription } from "@/components/ui/alert";
const ROLE_DESCRIPTIONS = {
long_context: {
@@ -163,7 +164,7 @@ export function AssignRolesStep({ onPreferencesUpdated }: AssignRolesStepProps)
-
+