| import { useState, useCallback, useEffect, useRef } from 'react' |
| import ThemeProvider from '@/components/ThemeProvider' |
| import TabVisibilityProvider from '@/contexts/TabVisibilityProvider' |
| import ApiKeyAlert from '@/components/ApiKeyAlert' |
| import StatusIndicator from '@/components/status/StatusIndicator' |
| import { SiteInfo, webuiPrefix } from '@/lib/constants' |
| import { useBackendState, useAuthStore } from '@/stores/state' |
| import { useSettingsStore } from '@/stores/settings' |
| import { getAuthStatus } from '@/api/lightrag' |
| import SiteHeader from '@/features/SiteHeader' |
| import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag' |
| import { ZapIcon } from 'lucide-react' |
|
|
| import GraphViewer from '@/features/GraphViewer' |
| import DocumentManager from '@/features/DocumentManager' |
| import RetrievalTesting from '@/features/RetrievalTesting' |
| import ApiSite from '@/features/ApiSite' |
|
|
| import { Tabs, TabsContent } from '@/components/ui/Tabs' |
|
|
| function App() { |
| const message = useBackendState.use.message() |
| const enableHealthCheck = useSettingsStore.use.enableHealthCheck() |
| const currentTab = useSettingsStore.use.currentTab() |
| const [apiKeyAlertOpen, setApiKeyAlertOpen] = useState(false) |
| const [initializing, setInitializing] = useState(true) |
| const versionCheckRef = useRef(false); |
| const healthCheckInitializedRef = useRef(false); |
|
|
| const handleApiKeyAlertOpenChange = useCallback((open: boolean) => { |
| setApiKeyAlertOpen(open) |
| if (!open) { |
| useBackendState.getState().clear() |
| } |
| }, []) |
|
|
| |
| const isMountedRef = useRef(true); |
|
|
| |
| useEffect(() => { |
| isMountedRef.current = true; |
|
|
| |
| const handleBeforeUnload = () => { |
| isMountedRef.current = false; |
| }; |
|
|
| window.addEventListener('beforeunload', handleBeforeUnload); |
|
|
| return () => { |
| isMountedRef.current = false; |
| window.removeEventListener('beforeunload', handleBeforeUnload); |
| }; |
| }, []); |
|
|
| |
| useEffect(() => { |
| |
| const performHealthCheck = async () => { |
| try { |
| |
| if (isMountedRef.current) { |
| await useBackendState.getState().check(); |
| } |
| } catch (error) { |
| console.error('Health check error:', error); |
| } |
| }; |
|
|
| |
| useBackendState.getState().setHealthCheckFunction(performHealthCheck); |
|
|
| if (!enableHealthCheck || apiKeyAlertOpen) { |
| useBackendState.getState().clearHealthCheckTimer(); |
| return; |
| } |
|
|
| |
| |
| if (!healthCheckInitializedRef.current) { |
| healthCheckInitializedRef.current = true; |
| } |
|
|
| |
| useBackendState.getState().resetHealthCheckTimer(); |
|
|
| |
| return () => { |
| useBackendState.getState().clearHealthCheckTimer(); |
| }; |
| }, [enableHealthCheck, apiKeyAlertOpen]); |
|
|
| |
| useEffect(() => { |
| const checkVersion = async () => { |
| |
| if (versionCheckRef.current) return; |
| versionCheckRef.current = true; |
|
|
| |
| const versionCheckedFromLogin = sessionStorage.getItem('VERSION_CHECKED_FROM_LOGIN') === 'true'; |
| if (versionCheckedFromLogin) { |
| setInitializing(false); |
| return; |
| } |
|
|
| try { |
| setInitializing(true); |
|
|
| |
| const token = localStorage.getItem('LIGHTRAG-API-TOKEN'); |
| const status = await getAuthStatus(); |
|
|
| |
| if (!status.auth_configured && status.access_token) { |
| useAuthStore.getState().login( |
| status.access_token, |
| true, |
| status.core_version, |
| status.api_version, |
| status.webui_title || null, |
| status.webui_description || null |
| ); |
| } else if (token && (status.core_version || status.api_version || status.webui_title || status.webui_description)) { |
| |
| const isGuestMode = status.auth_mode === 'disabled' || useAuthStore.getState().isGuestMode; |
| useAuthStore.getState().login( |
| token, |
| isGuestMode, |
| status.core_version, |
| status.api_version, |
| status.webui_title || null, |
| status.webui_description || null |
| ); |
| } |
|
|
| |
| sessionStorage.setItem('VERSION_CHECKED_FROM_LOGIN', 'true'); |
| } catch (error) { |
| console.error('Failed to get version info:', error); |
| } finally { |
| |
| setInitializing(false); |
| } |
| }; |
|
|
| |
| checkVersion(); |
| }, []); |
|
|
| const handleTabChange = useCallback( |
| (tab: string) => useSettingsStore.getState().setCurrentTab(tab as any), |
| [] |
| ) |
|
|
| useEffect(() => { |
| if (message) { |
| if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) { |
| setApiKeyAlertOpen(true) |
| } |
| } |
| }, [message]) |
|
|
| return ( |
| <ThemeProvider> |
| <TabVisibilityProvider> |
| {initializing ? ( |
| // Loading state while initializing with simplified header |
| <div className="flex h-screen w-screen flex-col"> |
| {/* Simplified header during initialization - matches SiteHeader structure */} |
| <header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur"> |
| <div className="min-w-[200px] w-auto flex items-center"> |
| <a href={webuiPrefix} className="flex items-center gap-2"> |
| <ZapIcon className="size-4 text-emerald-400" aria-hidden="true" /> |
| <span className="font-bold md:inline-block">{SiteInfo.name}</span> |
| </a> |
| </div> |
| |
| {/* Empty middle section to maintain layout */} |
| <div className="flex h-10 flex-1 items-center justify-center"> |
| </div> |
| |
| {/* Empty right section to maintain layout */} |
| <nav className="w-[200px] flex items-center justify-end"> |
| </nav> |
| </header> |
| |
| {/* Loading indicator in content area */} |
| <div className="flex flex-1 items-center justify-center"> |
| <div className="text-center"> |
| <div className="mb-2 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent mx-auto"></div> |
| <p>Initializing...</p> |
| </div> |
| </div> |
| </div> |
| ) : ( |
| // Main content after initialization |
| <main className="flex h-screen w-screen overflow-hidden"> |
| <Tabs |
| defaultValue={currentTab} |
| className="!m-0 flex grow flex-col !p-0 overflow-hidden" |
| onValueChange={handleTabChange} |
| > |
| <SiteHeader /> |
| <div className="relative grow"> |
| <TabsContent value="documents" className="absolute top-0 right-0 bottom-0 left-0 overflow-auto"> |
| <DocumentManager /> |
| </TabsContent> |
| <TabsContent value="knowledge-graph" className="absolute top-0 right-0 bottom-0 left-0 overflow-hidden"> |
| <GraphViewer /> |
| </TabsContent> |
| <TabsContent value="retrieval" className="absolute top-0 right-0 bottom-0 left-0 overflow-hidden"> |
| <RetrievalTesting /> |
| </TabsContent> |
| <TabsContent value="api" className="absolute top-0 right-0 bottom-0 left-0 overflow-hidden"> |
| <ApiSite /> |
| </TabsContent> |
| </div> |
| </Tabs> |
| {enableHealthCheck && <StatusIndicator />} |
| <ApiKeyAlert open={apiKeyAlertOpen} onOpenChange={handleApiKeyAlertOpenChange} /> |
| </main> |
| )} |
| </TabVisibilityProvider> |
| </ThemeProvider> |
| ) |
| } |
|
|
| export default App |
|
|