diff --git a/flows/openrag_agent.json b/flows/openrag_agent.json index 597011a4..c08a305c 100644 --- a/flows/openrag_agent.json +++ b/flows/openrag_agent.json @@ -2559,12 +2559,12 @@ "zoom": 0.602433700773958 } }, - "description": "OpenRAG Open Search Agent", + "description": "OpenRAG OpenSearch Agent", "endpoint_name": null, "id": "1098eea1-6649-4e1d-aed1-b77249fb8dd0", "is_component": false, "last_tested_version": "1.6.3.dev0", - "name": "OpenRAG Open Search Agent", + "name": "OpenRAG OpenSearch Agent", "tags": [ "assistants", "agents" diff --git a/flows/openrag_nudges.json b/flows/openrag_nudges.json index 95ff9172..7ed390d7 100644 --- a/flows/openrag_nudges.json +++ b/flows/openrag_nudges.json @@ -2337,12 +2337,12 @@ "zoom": 0.5380793988167256 } }, - "description": "OpenRAG Open Search Nudges generator, based on the Open Search documents and the chat history.", + "description": "OpenRAG OpenSearch Nudges generator, based on the OpenSearch documents and the chat history.", "endpoint_name": null, "id": "ebc01d31-1976-46ce-a385-b0240327226c", "is_component": false, "last_tested_version": "1.6.0", - "name": "OpenRAG Open Search Nudges", + "name": "OpenRAG OpenSearch Nudges", "tags": [ "assistants", "agents" diff --git a/frontend/components/docling-health-banner.tsx b/frontend/components/docling-health-banner.tsx new file mode 100644 index 00000000..c65a93cc --- /dev/null +++ b/frontend/components/docling-health-banner.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { AlertTriangle, ExternalLink, Copy } from "lucide-react"; +import { useDoclingHealthQuery } from "@/src/app/api/queries/useDoclingHealthQuery"; +import { Banner, BannerIcon, BannerTitle, BannerAction } from "@/components/ui/banner"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter +} from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; + +interface DoclingHealthBannerProps { + className?: string; +} + +// DoclingSetupDialog component +interface DoclingSetupDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + className?: string; +} + +function DoclingSetupDialog({ + open, + onOpenChange, + className +}: DoclingSetupDialogProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText("uv run openrag"); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + + + + + + docling-serve is stopped. Knowledge ingest is unavailable. + + + Start docling-serve by running: + + + +
+
+ + uv run openrag + + +
+ + + Then, select Start Native Services in the TUI. Once docling-serve is running, refresh OpenRAG. + +
+ + + + +
+
+ ); +} + +export function DoclingHealthBanner({ className }: DoclingHealthBannerProps) { + const { data: health, isLoading, isError } = useDoclingHealthQuery(); + const [showDialog, setShowDialog] = useState(false); + + const isHealthy = health?.status === "healthy" && !isError; + const isUnhealthy = health?.status === "unhealthy" || isError; + + // Only show banner when service is unhealthy + if (isLoading || isHealthy) { + return null; + } + + if (isUnhealthy) { + return ( + <> + + + + docling-serve native service is stopped. Knowledge ingest is unavailable. + + setShowDialog(true)} + className="bg-foreground text-background hover:bg-primary/90" + > + Setup Docling Serve + + + + + + + ); + } + + return null; +} \ No newline at end of file diff --git a/frontend/components/ui/banner.tsx b/frontend/components/ui/banner.tsx new file mode 100644 index 00000000..3a1ea9f5 --- /dev/null +++ b/frontend/components/ui/banner.tsx @@ -0,0 +1,141 @@ +'use client'; +import { useControllableState } from '@radix-ui/react-use-controllable-state'; +import { type LucideIcon, XIcon } from 'lucide-react'; +import { + type ComponentProps, + createContext, + type HTMLAttributes, + type MouseEventHandler, + useContext, +} from 'react'; +import { Button } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +type BannerContextProps = { + show: boolean; + setShow: (show: boolean) => void; +}; + +export const BannerContext = createContext({ + show: true, + setShow: () => {}, +}); + +export type BannerProps = HTMLAttributes & { + visible?: boolean; + defaultVisible?: boolean; + onClose?: () => void; + inset?: boolean; +}; + +export const Banner = ({ + children, + visible, + defaultVisible = true, + onClose, + className, + inset = false, + ...props +}: BannerProps) => { + const [show, setShow] = useControllableState({ + defaultProp: defaultVisible, + prop: visible, + onChange: onClose, + }); + + if (!show) { + return null; + } + + return ( + +
+ {children} +
+
+ ); +}; + +export type BannerIconProps = HTMLAttributes & { + icon: LucideIcon; +}; + +export const BannerIcon = ({ + icon: Icon, + className, + ...props +}: BannerIconProps) => ( +
+ +
+); + +export type BannerTitleProps = HTMLAttributes; + +export const BannerTitle = ({ className, ...props }: BannerTitleProps) => ( +

+); + +export type BannerActionProps = ComponentProps; + +export const BannerAction = ({ + variant = 'outline', + size = 'sm', + className, + ...props +}: BannerActionProps) => ( + + ); +}; \ No newline at end of file diff --git a/frontend/src/app/api/queries/useDoclingHealthQuery.ts b/frontend/src/app/api/queries/useDoclingHealthQuery.ts new file mode 100644 index 00000000..16ffc6c5 --- /dev/null +++ b/frontend/src/app/api/queries/useDoclingHealthQuery.ts @@ -0,0 +1,55 @@ +import { + type UseQueryOptions, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; + +export interface DoclingHealthResponse { + status: "healthy" | "unhealthy"; + message?: string; +} + +export const useDoclingHealthQuery = ( + options?: Omit, "queryKey" | "queryFn">, +) => { + const queryClient = useQueryClient(); + + async function checkDoclingHealth(): Promise { + try { + const response = await fetch("http://127.0.0.1:5001/health", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (response.ok) { + return { status: "healthy" }; + } else { + return { + status: "unhealthy", + message: `Health check failed with status: ${response.status}`, + }; + } + } catch (error) { + return { + status: "unhealthy", + message: error instanceof Error ? error.message : "Connection failed", + }; + } + } + + const queryResult = useQuery( + { + queryKey: ["docling-health"], + queryFn: checkDoclingHealth, + retry: 1, + refetchInterval: 30000, // Check every 30 seconds + staleTime: 25000, // Consider data stale after 25 seconds + ...options, + }, + queryClient, + ); + + return queryResult; +}; \ No newline at end of file diff --git a/frontend/src/app/chat/page.tsx b/frontend/src/app/chat/page.tsx index 01ee43c7..2c3bf278 100644 --- a/frontend/src/app/chat/page.tsx +++ b/frontend/src/app/chat/page.tsx @@ -31,6 +31,7 @@ import { import { useAuth } from "@/contexts/auth-context"; import { type EndpointType, useChat } from "@/contexts/chat-context"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; +import { useLayout } from "@/contexts/layout-context"; import { useTask } from "@/contexts/task-context"; import { useLoadingStore } from "@/stores/loadingStore"; import { useGetNudgesQuery } from "../api/queries/useGetNudgesQuery"; @@ -151,6 +152,7 @@ function ChatPage() { const streamIdRef = useRef(0); const lastLoadedConversationRef = useRef(null); const { addTask, isMenuOpen } = useTask(); + const { totalTopOffset } = useLayout(); const { selectedFilter, parsedFilterData, isPanelOpen, setSelectedFilter } = useKnowledgeFilter(); @@ -2046,7 +2048,7 @@ function ChatPage() { return (

{/* Debug header - only show in debug mode */} {isDebugMode && ( diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index 7d49a56e..b29d8370 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -9,283 +9,286 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; +import { useLayout } from "@/contexts/layout-context"; import { useTask } from "@/contexts/task-context"; import { - type ChunkResult, - type File, - useGetSearchQuery, + type ChunkResult, + type File, + useGetSearchQuery, } from "../../api/queries/useGetSearchQuery"; const getFileTypeLabel = (mimetype: string) => { - if (mimetype === "application/pdf") return "PDF"; - if (mimetype === "text/plain") return "Text"; - if (mimetype === "application/msword") return "Word Document"; - return "Unknown"; + if (mimetype === "application/pdf") return "PDF"; + if (mimetype === "text/plain") return "Text"; + if (mimetype === "application/msword") return "Word Document"; + return "Unknown"; }; function ChunksPageContent() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { isMenuOpen } = useTask(); - const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); + const router = useRouter(); + const searchParams = useSearchParams(); + const { isMenuOpen } = useTask(); + const { totalTopOffset } = useLayout(); + const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); - const filename = searchParams.get("filename"); - const [chunks, setChunks] = useState([]); - const [chunksFilteredByQuery, setChunksFilteredByQuery] = useState< - ChunkResult[] - >([]); - const [selectedChunks, setSelectedChunks] = useState>(new Set()); - const [activeCopiedChunkIndex, setActiveCopiedChunkIndex] = useState< - number | null - >(null); + const filename = searchParams.get("filename"); + const [chunks, setChunks] = useState([]); + const [chunksFilteredByQuery, setChunksFilteredByQuery] = useState< + ChunkResult[] + >([]); + const [selectedChunks, setSelectedChunks] = useState>(new Set()); + const [activeCopiedChunkIndex, setActiveCopiedChunkIndex] = useState< + number | null + >(null); - // Calculate average chunk length - const averageChunkLength = useMemo( - () => - chunks.reduce((acc, chunk) => acc + chunk.text.length, 0) / - chunks.length || 0, - [chunks], - ); + // Calculate average chunk length + const averageChunkLength = useMemo( + () => + chunks.reduce((acc, chunk) => acc + chunk.text.length, 0) / + chunks.length || 0, + [chunks], + ); - const [selectAll, setSelectAll] = useState(false); - const [queryInputText, setQueryInputText] = useState( - parsedFilterData?.query ?? "", - ); + const [selectAll, setSelectAll] = useState(false); + const [queryInputText, setQueryInputText] = useState( + parsedFilterData?.query ?? "", + ); - // Use the same search query as the knowledge page, but we'll filter for the specific file - const { data = [], isFetching } = useGetSearchQuery("*", parsedFilterData); + // Use the same search query as the knowledge page, but we'll filter for the specific file + const { data = [], isFetching } = useGetSearchQuery("*", parsedFilterData); - useEffect(() => { - if (queryInputText === "") { - setChunksFilteredByQuery(chunks); - } else { - setChunksFilteredByQuery( - chunks.filter((chunk) => - chunk.text.toLowerCase().includes(queryInputText.toLowerCase()), - ), - ); - } - }, [queryInputText, chunks]); + useEffect(() => { + if (queryInputText === "") { + setChunksFilteredByQuery(chunks); + } else { + setChunksFilteredByQuery( + chunks.filter((chunk) => + chunk.text.toLowerCase().includes(queryInputText.toLowerCase()), + ), + ); + } + }, [queryInputText, chunks]); - const handleCopy = useCallback((text: string, index: number) => { - // Trim whitespace and remove new lines/tabs for cleaner copy - navigator.clipboard.writeText(text.trim().replace(/[\n\r\t]/gm, "")); - setActiveCopiedChunkIndex(index); - setTimeout(() => setActiveCopiedChunkIndex(null), 10 * 1000); // 10 seconds - }, []); + const handleCopy = useCallback((text: string, index: number) => { + // Trim whitespace and remove new lines/tabs for cleaner copy + navigator.clipboard.writeText(text.trim().replace(/[\n\r\t]/gm, "")); + setActiveCopiedChunkIndex(index); + setTimeout(() => setActiveCopiedChunkIndex(null), 10 * 1000); // 10 seconds + }, []); - const fileData = (data as File[]).find( - (file: File) => file.filename === filename, - ); + const fileData = (data as File[]).find( + (file: File) => file.filename === filename, + ); - // Extract chunks for the specific file - useEffect(() => { - if (!filename || !(data as File[]).length) { - setChunks([]); - return; - } + // Extract chunks for the specific file + useEffect(() => { + if (!filename || !(data as File[]).length) { + setChunks([]); + return; + } - setChunks(fileData?.chunks || []); - }, [data, filename]); + setChunks(fileData?.chunks || []); + }, [data, filename]); - // Set selected state for all checkboxes when selectAll changes - useEffect(() => { - if (selectAll) { - setSelectedChunks(new Set(chunks.map((_, index) => index))); - } else { - setSelectedChunks(new Set()); - } - }, [selectAll, setSelectedChunks, chunks]); + // Set selected state for all checkboxes when selectAll changes + useEffect(() => { + if (selectAll) { + setSelectedChunks(new Set(chunks.map((_, index) => index))); + } else { + setSelectedChunks(new Set()); + } + }, [selectAll, setSelectedChunks, chunks]); - const handleBack = useCallback(() => { - router.push("/knowledge"); - }, [router]); + const handleBack = useCallback(() => { + router.push("/knowledge"); + }, [router]); - const handleChunkCardCheckboxChange = useCallback( - (index: number) => { - setSelectedChunks((prevSelected) => { - const newSelected = new Set(prevSelected); - if (newSelected.has(index)) { - newSelected.delete(index); - } else { - newSelected.add(index); - } - return newSelected; - }); - }, - [setSelectedChunks], - ); + const handleChunkCardCheckboxChange = useCallback( + (index: number) => { + setSelectedChunks((prevSelected) => { + const newSelected = new Set(prevSelected); + if (newSelected.has(index)) { + newSelected.delete(index); + } else { + newSelected.add(index); + } + return newSelected; + }); + }, + [setSelectedChunks], + ); - if (!filename) { - return ( -
-
- -

No file specified

-

- Please select a file from the knowledge page -

-
-
- ); - } + if (!filename) { + return ( +
+
+ +

No file specified

+

+ Please select a file from the knowledge page +

+
+
+ ); + } - return ( -
-
- {/* Header */} -
-
- -

- {/* Removes file extension from filename */} - {filename.replace(/\.[^/.]+$/, "")} -

-
-
-
- : null - } - id="search-query" - type="text" - defaultValue={parsedFilterData?.query} - value={queryInputText} - onChange={(e) => setQueryInputText(e.target.value)} - placeholder="Search chunks..." - /> -
-
- - setSelectAll(!!handleSelectAll) - } - /> - -
-
-
+ return ( +
+
+ {/* Header */} +
+
+ +

+ {/* Removes file extension from filename */} + {filename.replace(/\.[^/.]+$/, "")} +

+
+
+
+ : null + } + id="search-query" + type="text" + defaultValue={parsedFilterData?.query} + value={queryInputText} + onChange={(e) => setQueryInputText(e.target.value)} + placeholder="Search chunks..." + /> +
+
+ + setSelectAll(!!handleSelectAll) + } + /> + +
+
+
- {/* Content Area - matches knowledge page structure */} -
- {isFetching ? ( -
-
- -

- Loading chunks... -

-
-
- ) : chunks.length === 0 ? ( -
-
- -

No chunks found

-

- This file may not have been indexed yet -

-
-
- ) : ( -
- {chunksFilteredByQuery.map((chunk, index) => ( -
-
-
-
- - handleChunkCardCheckboxChange(index) - } - /> -
- - Chunk {chunk.page} - - - {chunk.text.length} chars - -
- -
-
+ {/* Content Area - matches knowledge page structure */} +
+ {isFetching ? ( +
+
+ +

+ Loading chunks... +

+
+
+ ) : chunks.length === 0 ? ( +
+
+ +

No chunks found

+

+ This file may not have been indexed yet +

+
+
+ ) : ( +
+ {chunksFilteredByQuery.map((chunk, index) => ( +
+
+
+
+ + handleChunkCardCheckboxChange(index) + } + /> +
+ + Chunk {chunk.page} + + + {chunk.text.length} chars + +
+ +
+
- {/* TODO: Update to use active toggle */} - {/* + {/* TODO: Update to use active toggle */} + {/* Active */} -
-
- {chunk.text} -
-
- ))} -
- )} -
-
- {/* Right panel - Summary (TODO), Technical details, */} -
-
-

Technical details

-
-
-
Total chunks
-
- {chunks.length} -
-
-
-
Avg length
-
- {averageChunkLength.toFixed(0)} chars -
-
- {/* TODO: Uncomment after data is available */} - {/*
+
+
+ {chunk.text} +
+
+ ))} +
+ )} +
+
+ {/* Right panel - Summary (TODO), Technical details, */} +
+
+

Technical details

+
+
+
Total chunks
+
+ {chunks.length} +
+
+
+
Avg length
+
+ {averageChunkLength.toFixed(0)} chars +
+
+ {/* TODO: Uncomment after data is available */} + {/*
Process time
@@ -295,76 +298,76 @@ function ChunksPageContent() {
*/} -
-
-
-

Original document

-
-
-
Name
-
- {fileData?.filename} -
-
-
-
Type
-
- {fileData ? getFileTypeLabel(fileData.mimetype) : "Unknown"} -
-
-
-
Size
-
- {fileData?.size - ? `${Math.round(fileData.size / 1024)} KB` - : "Unknown"} -
-
-
-
Uploaded
-
- N/A -
-
- {/* TODO: Uncomment after data is available */} - {/*
+
+
+
+

Original document

+
+
+
Name
+
+ {fileData?.filename} +
+
+
+
Type
+
+ {fileData ? getFileTypeLabel(fileData.mimetype) : "Unknown"} +
+
+
+
Size
+
+ {fileData?.size + ? `${Math.round(fileData.size / 1024)} KB` + : "Unknown"} +
+
+
+
Uploaded
+
+ N/A +
+
+ {/* TODO: Uncomment after data is available */} + {/*
Source
*/} -
-
Updated
-
- N/A -
-
-
-
-
-
- ); +
+
Updated
+
+ N/A +
+
+ +
+
+
+ ); } function ChunksPage() { - return ( - -
- -

Loading...

-
-
- } - > - - - ); + return ( + +
+ +

Loading...

+
+
+ } + > + + + ); } export default function ProtectedChunksPage() { - return ( - - - - ); + return ( + + + + ); } diff --git a/frontend/src/app/knowledge/page.tsx b/frontend/src/app/knowledge/page.tsx index 73cc2c7d..64eeb49c 100644 --- a/frontend/src/app/knowledge/page.tsx +++ b/frontend/src/app/knowledge/page.tsx @@ -17,6 +17,7 @@ import { KnowledgeDropdown } from "@/components/knowledge-dropdown"; import { ProtectedRoute } from "@/components/protected-route"; import { Button } from "@/components/ui/button"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; +import { useLayout } from "@/contexts/layout-context"; import { useTask } from "@/contexts/task-context"; import { type File, useGetSearchQuery } from "../api/queries/useGetSearchQuery"; import "@/components/AgGrid/registerAgGridModules"; @@ -53,6 +54,7 @@ function getSourceIcon(connectorType?: string) { function SearchPage() { const router = useRouter(); const { isMenuOpen, files: taskFiles, refreshTasks } = useTask(); + const { totalTopOffset } = useLayout(); const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const [selectedRows, setSelectedRows] = useState([]); @@ -274,26 +276,27 @@ function SearchPage() { } }; - return ( -
-
-
-

Project Knowledge

- -
+ return ( +
+
+
+

Project Knowledge

+ +
{/* Search Input Area */}
diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index 7a444a02..a6e22ffd 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -149,7 +149,7 @@ function KnowledgeSourcesPage() { const [systemPrompt, setSystemPrompt] = useState(""); const [chunkSize, setChunkSize] = useState(1024); const [chunkOverlap, setChunkOverlap] = useState(50); - const [tableStructure, setTableStructure] = useState(false); + const [tableStructure, setTableStructure] = useState(true); const [ocr, setOcr] = useState(false); const [pictureDescriptions, setPictureDescriptions] = useState(false); diff --git a/frontend/src/components/layout-wrapper.tsx b/frontend/src/components/layout-wrapper.tsx index cb0794e0..79417654 100644 --- a/frontend/src/components/layout-wrapper.tsx +++ b/frontend/src/components/layout-wrapper.tsx @@ -7,6 +7,7 @@ import { type ChatConversation, } from "@/app/api/queries/useGetConversationsQuery"; import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery"; +import { DoclingHealthBanner } from "@/components/docling-health-banner"; import { KnowledgeFilterPanel } from "@/components/knowledge-filter-panel"; import Logo from "@/components/logo/logo"; import { Navigation } from "@/components/navigation"; @@ -16,9 +17,11 @@ import { UserNav } from "@/components/user-nav"; import { useAuth } from "@/contexts/auth-context"; import { useChat } from "@/contexts/chat-context"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; +import { LayoutProvider } from "@/contexts/layout-context"; // import { GitHubStarButton } from "@/components/github-star-button" // import { DiscordLink } from "@/components/discord-link" import { useTask } from "@/contexts/task-context"; +import { useDoclingHealthQuery } from "@/src/app/api/queries/useDoclingHealthQuery"; import { cn } from "@/lib/utils"; export function LayoutWrapper({ children }: { children: React.ReactNode }) { @@ -35,6 +38,11 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { const { isLoading: isSettingsLoading, data: settings } = useGetSettingsQuery({ enabled: isAuthenticated || isNoAuthMode, }); + const { + data: health, + isLoading: isHealthLoading, + isError, + } = useDoclingHealthQuery(); // Only fetch conversations on chat page const isOnChatPage = pathname === "/" || pathname === "/chat"; @@ -64,6 +72,17 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { task.status === "processing" ); + const isUnhealthy = health?.status === "unhealthy" || isError; + const isBannerVisible = !isHealthLoading && isUnhealthy; + + // Dynamic height calculations based on banner visibility + const headerHeight = 53; + const bannerHeight = 52; // Approximate banner height + const totalTopOffset = isBannerVisible + ? headerHeight + bannerHeight + : headerHeight; + const mainContentHeight = `calc(100vh - ${totalTopOffset}px)`; + // Show loading state when backend isn't ready if (isLoading || isSettingsLoading) { return ( @@ -76,7 +95,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { ); } - if (isAuthPage || (settings && !settings.edited)) { + if (isAuthPage) { // For auth pages, render without navigation return
{children}
; } @@ -84,6 +103,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { // For all other pages, render with Langflow-styled navigation and task menu return (
+
{/* Logo/Title */} @@ -124,7 +144,10 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
-
+
-
- {children} -
+
+ {children} +
+
diff --git a/frontend/src/contexts/layout-context.tsx b/frontend/src/contexts/layout-context.tsx new file mode 100644 index 00000000..f40ea28c --- /dev/null +++ b/frontend/src/contexts/layout-context.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { createContext, useContext } from "react"; + +interface LayoutContextType { + headerHeight: number; + totalTopOffset: number; +} + +const LayoutContext = createContext(undefined); + +export function useLayout() { + const context = useContext(LayoutContext); + if (context === undefined) { + throw new Error("useLayout must be used within a LayoutProvider"); + } + return context; +} + +export function LayoutProvider({ + children, + headerHeight, + totalTopOffset +}: { + children: React.ReactNode; + headerHeight: number; + totalTopOffset: number; +}) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/lib/constants.ts b/frontend/src/lib/constants.ts index 8e7770fb..9ce34634 100644 --- a/frontend/src/lib/constants.ts +++ b/frontend/src/lib/constants.ts @@ -12,7 +12,7 @@ export const DEFAULT_AGENT_SETTINGS = { export const DEFAULT_KNOWLEDGE_SETTINGS = { chunk_size: 1000, chunk_overlap: 200, - table_structure: false, + table_structure: true, ocr: false, picture_descriptions: false } as const; diff --git a/src/config/config_manager.py b/src/config/config_manager.py index da059d0d..93fb86c5 100644 --- a/src/config/config_manager.py +++ b/src/config/config_manager.py @@ -27,7 +27,7 @@ class KnowledgeConfig: embedding_model: str = "text-embedding-3-small" chunk_size: int = 1000 chunk_overlap: int = 200 - table_structure: bool = False + table_structure: bool = True ocr: bool = False picture_descriptions: bool = False