mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 02:46:25 +02:00
Biome: fixes for compontents directory
This commit is contained in:
parent
758603b275
commit
2950573271
69 changed files with 478 additions and 648 deletions
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Manrope } from "next/font/google";
|
||||
import React, { useRef, useEffect, useReducer, useMemo } from "react";
|
||||
import { RoughNotation, RoughNotationGroup } from "react-rough-notation";
|
||||
import { useInView } from "framer-motion";
|
||||
import { Manrope } from "next/font/google";
|
||||
import { useEffect, useMemo, useReducer, useRef } from "react";
|
||||
import { RoughNotation, RoughNotationGroup } from "react-rough-notation";
|
||||
import { useSidebar } from "@/components/ui/sidebar";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Font configuration - could be moved to a global font config file
|
||||
const manrope = Manrope({
|
||||
|
|
@ -115,7 +115,7 @@ export function AnimatedEmptyState() {
|
|||
}, TIMING.SIDEBAR_TRANSITION);
|
||||
|
||||
return () => clearTimeout(stabilizeTimer);
|
||||
}, [sidebarState]);
|
||||
}, []);
|
||||
|
||||
// Handle highlight visibility based on layout stability and viewport visibility
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import type React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
|
||||
export const CitationDisplay: React.FC<{ index: number; node: any }> = ({ index, node }) => {
|
||||
const truncateText = (text: string, maxLength: number = 200) => {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + "...";
|
||||
return `${text.substring(0, maxLength)}...`;
|
||||
};
|
||||
|
||||
const handleUrlClick = (e: React.MouseEvent, url: string) => {
|
||||
|
|
@ -26,13 +27,15 @@ export const CitationDisplay: React.FC<{ index: number; node: any }> = ({ index,
|
|||
<PopoverContent className="w-80 p-4 space-y-3 relative" align="start">
|
||||
{/* External Link Button - Top Right */}
|
||||
{node?.url && (
|
||||
<button
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={(e) => handleUrlClick(e, node.url)}
|
||||
className="absolute top-3 right-3 inline-flex items-center justify-center w-6 h-6 text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded transition-colors"
|
||||
title="Open in new tab"
|
||||
>
|
||||
<ExternalLink size={14} />
|
||||
</button>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Heading */}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { SuggestedQuestions } from "@llamaindex/chat-ui/widgets";
|
||||
import { getAnnotationData, type Message, useChatUI } from "@llamaindex/chat-ui";
|
||||
import { SuggestedQuestions } from "@llamaindex/chat-ui/widgets";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
|
|
@ -14,7 +14,7 @@ export const ChatFurtherQuestions: React.FC<{ message: Message }> = ({ message }
|
|||
const { append, requestData } = useChatUI();
|
||||
|
||||
if (annotations.length !== 1 || annotations[0].length === 0) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
"use client";
|
||||
|
||||
import { ChatInput } from "@llamaindex/chat-ui";
|
||||
import { FolderOpen, Check, Zap, Brain } from "lucide-react";
|
||||
import { Brain, Check, FolderOpen, Zap } from "lucide-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import React, { Suspense, useCallback, useState } from "react";
|
||||
import type { ResearchMode } from "@/components/chat";
|
||||
import {
|
||||
ConnectorButton as ConnectorButtonComponent,
|
||||
getConnectorIcon,
|
||||
} from "@/components/chat/ConnectorComponents";
|
||||
import { DocumentsDataTable } from "@/components/chat/DocumentsDataTable";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -18,19 +27,9 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Suspense, useState, useCallback } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useDocuments, type Document } from "@/hooks/use-documents";
|
||||
import { DocumentsDataTable } from "@/components/chat/DocumentsDataTable";
|
||||
import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors";
|
||||
import {
|
||||
getConnectorIcon,
|
||||
ConnectorButton as ConnectorButtonComponent,
|
||||
} from "@/components/chat/ConnectorComponents";
|
||||
import type { ResearchMode } from "@/components/chat";
|
||||
import { type Document, useDocuments } from "@/hooks/use-documents";
|
||||
import { useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
|
||||
import React from "react";
|
||||
import { useSearchSourceConnectors } from "@/hooks/useSearchSourceConnectors";
|
||||
|
||||
const DocumentSelector = React.memo(
|
||||
({
|
||||
|
|
@ -188,24 +187,17 @@ const ConnectorSelector = React.memo(
|
|||
const isSelected = selectedConnectors.includes(connector.type);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Button
|
||||
key={connector.id}
|
||||
className={`flex items-center gap-2 p-2 rounded-md border cursor-pointer transition-colors ${
|
||||
isSelected
|
||||
? "border-primary bg-primary/10"
|
||||
: "border-border hover:border-primary/50 hover:bg-muted"
|
||||
}`}
|
||||
className={`flex items-center gap-2 p-2 rounded-md border cursor-pointer transition-colors`}
|
||||
onClick={() => handleConnectorToggle(connector.type)}
|
||||
role="checkbox"
|
||||
aria-checked={isSelected}
|
||||
tabIndex={0}
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
size="sm"
|
||||
type="button"
|
||||
>
|
||||
<div className="flex-shrink-0 w-6 h-6 flex items-center justify-center rounded-full bg-muted">
|
||||
{getConnectorIcon(connector.type)}
|
||||
</div>
|
||||
{getConnectorIcon(connector.type)}
|
||||
<span className="flex-1 text-sm font-medium">{connector.name}</span>
|
||||
{isSelected && <Check className="h-4 w-4 text-primary" />}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { ChatSection as LlamaIndexChatSection, type ChatHandler } from "@llamaindex/chat-ui";
|
||||
import type { Document } from "@/hooks/use-documents";
|
||||
import { ChatInputUI } from "@/components/chat/ChatInputGroup";
|
||||
import { type ChatHandler, ChatSection as LlamaIndexChatSection } from "@llamaindex/chat-ui";
|
||||
import type { ResearchMode } from "@/components/chat";
|
||||
import { ChatInputUI } from "@/components/chat/ChatInputGroup";
|
||||
import { ChatMessagesUI } from "@/components/chat/ChatMessages";
|
||||
import type { Document } from "@/hooks/use-documents";
|
||||
|
||||
interface ChatInterfaceProps {
|
||||
handler: ChatHandler;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
ChatMessage as LlamaIndexChatMessage,
|
||||
ChatMessages as LlamaIndexChatMessages,
|
||||
type Message,
|
||||
useChatUI,
|
||||
} from "@llamaindex/chat-ui";
|
||||
import TerminalDisplay from "@/components/chat/ChatTerminal";
|
||||
import ChatSourcesDisplay from "@/components/chat/ChatSources";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { AnimatedEmptyState } from "@/components/chat/AnimatedEmptyState";
|
||||
import { CitationDisplay } from "@/components/chat/ChatCitation";
|
||||
import { ChatFurtherQuestions } from "@/components/chat/ChatFurtherQuestions";
|
||||
import { AnimatedEmptyState } from "@/components/chat/AnimatedEmptyState";
|
||||
import ChatSourcesDisplay from "@/components/chat/ChatSources";
|
||||
import TerminalDisplay from "@/components/chat/ChatTerminal";
|
||||
import { languageRenderers } from "@/components/chat/CodeBlock";
|
||||
|
||||
export function ChatMessagesUI() {
|
||||
|
|
@ -37,13 +37,13 @@ export function ChatMessagesUI() {
|
|||
}
|
||||
|
||||
function ChatMessageUI({ message, isLast }: { message: Message; isLast: boolean }) {
|
||||
const bottomRef = React.useRef<HTMLDivElement>(null);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (isLast && bottomRef.current) {
|
||||
bottomRef.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
}, [message]);
|
||||
}, [isLast]);
|
||||
|
||||
return (
|
||||
<LlamaIndexChatMessage message={message} isLast={isLast} className="flex flex-col ">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { getAnnotationData, type Message } from "@llamaindex/chat-ui";
|
||||
import { IconBrandGithub } from "@tabler/icons-react";
|
||||
import { ExternalLink, FileText, Globe } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -11,10 +15,6 @@ import {
|
|||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ExternalLink, FileText, Globe } from "lucide-react";
|
||||
import { IconBrandGithub } from "@tabler/icons-react";
|
||||
|
||||
interface Source {
|
||||
id: string;
|
||||
|
|
@ -44,10 +44,6 @@ interface SourceNode {
|
|||
metadata: NodeMetadata;
|
||||
}
|
||||
|
||||
interface NodesResponse {
|
||||
nodes: SourceNode[];
|
||||
}
|
||||
|
||||
function getSourceIcon(type: string) {
|
||||
switch (type) {
|
||||
case "USER_SELECTED_GITHUB_CONNECTOR":
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { getAnnotationData, type Message } from "@llamaindex/chat-ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function TerminalDisplay({ message, open }: { message: Message; open: boolean }) {
|
||||
const [isCollapsed, setIsCollapsed] = React.useState(!open);
|
||||
const [isCollapsed, setIsCollapsed] = useState(!open);
|
||||
|
||||
const bottomRef = React.useRef<HTMLDivElement>(null);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (bottomRef.current) {
|
||||
bottomRef.current.scrollTo({
|
||||
top: bottomRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Get the last assistant message that's not being typed
|
||||
if (!message) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
interface TerminalInfo {
|
||||
|
|
@ -22,24 +32,17 @@ export default function TerminalDisplay({ message, open }: { message: Message; o
|
|||
const events = getAnnotationData(message, "TERMINAL_INFO") as TerminalInfo[];
|
||||
|
||||
if (events.length === 0) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (bottomRef.current) {
|
||||
bottomRef.current.scrollTo({
|
||||
top: bottomRef.current.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-900 rounded-lg border border-gray-700 overflow-hidden font-mono text-sm shadow-lg">
|
||||
{/* Terminal Header */}
|
||||
<div
|
||||
className="bg-gray-800 px-4 py-2 flex items-center gap-2 border-b border-gray-700 cursor-pointer hover:bg-gray-750 transition-colors"
|
||||
<Button
|
||||
className="w-full bg-gray-800 px-4 py-2 flex items-center gap-2 border-b border-gray-700 cursor-pointer hover:bg-gray-750 transition-colors"
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
variant="ghost"
|
||||
type="button"
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
|
|
@ -52,6 +55,7 @@ export default function TerminalDisplay({ message, open }: { message: Message; o
|
|||
<div className="text-gray-400">
|
||||
{isCollapsed ? (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<title>Collapse</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -61,6 +65,7 @@ export default function TerminalDisplay({ message, open }: { message: Message; o
|
|||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<title>Expand</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
|
@ -70,7 +75,7 @@ export default function TerminalDisplay({ message, open }: { message: Message; o
|
|||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
{/* Terminal Content */}
|
||||
{!isCollapsed && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { memo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
|
|
@ -20,57 +20,55 @@ type CitationProps = {
|
|||
/**
|
||||
* Citation component to handle individual citations
|
||||
*/
|
||||
export const Citation = React.memo(
|
||||
({ citationId, citationText, position, source }: CitationProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const citationKey = `citation-${citationId}-${position}`;
|
||||
export const Citation = memo(({ citationId, citationText, position, source }: CitationProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const citationKey = `citation-${citationId}-${position}`;
|
||||
|
||||
if (!source) return <>{citationText}</>;
|
||||
if (!source) return <>{citationText}</>;
|
||||
|
||||
return (
|
||||
<span key={citationKey} className="relative inline-flex items-center">
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<sup>
|
||||
<span className="inline-flex items-center justify-center text-primary cursor-pointer bg-primary/10 hover:bg-primary/15 w-4 h-4 rounded-full text-[10px] font-medium ml-0.5 transition-colors border border-primary/20 shadow-sm">
|
||||
{citationId}
|
||||
</span>
|
||||
</sup>
|
||||
</DropdownMenuTrigger>
|
||||
{open && (
|
||||
<DropdownMenuContent align="start" className="w-80 p-0" forceMount>
|
||||
<Card className="border-0 shadow-none">
|
||||
<div className="p-3 flex items-start gap-3">
|
||||
<div className="flex-shrink-0 w-7 h-7 flex items-center justify-center bg-muted rounded-full">
|
||||
{getConnectorIcon(source.connectorType || "")}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="font-medium text-sm text-card-foreground">{source.title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">{source.description}</p>
|
||||
<div className="mt-2 flex items-center text-xs text-muted-foreground">
|
||||
<span className="truncate max-w-[200px]">{source.url}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 rounded-full"
|
||||
onClick={() => window.open(source.url, "_blank", "noopener,noreferrer")}
|
||||
title="Open in new tab"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
return (
|
||||
<span key={citationKey} className="relative inline-flex items-center">
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<sup>
|
||||
<span className="inline-flex items-center justify-center text-primary cursor-pointer bg-primary/10 hover:bg-primary/15 w-4 h-4 rounded-full text-[10px] font-medium ml-0.5 transition-colors border border-primary/20 shadow-sm">
|
||||
{citationId}
|
||||
</span>
|
||||
</sup>
|
||||
</DropdownMenuTrigger>
|
||||
{open && (
|
||||
<DropdownMenuContent align="start" className="w-80 p-0" forceMount>
|
||||
<Card className="border-0 shadow-none">
|
||||
<div className="p-3 flex items-start gap-3">
|
||||
<div className="flex-shrink-0 w-7 h-7 flex items-center justify-center bg-muted rounded-full">
|
||||
{getConnectorIcon(source.connectorType || "")}
|
||||
</div>
|
||||
</Card>
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
);
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="font-medium text-sm text-card-foreground">{source.title}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">{source.description}</p>
|
||||
<div className="mt-2 flex items-center text-xs text-muted-foreground">
|
||||
<span className="truncate max-w-[200px]">{source.url}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 rounded-full"
|
||||
onClick={() => window.open(source.url, "_blank", "noopener,noreferrer")}
|
||||
title="Open in new tab"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 }) => (
|
||||
<div className="bg-muted p-4 rounded-md">
|
||||
<pre className="m-0 p-0 border-0">
|
||||
<code className="text-xs font-mono border-0 leading-6">{children}</code>
|
||||
|
|
@ -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<CodeBlockProps>(({ children, language }) => {
|
||||
export const CodeBlock = memo<CodeBlockProps>(({ children, language }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const { resolvedTheme, theme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<div className="flex items-center gap-2">
|
||||
{/* Main Q/A vs Report Toggle */}
|
||||
<div className="flex h-8 rounded-md border border-border overflow-hidden">
|
||||
<button
|
||||
<Button
|
||||
className={`flex h-full items-center gap-1 px-3 text-xs font-medium transition-colors whitespace-nowrap ${
|
||||
isQnaMode
|
||||
? "bg-primary text-primary-foreground"
|
||||
|
|
@ -249,8 +249,8 @@ export const ResearchModeControl = ({ value, onChange }: ResearchModeControlProp
|
|||
>
|
||||
<MessageCircle className="h-3 w-3" />
|
||||
<span>Q/A</span>
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
className={`flex h-full items-center gap-1 px-3 text-xs font-medium transition-colors whitespace-nowrap ${
|
||||
isReportMode
|
||||
? "bg-primary text-primary-foreground"
|
||||
|
|
@ -261,14 +261,14 @@ export const ResearchModeControl = ({ value, onChange }: ResearchModeControlProp
|
|||
>
|
||||
<FileText className="h-3 w-3" />
|
||||
<span>Report</span>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Report Sub-options (only show when in Report mode) */}
|
||||
{isReportMode && (
|
||||
<div className="flex h-8 rounded-md border border-border overflow-hidden">
|
||||
{reportSubOptions.map((option) => (
|
||||
<button
|
||||
<Button
|
||||
key={option.value}
|
||||
className={`flex h-full items-center gap-1 px-2 text-xs font-medium transition-colors whitespace-nowrap ${
|
||||
getCurrentReportMode() === option.value
|
||||
|
|
@ -280,7 +280,7 @@ export const ResearchModeControl = ({ value, onChange }: ResearchModeControlProp
|
|||
>
|
||||
{option.icon}
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
||||
const [documentTypeFilter, setDocumentTypeFilter] = React.useState<DocumentType | "ALL">("ALL");
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
const [documentTypeFilter, setDocumentTypeFilter] = useState<DocumentType | "ALL">("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<string, boolean> = {};
|
||||
|
|
@ -222,24 +221,24 @@ export function DocumentsDataTable({
|
|||
return selection;
|
||||
}, [documents, initialSelectedDocuments]);
|
||||
|
||||
const [rowSelection, setRowSelection] = React.useState<Record<string, boolean>>({});
|
||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||
|
||||
// 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({});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type React from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type SegmentedControlProps<T extends string> = {
|
||||
value: T;
|
||||
|
|
@ -21,7 +22,7 @@ function SegmentedControl<T extends string>({
|
|||
return (
|
||||
<div className="flex h-7 rounded-md border border-border overflow-hidden">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
<Button
|
||||
key={option.value}
|
||||
className={`flex h-full items-center gap-1 px-2 text-xs transition-colors ${
|
||||
value === option.value ? "bg-primary text-primary-foreground" : "hover:bg-muted"
|
||||
|
|
@ -31,7 +32,7 @@ function SegmentedControl<T extends string>({
|
|||
>
|
||||
{option.icon}
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Source, Connector } from "./types";
|
||||
import type { Connector, Source } from "./types";
|
||||
|
||||
/**
|
||||
* Function to get sources for the main view
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue