From ba22091f31b82f833933523f2d893a89d87ee8f3 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 26 Sep 2025 16:44:15 -0500 Subject: [PATCH 01/20] add banner for docling serve --- frontend/components/docling-health-banner.tsx | 134 +++++++++++++++++ frontend/components/ui/banner.tsx | 141 ++++++++++++++++++ .../app/api/queries/useDoclingHealthQuery.ts | 55 +++++++ frontend/src/app/chat/page.tsx | 5 +- frontend/src/app/knowledge/chunks/page.tsx | 5 +- frontend/src/app/knowledge/page.tsx | 5 +- frontend/src/components/layout-wrapper.tsx | 26 +++- frontend/src/contexts/layout-context.tsx | 34 +++++ src/tui/managers/env_manager.py | 2 +- 9 files changed, 400 insertions(+), 7 deletions(-) create mode 100644 frontend/components/docling-health-banner.tsx create mode 100644 frontend/components/ui/banner.tsx create mode 100644 frontend/src/app/api/queries/useDoclingHealthQuery.ts create mode 100644 frontend/src/contexts/layout-context.tsx 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 072567ba..bcb9d75f 100644 --- a/frontend/src/app/chat/page.tsx +++ b/frontend/src/app/chat/page.tsx @@ -22,6 +22,7 @@ import { Button } from "@/components/ui/button"; 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"; @@ -140,6 +141,7 @@ function ChatPage() { const streamIdRef = useRef(0); const lastLoadedConversationRef = useRef(null); const { addTask, isMenuOpen } = useTask(); + const { totalTopOffset } = useLayout(); const { selectedFilter, parsedFilterData, isPanelOpen, setSelectedFilter } = useKnowledgeFilter(); @@ -1891,7 +1893,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 cdc9fcc3..73af7e95 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -12,6 +12,7 @@ import { useRouter, useSearchParams } from "next/navigation"; 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 ChunkResult, @@ -33,6 +34,7 @@ function ChunksPageContent() { const router = useRouter(); const searchParams = useSearchParams(); const { isMenuOpen } = useTask(); + const { totalTopOffset } = useLayout(); const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const filename = searchParams.get("filename"); @@ -132,7 +134,7 @@ function ChunksPageContent() { return (
{/* Header */} diff --git a/frontend/src/app/knowledge/page.tsx b/frontend/src/app/knowledge/page.tsx index 5155f4e2..dc275396 100644 --- a/frontend/src/app/knowledge/page.tsx +++ b/frontend/src/app/knowledge/page.tsx @@ -11,6 +11,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"; @@ -46,6 +47,7 @@ function getSourceIcon(connectorType?: string) { function SearchPage() { const router = useRouter(); const { isMenuOpen, files: taskFiles } = useTask(); + const { totalTopOffset } = useLayout(); const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const [selectedRows, setSelectedRows] = useState([]); @@ -229,7 +231,7 @@ function SearchPage() { return (
diff --git a/frontend/src/components/layout-wrapper.tsx b/frontend/src/components/layout-wrapper.tsx index 5c2f0e13..5b598eb9 100644 --- a/frontend/src/components/layout-wrapper.tsx +++ b/frontend/src/components/layout-wrapper.tsx @@ -16,6 +16,9 @@ import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; // import { GitHubStarButton } from "@/components/github-star-button" // import { DiscordLink } from "@/components/discord-link" import { useTask } from "@/contexts/task-context"; +import { DoclingHealthBanner } from "@/components/docling-health-banner"; +import { useDoclingHealthQuery } from "@/src/app/api/queries/useDoclingHealthQuery"; +import { LayoutProvider } from "@/contexts/layout-context"; export function LayoutWrapper({ children }: { children: React.ReactNode }) { const pathname = usePathname(); @@ -31,6 +34,7 @@ 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"; @@ -56,6 +60,15 @@ 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,6 +89,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { // For all other pages, render with Langflow-styled navigation and task menu return (
+
{/* Logo/Title */} @@ -118,7 +132,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/src/tui/managers/env_manager.py b/src/tui/managers/env_manager.py index 9954b463..282f73d0 100644 --- a/src/tui/managers/env_manager.py +++ b/src/tui/managers/env_manager.py @@ -32,7 +32,7 @@ class EnvConfig: langflow_superuser: str = "admin" langflow_superuser_password: str = "" langflow_chat_flow_id: str = "1098eea1-6649-4e1d-aed1-b77249fb8dd0" - langflow_ingest_flow_id: str = "5488df7c-b93f-4f87-a446-b67028bc0813" + langflow_ingest_flow_id: str = "1402618b-e6d1-4ff2-9a11-d6ce71186915" # OAuth settings google_oauth_client_id: str = "" From c5c5ed46f489218bfc635fde29bdb6cd54a7c852 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 26 Sep 2025 16:46:28 -0500 Subject: [PATCH 02/20] update example --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index ee2a838c..7867ffb9 100644 --- a/.env.example +++ b/.env.example @@ -7,9 +7,9 @@ LANGFLOW_SECRET_KEY= # flow ids for chat and ingestion flows LANGFLOW_CHAT_FLOW_ID=1098eea1-6649-4e1d-aed1-b77249fb8dd0 -LANGFLOW_INGEST_FLOW_ID=5488df7c-b93f-4f87-a446-b67028bc0813 +# LANGFLOW_INGEST_FLOW_ID=5488df7c-b93f-4f87-a446-b67028bc0813 # Ingest flow using docling -# LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915 +LANGFLOW_INGEST_FLOW_ID=1402618b-e6d1-4ff2-9a11-d6ce71186915 NUDGES_FLOW_ID=ebc01d31-1976-46ce-a385-b0240327226c # Set a strong admin password for OpenSearch; a bcrypt hash is generated at From f51960476e0466c502176d7dee2d24dbac36b958 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Mon, 29 Sep 2025 14:52:21 -0300 Subject: [PATCH 03/20] fix layout not showing on chat --- frontend/src/components/layout-wrapper.tsx | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/layout-wrapper.tsx b/frontend/src/components/layout-wrapper.tsx index 5b598eb9..7f352601 100644 --- a/frontend/src/components/layout-wrapper.tsx +++ b/frontend/src/components/layout-wrapper.tsx @@ -2,8 +2,12 @@ import { Bell, Loader2 } from "lucide-react"; import { usePathname } from "next/navigation"; -import { useGetConversationsQuery, type ChatConversation } from "@/app/api/queries/useGetConversationsQuery"; +import { + type ChatConversation, + useGetConversationsQuery, +} 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"; @@ -13,12 +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 { DoclingHealthBanner } from "@/components/docling-health-banner"; import { useDoclingHealthQuery } from "@/src/app/api/queries/useDoclingHealthQuery"; -import { LayoutProvider } from "@/contexts/layout-context"; export function LayoutWrapper({ children }: { children: React.ReactNode }) { const pathname = usePathname(); @@ -34,7 +37,11 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { const { isLoading: isSettingsLoading, data: settings } = useGetSettingsQuery({ enabled: isAuthenticated || isNoAuthMode, }); - const { data: health, isLoading: isHealthLoading, isError } = useDoclingHealthQuery(); + const { + data: health, + isLoading: isHealthLoading, + isError, + } = useDoclingHealthQuery(); // Only fetch conversations on chat page const isOnChatPage = pathname === "/" || pathname === "/chat"; @@ -66,7 +73,9 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { // Dynamic height calculations based on banner visibility const headerHeight = 53; const bannerHeight = 52; // Approximate banner height - const totalTopOffset = isBannerVisible ? headerHeight + bannerHeight : headerHeight; + const totalTopOffset = isBannerVisible + ? headerHeight + bannerHeight + : headerHeight; const mainContentHeight = `calc(100vh - ${totalTopOffset}px)`; // Show loading state when backend isn't ready @@ -81,7 +90,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { ); } - if (isAuthPage || (settings && !settings.edited)) { + if (isAuthPage) { // For auth pages, render without navigation return
{children}
; } @@ -157,7 +166,10 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { }`} style={{ height: mainContentHeight }} > - +
{children}
From 12a72b2cd98ec56b800301cfe81c400464ea2c77 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Thu, 2 Oct 2025 16:30:22 -0600 Subject: [PATCH 04/20] remove unused fields --- frontend/src/app/knowledge/chunks/page.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index cb96eddc..267d0afc 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -298,12 +298,12 @@ function ChunksPageContent() {

Original document

-
+ {/*
Name
{fileData?.filename}
-
+
*/}
Type
@@ -318,23 +318,23 @@ function ChunksPageContent() { : "Unknown"}
-
+ {/*
Uploaded
N/A
-
+
*/} {/* TODO: Uncomment after data is available */} {/*
Source
*/} -
+ {/*
Updated
N/A
-
+
*/}
From 042ffb421fc55c9500530646182887f85834c073 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Thu, 2 Oct 2025 16:41:59 -0600 Subject: [PATCH 05/20] fix search --- frontend/src/app/knowledge/chunks/page.tsx | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index 267d0afc..bc00f886 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { ArrowLeft, Check, Copy, Loader2, Search } from "lucide-react"; +import { ArrowLeft, Check, Copy, Loader2, Search, X } from "lucide-react"; import { Suspense, useCallback, useEffect, useMemo, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { ProtectedRoute } from "@/components/protected-route"; @@ -14,7 +14,7 @@ import { } from "../../api/queries/useGetSearchQuery"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; -import { Input } from "@/components/ui/input"; +import { filterAccentClasses } from "@/components/knowledge-filter-panel"; const getFileTypeLabel = (mimetype: string) => { if (mimetype === "application/pdf") return "PDF"; @@ -26,8 +26,9 @@ const getFileTypeLabel = (mimetype: string) => { function ChunksPageContent() { const router = useRouter(); const searchParams = useSearchParams(); + const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } = + useKnowledgeFilter(); const { isMenuOpen } = useTask(); - const { parsedFilterData, isPanelOpen } = useKnowledgeFilter(); const filename = searchParams.get("filename"); const [chunks, setChunks] = useState([]); @@ -158,17 +159,35 @@ function ChunksPageContent() {
-
- : null} - id="search-query" - type="text" - defaultValue={parsedFilterData?.query} - value={queryInputText} - onChange={(e) => setQueryInputText(e.target.value)} - placeholder="Search chunks..." - /> +
+
+ {selectedFilter?.name && ( +
+ {selectedFilter?.name} + setSelectedFilter(null)} + /> +
+ )} + + setQueryInputText(e.target.value)} + /> +
Date: Fri, 3 Oct 2025 09:08:33 -0600 Subject: [PATCH 06/20] fix frontend ID --- frontend/src/app/api/queries/useGetSearchQuery.ts | 5 +++-- frontend/src/app/knowledge/chunks/page.tsx | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/api/queries/useGetSearchQuery.ts b/frontend/src/app/api/queries/useGetSearchQuery.ts index 5383178d..d0c1a3a9 100644 --- a/frontend/src/app/api/queries/useGetSearchQuery.ts +++ b/frontend/src/app/api/queries/useGetSearchQuery.ts @@ -29,6 +29,7 @@ export interface ChunkResult { owner_email?: string; file_size?: number; connector_type?: string; + index?: number; } export interface File { @@ -55,7 +56,7 @@ export interface File { export const useGetSearchQuery = ( query: string, queryData?: ParsedQueryData | null, - options?: Omit, + options?: Omit ) => { const queryClient = useQueryClient(); @@ -184,7 +185,7 @@ export const useGetSearchQuery = ( queryFn: getFiles, ...options, }, - queryClient, + queryClient ); return queryResult; diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index bc00f886..9b02506b 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -86,7 +86,9 @@ function ChunksPageContent() { return; } - setChunks(fileData?.chunks || []); + setChunks( + fileData?.chunks?.map((chunk, i) => ({ ...chunk, index: i + 1 })) || [] + ); }, [data, filename]); // Set selected state for all checkboxes when selectAll changes @@ -246,7 +248,7 @@ function ChunksPageContent() { />
- Chunk {chunk.page} + Chunk {chunk.index} {chunk.text.length} chars From ac333b99d7317b8b3400e280d836454ffae64676 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Fri, 3 Oct 2025 09:12:35 -0600 Subject: [PATCH 07/20] added value --- frontend/src/app/knowledge/chunks/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index 9b02506b..bbb7f019 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -188,6 +188,7 @@ function ChunksPageContent() { type="text" placeholder="Search your documents..." onChange={(e) => setQueryInputText(e.target.value)} + value={queryInputText} />
From 11a6f0e8d415ce989929c49e566da75d474b11bf Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Fri, 3 Oct 2025 10:47:31 -0600 Subject: [PATCH 08/20] updated header and cloud picker header --- frontend/src/app/upload/[provider]/page.tsx | 659 +++++++++--------- .../components/cloud-picker/picker-header.tsx | 8 +- 2 files changed, 332 insertions(+), 335 deletions(-) diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx index 7c72ec3d..8e0a306c 100644 --- a/frontend/src/app/upload/[provider]/page.tsx +++ b/frontend/src/app/upload/[provider]/page.tsx @@ -12,367 +12,370 @@ import { useTask } from "@/contexts/task-context"; // CloudFile interface is now imported from the unified cloud picker interface CloudConnector { - id: string; - name: string; - description: string; - status: "not_connected" | "connecting" | "connected" | "error"; - type: string; - connectionId?: string; - clientId: string; - hasAccessToken: boolean; - accessTokenError?: string; + id: string; + name: string; + description: string; + status: "not_connected" | "connecting" | "connected" | "error"; + type: string; + connectionId?: string; + clientId: string; + hasAccessToken: boolean; + accessTokenError?: string; } export default function UploadProviderPage() { - const params = useParams(); - const router = useRouter(); - const provider = params.provider as string; - const { addTask, tasks } = useTask(); + const params = useParams(); + const router = useRouter(); + const provider = params.provider as string; + const { addTask, tasks } = useTask(); - const [connector, setConnector] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [accessToken, setAccessToken] = useState(null); - const [selectedFiles, setSelectedFiles] = useState([]); - const [isIngesting, setIsIngesting] = useState(false); - const [currentSyncTaskId, setCurrentSyncTaskId] = useState( - null, - ); - const [ingestSettings, setIngestSettings] = useState({ - chunkSize: 1000, - chunkOverlap: 200, - ocr: false, - pictureDescriptions: false, - embeddingModel: "text-embedding-3-small", - }); + const [connector, setConnector] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [accessToken, setAccessToken] = useState(null); + const [selectedFiles, setSelectedFiles] = useState([]); + const [isIngesting, setIsIngesting] = useState(false); + const [currentSyncTaskId, setCurrentSyncTaskId] = useState( + null + ); + const [ingestSettings, setIngestSettings] = useState({ + chunkSize: 1000, + chunkOverlap: 200, + ocr: false, + pictureDescriptions: false, + embeddingModel: "text-embedding-3-small", + }); - useEffect(() => { - const fetchConnectorInfo = async () => { - setIsLoading(true); - setError(null); + useEffect(() => { + const fetchConnectorInfo = async () => { + setIsLoading(true); + setError(null); - try { - // Fetch available connectors to validate the provider - const connectorsResponse = await fetch("/api/connectors"); - if (!connectorsResponse.ok) { - throw new Error("Failed to load connectors"); - } + try { + // Fetch available connectors to validate the provider + const connectorsResponse = await fetch("/api/connectors"); + if (!connectorsResponse.ok) { + throw new Error("Failed to load connectors"); + } - const connectorsResult = await connectorsResponse.json(); - const providerInfo = connectorsResult.connectors[provider]; + const connectorsResult = await connectorsResponse.json(); + const providerInfo = connectorsResult.connectors[provider]; - if (!providerInfo || !providerInfo.available) { - setError( - `Cloud provider "${provider}" is not available or configured.`, - ); - return; - } + if (!providerInfo || !providerInfo.available) { + setError( + `Cloud provider "${provider}" is not available or configured.` + ); + return; + } - // Check connector status - const statusResponse = await fetch( - `/api/connectors/${provider}/status`, - ); - if (!statusResponse.ok) { - throw new Error(`Failed to check ${provider} status`); - } + // Check connector status + const statusResponse = await fetch( + `/api/connectors/${provider}/status` + ); + if (!statusResponse.ok) { + throw new Error(`Failed to check ${provider} status`); + } - const statusData = await statusResponse.json(); - const connections = statusData.connections || []; - const activeConnection = connections.find( - (conn: { is_active: boolean; connection_id: string }) => - conn.is_active, - ); - const isConnected = activeConnection !== undefined; + const statusData = await statusResponse.json(); + const connections = statusData.connections || []; + const activeConnection = connections.find( + (conn: { is_active: boolean; connection_id: string }) => + conn.is_active + ); + const isConnected = activeConnection !== undefined; - let hasAccessToken = false; - let accessTokenError: string | undefined; + let hasAccessToken = false; + let accessTokenError: string | undefined; - // Try to get access token for connected connectors - if (isConnected && activeConnection) { - try { - const tokenResponse = await fetch( - `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}`, - ); - if (tokenResponse.ok) { - const tokenData = await tokenResponse.json(); - if (tokenData.access_token) { - hasAccessToken = true; - setAccessToken(tokenData.access_token); - } - } else { - const errorData = await tokenResponse - .json() - .catch(() => ({ error: "Token unavailable" })); - accessTokenError = errorData.error || "Access token unavailable"; - } - } catch { - accessTokenError = "Failed to fetch access token"; - } - } + // Try to get access token for connected connectors + if (isConnected && activeConnection) { + try { + const tokenResponse = await fetch( + `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}` + ); + if (tokenResponse.ok) { + const tokenData = await tokenResponse.json(); + if (tokenData.access_token) { + hasAccessToken = true; + setAccessToken(tokenData.access_token); + } + } else { + const errorData = await tokenResponse + .json() + .catch(() => ({ error: "Token unavailable" })); + accessTokenError = errorData.error || "Access token unavailable"; + } + } catch { + accessTokenError = "Failed to fetch access token"; + } + } - setConnector({ - id: provider, - name: providerInfo.name, - description: providerInfo.description, - status: isConnected ? "connected" : "not_connected", - type: provider, - connectionId: activeConnection?.connection_id, - clientId: activeConnection?.client_id, - hasAccessToken, - accessTokenError, - }); - } catch (error) { - console.error("Failed to load connector info:", error); - setError( - error instanceof Error - ? error.message - : "Failed to load connector information", - ); - } finally { - setIsLoading(false); - } - }; + setConnector({ + id: provider, + name: providerInfo.name, + description: providerInfo.description, + status: isConnected ? "connected" : "not_connected", + type: provider, + connectionId: activeConnection?.connection_id, + clientId: activeConnection?.client_id, + hasAccessToken, + accessTokenError, + }); + } catch (error) { + console.error("Failed to load connector info:", error); + setError( + error instanceof Error + ? error.message + : "Failed to load connector information" + ); + } finally { + setIsLoading(false); + } + }; - if (provider) { - fetchConnectorInfo(); - } - }, [provider]); + if (provider) { + fetchConnectorInfo(); + } + }, [provider]); - // Watch for sync task completion and redirect - useEffect(() => { - if (!currentSyncTaskId) return; + // Watch for sync task completion and redirect + useEffect(() => { + if (!currentSyncTaskId) return; - const currentTask = tasks.find( - (task) => task.task_id === currentSyncTaskId, - ); + const currentTask = tasks.find( + (task) => task.task_id === currentSyncTaskId + ); - if (currentTask && currentTask.status === "completed") { - // Task completed successfully, show toast and redirect - setIsIngesting(false); - setTimeout(() => { - router.push("/knowledge"); - }, 2000); // 2 second delay to let user see toast - } else if (currentTask && currentTask.status === "failed") { - // Task failed, clear the tracking but don't redirect - setIsIngesting(false); - setCurrentSyncTaskId(null); - } - }, [tasks, currentSyncTaskId, router]); + if (currentTask && currentTask.status === "completed") { + // Task completed successfully, show toast and redirect + setIsIngesting(false); + setTimeout(() => { + router.push("/knowledge"); + }, 2000); // 2 second delay to let user see toast + } else if (currentTask && currentTask.status === "failed") { + // Task failed, clear the tracking but don't redirect + setIsIngesting(false); + setCurrentSyncTaskId(null); + } + }, [tasks, currentSyncTaskId, router]); - const handleFileSelected = (files: CloudFile[]) => { - setSelectedFiles(files); - console.log(`Selected ${files.length} files from ${provider}:`, files); - // You can add additional handling here like triggering sync, etc. - }; + const handleFileSelected = (files: CloudFile[]) => { + setSelectedFiles(files); + console.log(`Selected ${files.length} files from ${provider}:`, files); + // You can add additional handling here like triggering sync, etc. + }; - const handleSync = async (connector: CloudConnector) => { - if (!connector.connectionId || selectedFiles.length === 0) return; + const handleSync = async (connector: CloudConnector) => { + if (!connector.connectionId || selectedFiles.length === 0) return; - setIsIngesting(true); + setIsIngesting(true); - try { - const syncBody: { - connection_id: string; - max_files?: number; - selected_files?: string[]; - settings?: IngestSettings; - } = { - connection_id: connector.connectionId, - selected_files: selectedFiles.map((file) => file.id), - settings: ingestSettings, - }; + try { + const syncBody: { + connection_id: string; + max_files?: number; + selected_files?: string[]; + settings?: IngestSettings; + } = { + connection_id: connector.connectionId, + selected_files: selectedFiles.map((file) => file.id), + settings: ingestSettings, + }; - const response = await fetch(`/api/connectors/${connector.type}/sync`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(syncBody), - }); + const response = await fetch(`/api/connectors/${connector.type}/sync`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(syncBody), + }); - const result = await response.json(); + const result = await response.json(); - if (response.status === 201) { - const taskIds = result.task_ids; - if (taskIds && taskIds.length > 0) { - const taskId = taskIds[0]; // Use the first task ID - addTask(taskId); - setCurrentSyncTaskId(taskId); - } - } else { - console.error("Sync failed:", result.error); - } - } catch (error) { - console.error("Sync error:", error); - setIsIngesting(false); - } - }; + if (response.status === 201) { + const taskIds = result.task_ids; + if (taskIds && taskIds.length > 0) { + const taskId = taskIds[0]; // Use the first task ID + addTask(taskId); + setCurrentSyncTaskId(taskId); + } + } else { + console.error("Sync failed:", result.error); + } + } catch (error) { + console.error("Sync error:", error); + setIsIngesting(false); + } + }; - const getProviderDisplayName = () => { - const nameMap: { [key: string]: string } = { - google_drive: "Google Drive", - onedrive: "OneDrive", - sharepoint: "SharePoint", - }; - return nameMap[provider] || provider; - }; + const getProviderDisplayName = () => { + const nameMap: { [key: string]: string } = { + google_drive: "Google Drive", + onedrive: "OneDrive", + sharepoint: "SharePoint", + }; + return nameMap[provider] || provider; + }; - if (isLoading) { - return ( -
-
-
-
-

Loading {getProviderDisplayName()} connector...

-
-
-
- ); - } + if (isLoading) { + return ( +
+
+
+
+

Loading {getProviderDisplayName()} connector...

+
+
+
+ ); + } - if (error || !connector) { - return ( -
-
- -
+ if (error || !connector) { + return ( +
+
+ +
-
-
- -

- Provider Not Available -

-

{error}

- -
-
-
- ); - } +
+
+ +

+ Provider Not Available +

+

{error}

+ +
+
+
+ ); + } - if (connector.status !== "connected") { - return ( -
-
- -
+ if (connector.status !== "connected") { + return ( +
+
+ +
-
-
- -

- {connector.name} Not Connected -

-

- You need to connect your {connector.name} account before you can - select files. -

- -
-
-
- ); - } +
+
+ +

+ {connector.name} Not Connected +

+

+ You need to connect your {connector.name} account before you can + select files. +

+ +
+
+
+ ); + } - if (!connector.hasAccessToken) { - return ( -
-
- -
+ if (!connector.hasAccessToken) { + return ( +
+
+ +
-
-
- -

- Access Token Required -

-

- {connector.accessTokenError || - `Unable to get access token for ${connector.name}. Try reconnecting your account.`} -

- -
-
-
- ); - } +
+
+ +

+ Access Token Required +

+

+ {connector.accessTokenError || + `Unable to get access token for ${connector.name}. Try reconnecting your account.`} +

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

- Add from {getProviderDisplayName()} -

-
+ return ( +
+
+ +

+ Add from {getProviderDisplayName()} +

+
-
- -
+
+ +
-
-
- - -
-
-
- ); +
+
+ + +
+
+
+ ); } diff --git a/frontend/src/components/cloud-picker/picker-header.tsx b/frontend/src/components/cloud-picker/picker-header.tsx index 05dcaebd..e0d9cfa4 100644 --- a/frontend/src/components/cloud-picker/picker-header.tsx +++ b/frontend/src/components/cloud-picker/picker-header.tsx @@ -51,19 +51,13 @@ export const PickerHeader = ({ Select files from {getProviderName(provider)} to ingest.

-
- csv, json, pdf,{" "} - +16 more{" "} - 150 MB max -
); From 088ed2697b5eadf5802cf8b39e2e8c71c93c03e1 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Fri, 3 Oct 2025 12:00:13 -0600 Subject: [PATCH 09/20] updates from sync --- frontend/src/app/knowledge/chunks/page.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index bbb7f019..85a4e3f2 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -161,7 +161,7 @@ function ChunksPageContent() {
-
+
{selectedFilter?.name && (
setQueryInputText(e.target.value)} value={queryInputText} />
-
+ {/*
Select all -
+
*/}
@@ -269,6 +269,10 @@ function ChunksPageContent() {
+ + {chunk.score.toFixed(2)} score + + {/* TODO: Update to use active toggle */} {/* Date: Fri, 3 Oct 2025 12:12:47 -0600 Subject: [PATCH 10/20] remove checkboxes --- frontend/src/app/knowledge/chunks/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index 85a4e3f2..cde2c3e8 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -240,14 +240,14 @@ function ChunksPageContent() { >
-
+ {/*
handleChunkCardCheckboxChange(index) } /> -
+
*/} Chunk {chunk.index} @@ -282,7 +282,7 @@ function ChunksPageContent() { Active */}
-
+
{chunk.text}
From 99cc806a559f526e9e4a837c41096eef19fd3116 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Fri, 3 Oct 2025 13:53:39 -0600 Subject: [PATCH 11/20] fix empty state --- frontend/src/app/knowledge/chunks/page.tsx | 103 +++++++++++---------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index cde2c3e8..31b741a4 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -224,10 +224,9 @@ function ChunksPageContent() { ) : chunks.length === 0 ? (
- -

No chunks found

-

- This file may not have been indexed yet +

No knowledge

+

+ Clear the knowledge filter or return to the knowledge page

@@ -292,24 +291,29 @@ function ChunksPageContent() {
{/* Right panel - Summary (TODO), Technical details, */} -
-
-

Technical details

-
-
-
Total chunks
-
- {chunks.length} -
-
-
-
Avg length
-
- {averageChunkLength.toFixed(0)} chars -
-
- {/* TODO: Uncomment after data is available */} - {/*
+ {chunks.length > 0 && ( +
+
+

+ Technical details +

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

Original document

-
- {/*
+
+
+
+

+ Original document +

+
+ {/*
Name
{fileData?.filename}
*/} -
-
Type
-
- {fileData ? getFileTypeLabel(fileData.mimetype) : "Unknown"} -
-
-
-
Size
-
- {fileData?.size - ? `${Math.round(fileData.size / 1024)} KB` - : "Unknown"} -
-
- {/*
+
+
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 */} - {/*
+ {/* TODO: Uncomment after data is available */} + {/*
Source
*/} - {/*
+ {/*
Updated
N/A
*/} -
+
+
-
+ )} ); } From 3c97a06256d5e9e9ad8016072da3dabddc930dd1 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 3 Oct 2025 15:29:13 -0500 Subject: [PATCH 12/20] Fix layout aligment and folder name --- docker-compose-cpu.yml | 2 +- docker-compose.yml | 2 +- frontend/src/components/layout-wrapper.tsx | 5 +++-- src/tui/_assets/docker-compose-cpu.yml | 2 +- src/tui/_assets/docker-compose.yml | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docker-compose-cpu.yml b/docker-compose-cpu.yml index 9c121f89..968f3ba7 100644 --- a/docker-compose-cpu.yml +++ b/docker-compose-cpu.yml @@ -109,5 +109,5 @@ services: - LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD} - LANGFLOW_NEW_USER_IS_ACTIVE=${LANGFLOW_NEW_USER_IS_ACTIVE} - LANGFLOW_ENABLE_SUPERUSER_CLI=${LANGFLOW_ENABLE_SUPERUSER_CLI} - - DEFAULT_FOLDER_NAME="OpenRAG" + - DEFAULT_FOLDER_NAME=OpenRAG - HIDE_GETTING_STARTED_PROGRESS=true diff --git a/docker-compose.yml b/docker-compose.yml index 64226fd5..264efe3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,5 +117,5 @@ services: - LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD} - LANGFLOW_NEW_USER_IS_ACTIVE=${LANGFLOW_NEW_USER_IS_ACTIVE} - LANGFLOW_ENABLE_SUPERUSER_CLI=${LANGFLOW_ENABLE_SUPERUSER_CLI} - - DEFAULT_FOLDER_NAME="OpenRAG" + - DEFAULT_FOLDER_NAME=OpenRAG - HIDE_GETTING_STARTED_PROGRESS=true diff --git a/frontend/src/components/layout-wrapper.tsx b/frontend/src/components/layout-wrapper.tsx index d657d876..79417654 100644 --- a/frontend/src/components/layout-wrapper.tsx +++ b/frontend/src/components/layout-wrapper.tsx @@ -12,6 +12,7 @@ import { KnowledgeFilterPanel } from "@/components/knowledge-filter-panel"; import Logo from "@/components/logo/logo"; import { Navigation } from "@/components/navigation"; import { TaskNotificationMenu } from "@/components/task-notification-menu"; +import { Button } from "@/components/ui/button"; import { UserNav } from "@/components/user-nav"; import { useAuth } from "@/contexts/auth-context"; import { useChat } from "@/contexts/chat-context"; @@ -34,7 +35,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { refreshConversations, startNewConversation, } = useChat(); - const { isLoading: isSettingsLoading } = useGetSettingsQuery({ + const { isLoading: isSettingsLoading, data: settings } = useGetSettingsQuery({ enabled: isAuthenticated || isNoAuthMode, }); const { @@ -102,7 +103,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) { // For all other pages, render with Langflow-styled navigation and task menu return (
- +
{/* Logo/Title */} diff --git a/src/tui/_assets/docker-compose-cpu.yml b/src/tui/_assets/docker-compose-cpu.yml index 9c121f89..968f3ba7 100644 --- a/src/tui/_assets/docker-compose-cpu.yml +++ b/src/tui/_assets/docker-compose-cpu.yml @@ -109,5 +109,5 @@ services: - LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD} - LANGFLOW_NEW_USER_IS_ACTIVE=${LANGFLOW_NEW_USER_IS_ACTIVE} - LANGFLOW_ENABLE_SUPERUSER_CLI=${LANGFLOW_ENABLE_SUPERUSER_CLI} - - DEFAULT_FOLDER_NAME="OpenRAG" + - DEFAULT_FOLDER_NAME=OpenRAG - HIDE_GETTING_STARTED_PROGRESS=true diff --git a/src/tui/_assets/docker-compose.yml b/src/tui/_assets/docker-compose.yml index 3f1bf1a6..e8cb25d4 100644 --- a/src/tui/_assets/docker-compose.yml +++ b/src/tui/_assets/docker-compose.yml @@ -109,5 +109,5 @@ services: - LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD} - LANGFLOW_NEW_USER_IS_ACTIVE=${LANGFLOW_NEW_USER_IS_ACTIVE} - LANGFLOW_ENABLE_SUPERUSER_CLI=${LANGFLOW_ENABLE_SUPERUSER_CLI} - - DEFAULT_FOLDER_NAME="OpenRAG" + - DEFAULT_FOLDER_NAME=OpenRAG - HIDE_GETTING_STARTED_PROGRESS=true From b93faa26b0799fb6fd2cc9a3d1b95dafb6edb86a Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 3 Oct 2025 15:38:18 -0500 Subject: [PATCH 13/20] fix flow names and descriptions from "Open Search" to "OpenSearch" --- flows/openrag_agent.json | 4 ++-- flows/openrag_nudges.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flows/openrag_agent.json b/flows/openrag_agent.json index 9e1f33cc..a840610d 100644 --- a/flows/openrag_agent.json +++ b/flows/openrag_agent.json @@ -2556,12 +2556,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.0", - "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" From 84a43141d90e06acbc6a0d9f223a652ef66ed072 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 3 Oct 2025 15:54:57 -0500 Subject: [PATCH 14/20] set table structure to true by default --- frontend/src/app/settings/page.tsx | 2 +- frontend/src/lib/constants.ts | 2 +- src/config/config_manager.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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 From 81c82cea411437252fc2f098bc6284aa4860d25d Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Fri, 3 Oct 2025 16:53:48 -0500 Subject: [PATCH 15/20] tweak id fix --- src/services/langflow_file_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/langflow_file_service.py b/src/services/langflow_file_service.py index 7ffdd3aa..14d2f42d 100644 --- a/src/services/langflow_file_service.py +++ b/src/services/langflow_file_service.py @@ -86,12 +86,12 @@ class LangflowFileService: # Pass files via tweaks to File component (File-PSU37 from the flow) if file_paths: - tweaks["File-PSU37"] = {"path": file_paths} + tweaks["DoclingRemote-78KoX"] = {"path": file_paths} # Pass JWT token via tweaks using the x-langflow-global-var- pattern if jwt_token: # Using the global variable pattern that Langflow expects for OpenSearch components - tweaks["OpenSearchHybrid-Ve6bS"] = {"jwt_token": jwt_token} + tweaks["OpenSearchHybrid-XtKoA"] = {"jwt_token": jwt_token} logger.debug("[LF] Added JWT token to tweaks for OpenSearch components") else: logger.warning("[LF] No JWT token provided") From 2493766baaaa96056a84f1521dab12f862119ea6 Mon Sep 17 00:00:00 2001 From: Mike Fortman Date: Mon, 6 Oct 2025 09:50:29 -0500 Subject: [PATCH 16/20] fix tweak id --- src/services/langflow_file_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/langflow_file_service.py b/src/services/langflow_file_service.py index 7639b891..1bce86f0 100644 --- a/src/services/langflow_file_service.py +++ b/src/services/langflow_file_service.py @@ -94,7 +94,7 @@ class LangflowFileService: # Pass JWT token via tweaks using the x-langflow-global-var- pattern if jwt_token: # Using the global variable pattern that Langflow expects for OpenSearch components - tweaks["OpenSearchHybrid-XtKoA"] = {"jwt_token": jwt_token} + tweaks["OpenSearchHybrid-Ve6bS"] = {"jwt_token": jwt_token} logger.debug("[LF] Added JWT token to tweaks for OpenSearch components") else: logger.warning("[LF] No JWT token provided") From 8213f0f6136242e160df49597064d050b30db5e3 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Mon, 6 Oct 2025 08:53:09 -0600 Subject: [PATCH 17/20] revert --- frontend/src/components/cloud-picker/picker-header.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/cloud-picker/picker-header.tsx b/frontend/src/components/cloud-picker/picker-header.tsx index e0d9cfa4..54407aa7 100644 --- a/frontend/src/components/cloud-picker/picker-header.tsx +++ b/frontend/src/components/cloud-picker/picker-header.tsx @@ -51,13 +51,19 @@ export const PickerHeader = ({ Select files from {getProviderName(provider)} to ingest.

+
+ csv, json, pdf,{" "} + +16 more{" "} + 150 MB max +
); From c2f50fe139563453b8edd5cf311095d0ab71a9e3 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Mon, 6 Oct 2025 08:53:39 -0600 Subject: [PATCH 18/20] revert --- frontend/src/components/cloud-picker/picker-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/cloud-picker/picker-header.tsx b/frontend/src/components/cloud-picker/picker-header.tsx index 54407aa7..05dcaebd 100644 --- a/frontend/src/components/cloud-picker/picker-header.tsx +++ b/frontend/src/components/cloud-picker/picker-header.tsx @@ -57,7 +57,7 @@ export const PickerHeader = ({ className="bg-foreground text-background hover:bg-foreground/90 font-semibold" > - Add Files + {isPickerOpen ? "Opening Picker..." : "Add Files"}
csv, json, pdf,{" "} From 47b84a3d8a2e74d7db88ae578a328a6e60d10214 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Mon, 6 Oct 2025 08:55:12 -0600 Subject: [PATCH 19/20] revert --- frontend/src/app/upload/[provider]/page.tsx | 659 ++++++++++---------- 1 file changed, 328 insertions(+), 331 deletions(-) diff --git a/frontend/src/app/upload/[provider]/page.tsx b/frontend/src/app/upload/[provider]/page.tsx index 8e0a306c..7c72ec3d 100644 --- a/frontend/src/app/upload/[provider]/page.tsx +++ b/frontend/src/app/upload/[provider]/page.tsx @@ -12,370 +12,367 @@ import { useTask } from "@/contexts/task-context"; // CloudFile interface is now imported from the unified cloud picker interface CloudConnector { - id: string; - name: string; - description: string; - status: "not_connected" | "connecting" | "connected" | "error"; - type: string; - connectionId?: string; - clientId: string; - hasAccessToken: boolean; - accessTokenError?: string; + id: string; + name: string; + description: string; + status: "not_connected" | "connecting" | "connected" | "error"; + type: string; + connectionId?: string; + clientId: string; + hasAccessToken: boolean; + accessTokenError?: string; } export default function UploadProviderPage() { - const params = useParams(); - const router = useRouter(); - const provider = params.provider as string; - const { addTask, tasks } = useTask(); + const params = useParams(); + const router = useRouter(); + const provider = params.provider as string; + const { addTask, tasks } = useTask(); - const [connector, setConnector] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [accessToken, setAccessToken] = useState(null); - const [selectedFiles, setSelectedFiles] = useState([]); - const [isIngesting, setIsIngesting] = useState(false); - const [currentSyncTaskId, setCurrentSyncTaskId] = useState( - null - ); - const [ingestSettings, setIngestSettings] = useState({ - chunkSize: 1000, - chunkOverlap: 200, - ocr: false, - pictureDescriptions: false, - embeddingModel: "text-embedding-3-small", - }); + const [connector, setConnector] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [accessToken, setAccessToken] = useState(null); + const [selectedFiles, setSelectedFiles] = useState([]); + const [isIngesting, setIsIngesting] = useState(false); + const [currentSyncTaskId, setCurrentSyncTaskId] = useState( + null, + ); + const [ingestSettings, setIngestSettings] = useState({ + chunkSize: 1000, + chunkOverlap: 200, + ocr: false, + pictureDescriptions: false, + embeddingModel: "text-embedding-3-small", + }); - useEffect(() => { - const fetchConnectorInfo = async () => { - setIsLoading(true); - setError(null); + useEffect(() => { + const fetchConnectorInfo = async () => { + setIsLoading(true); + setError(null); - try { - // Fetch available connectors to validate the provider - const connectorsResponse = await fetch("/api/connectors"); - if (!connectorsResponse.ok) { - throw new Error("Failed to load connectors"); - } + try { + // Fetch available connectors to validate the provider + const connectorsResponse = await fetch("/api/connectors"); + if (!connectorsResponse.ok) { + throw new Error("Failed to load connectors"); + } - const connectorsResult = await connectorsResponse.json(); - const providerInfo = connectorsResult.connectors[provider]; + const connectorsResult = await connectorsResponse.json(); + const providerInfo = connectorsResult.connectors[provider]; - if (!providerInfo || !providerInfo.available) { - setError( - `Cloud provider "${provider}" is not available or configured.` - ); - return; - } + if (!providerInfo || !providerInfo.available) { + setError( + `Cloud provider "${provider}" is not available or configured.`, + ); + return; + } - // Check connector status - const statusResponse = await fetch( - `/api/connectors/${provider}/status` - ); - if (!statusResponse.ok) { - throw new Error(`Failed to check ${provider} status`); - } + // Check connector status + const statusResponse = await fetch( + `/api/connectors/${provider}/status`, + ); + if (!statusResponse.ok) { + throw new Error(`Failed to check ${provider} status`); + } - const statusData = await statusResponse.json(); - const connections = statusData.connections || []; - const activeConnection = connections.find( - (conn: { is_active: boolean; connection_id: string }) => - conn.is_active - ); - const isConnected = activeConnection !== undefined; + const statusData = await statusResponse.json(); + const connections = statusData.connections || []; + const activeConnection = connections.find( + (conn: { is_active: boolean; connection_id: string }) => + conn.is_active, + ); + const isConnected = activeConnection !== undefined; - let hasAccessToken = false; - let accessTokenError: string | undefined; + let hasAccessToken = false; + let accessTokenError: string | undefined; - // Try to get access token for connected connectors - if (isConnected && activeConnection) { - try { - const tokenResponse = await fetch( - `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}` - ); - if (tokenResponse.ok) { - const tokenData = await tokenResponse.json(); - if (tokenData.access_token) { - hasAccessToken = true; - setAccessToken(tokenData.access_token); - } - } else { - const errorData = await tokenResponse - .json() - .catch(() => ({ error: "Token unavailable" })); - accessTokenError = errorData.error || "Access token unavailable"; - } - } catch { - accessTokenError = "Failed to fetch access token"; - } - } + // Try to get access token for connected connectors + if (isConnected && activeConnection) { + try { + const tokenResponse = await fetch( + `/api/connectors/${provider}/token?connection_id=${activeConnection.connection_id}`, + ); + if (tokenResponse.ok) { + const tokenData = await tokenResponse.json(); + if (tokenData.access_token) { + hasAccessToken = true; + setAccessToken(tokenData.access_token); + } + } else { + const errorData = await tokenResponse + .json() + .catch(() => ({ error: "Token unavailable" })); + accessTokenError = errorData.error || "Access token unavailable"; + } + } catch { + accessTokenError = "Failed to fetch access token"; + } + } - setConnector({ - id: provider, - name: providerInfo.name, - description: providerInfo.description, - status: isConnected ? "connected" : "not_connected", - type: provider, - connectionId: activeConnection?.connection_id, - clientId: activeConnection?.client_id, - hasAccessToken, - accessTokenError, - }); - } catch (error) { - console.error("Failed to load connector info:", error); - setError( - error instanceof Error - ? error.message - : "Failed to load connector information" - ); - } finally { - setIsLoading(false); - } - }; + setConnector({ + id: provider, + name: providerInfo.name, + description: providerInfo.description, + status: isConnected ? "connected" : "not_connected", + type: provider, + connectionId: activeConnection?.connection_id, + clientId: activeConnection?.client_id, + hasAccessToken, + accessTokenError, + }); + } catch (error) { + console.error("Failed to load connector info:", error); + setError( + error instanceof Error + ? error.message + : "Failed to load connector information", + ); + } finally { + setIsLoading(false); + } + }; - if (provider) { - fetchConnectorInfo(); - } - }, [provider]); + if (provider) { + fetchConnectorInfo(); + } + }, [provider]); - // Watch for sync task completion and redirect - useEffect(() => { - if (!currentSyncTaskId) return; + // Watch for sync task completion and redirect + useEffect(() => { + if (!currentSyncTaskId) return; - const currentTask = tasks.find( - (task) => task.task_id === currentSyncTaskId - ); + const currentTask = tasks.find( + (task) => task.task_id === currentSyncTaskId, + ); - if (currentTask && currentTask.status === "completed") { - // Task completed successfully, show toast and redirect - setIsIngesting(false); - setTimeout(() => { - router.push("/knowledge"); - }, 2000); // 2 second delay to let user see toast - } else if (currentTask && currentTask.status === "failed") { - // Task failed, clear the tracking but don't redirect - setIsIngesting(false); - setCurrentSyncTaskId(null); - } - }, [tasks, currentSyncTaskId, router]); + if (currentTask && currentTask.status === "completed") { + // Task completed successfully, show toast and redirect + setIsIngesting(false); + setTimeout(() => { + router.push("/knowledge"); + }, 2000); // 2 second delay to let user see toast + } else if (currentTask && currentTask.status === "failed") { + // Task failed, clear the tracking but don't redirect + setIsIngesting(false); + setCurrentSyncTaskId(null); + } + }, [tasks, currentSyncTaskId, router]); - const handleFileSelected = (files: CloudFile[]) => { - setSelectedFiles(files); - console.log(`Selected ${files.length} files from ${provider}:`, files); - // You can add additional handling here like triggering sync, etc. - }; + const handleFileSelected = (files: CloudFile[]) => { + setSelectedFiles(files); + console.log(`Selected ${files.length} files from ${provider}:`, files); + // You can add additional handling here like triggering sync, etc. + }; - const handleSync = async (connector: CloudConnector) => { - if (!connector.connectionId || selectedFiles.length === 0) return; + const handleSync = async (connector: CloudConnector) => { + if (!connector.connectionId || selectedFiles.length === 0) return; - setIsIngesting(true); + setIsIngesting(true); - try { - const syncBody: { - connection_id: string; - max_files?: number; - selected_files?: string[]; - settings?: IngestSettings; - } = { - connection_id: connector.connectionId, - selected_files: selectedFiles.map((file) => file.id), - settings: ingestSettings, - }; + try { + const syncBody: { + connection_id: string; + max_files?: number; + selected_files?: string[]; + settings?: IngestSettings; + } = { + connection_id: connector.connectionId, + selected_files: selectedFiles.map((file) => file.id), + settings: ingestSettings, + }; - const response = await fetch(`/api/connectors/${connector.type}/sync`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(syncBody), - }); + const response = await fetch(`/api/connectors/${connector.type}/sync`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(syncBody), + }); - const result = await response.json(); + const result = await response.json(); - if (response.status === 201) { - const taskIds = result.task_ids; - if (taskIds && taskIds.length > 0) { - const taskId = taskIds[0]; // Use the first task ID - addTask(taskId); - setCurrentSyncTaskId(taskId); - } - } else { - console.error("Sync failed:", result.error); - } - } catch (error) { - console.error("Sync error:", error); - setIsIngesting(false); - } - }; + if (response.status === 201) { + const taskIds = result.task_ids; + if (taskIds && taskIds.length > 0) { + const taskId = taskIds[0]; // Use the first task ID + addTask(taskId); + setCurrentSyncTaskId(taskId); + } + } else { + console.error("Sync failed:", result.error); + } + } catch (error) { + console.error("Sync error:", error); + setIsIngesting(false); + } + }; - const getProviderDisplayName = () => { - const nameMap: { [key: string]: string } = { - google_drive: "Google Drive", - onedrive: "OneDrive", - sharepoint: "SharePoint", - }; - return nameMap[provider] || provider; - }; + const getProviderDisplayName = () => { + const nameMap: { [key: string]: string } = { + google_drive: "Google Drive", + onedrive: "OneDrive", + sharepoint: "SharePoint", + }; + return nameMap[provider] || provider; + }; - if (isLoading) { - return ( -
-
-
-
-

Loading {getProviderDisplayName()} connector...

-
-
-
- ); - } + if (isLoading) { + return ( +
+
+
+
+

Loading {getProviderDisplayName()} connector...

+
+
+
+ ); + } - if (error || !connector) { - return ( -
-
- -
+ if (error || !connector) { + return ( +
+
+ +
-
-
- -

- Provider Not Available -

-

{error}

- -
-
-
- ); - } +
+
+ +

+ Provider Not Available +

+

{error}

+ +
+
+
+ ); + } - if (connector.status !== "connected") { - return ( -
-
- -
+ if (connector.status !== "connected") { + return ( +
+
+ +
-
-
- -

- {connector.name} Not Connected -

-

- You need to connect your {connector.name} account before you can - select files. -

- -
-
-
- ); - } +
+
+ +

+ {connector.name} Not Connected +

+

+ You need to connect your {connector.name} account before you can + select files. +

+ +
+
+
+ ); + } - if (!connector.hasAccessToken) { - return ( -
-
- -
+ if (!connector.hasAccessToken) { + return ( +
+
+ +
-
-
- -

- Access Token Required -

-

- {connector.accessTokenError || - `Unable to get access token for ${connector.name}. Try reconnecting your account.`} -

- -
-
-
- ); - } +
+
+ +

+ Access Token Required +

+

+ {connector.accessTokenError || + `Unable to get access token for ${connector.name}. Try reconnecting your account.`} +

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

- Add from {getProviderDisplayName()} -

-
+ return ( +
+
+ +

+ Add from {getProviderDisplayName()} +

+
-
- -
+
+ +
-
-
- - -
-
-
- ); +
+
+ + +
+
+
+ ); } From 1643e10d5f215443c9039c1e07d8024613f96546 Mon Sep 17 00:00:00 2001 From: Brent O'Neill Date: Mon, 6 Oct 2025 09:53:14 -0600 Subject: [PATCH 20/20] Cleanup commented code --- frontend/src/app/knowledge/chunks/page.tsx | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/knowledge/chunks/page.tsx b/frontend/src/app/knowledge/chunks/page.tsx index 9ae3619c..6288bcfc 100644 --- a/frontend/src/app/knowledge/chunks/page.tsx +++ b/frontend/src/app/knowledge/chunks/page.tsx @@ -13,8 +13,8 @@ import { type File, useGetSearchQuery, } from "../../api/queries/useGetSearchQuery"; -import { Label } from "@/components/ui/label"; -import { Checkbox } from "@/components/ui/checkbox"; +// import { Label } from "@/components/ui/label"; +// import { Checkbox } from "@/components/ui/checkbox"; import { filterAccentClasses } from "@/components/knowledge-filter-panel"; const getFileTypeLabel = (mimetype: string) => { @@ -106,20 +106,20 @@ function ChunksPageContent() { 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 (