From 7d6ffbbd87f2de64ddea87993df9f7e330604666 Mon Sep 17 00:00:00 2001 From: ArnoChen Date: Mon, 10 Feb 2025 23:33:51 +0800 Subject: [PATCH] implement backend health check and alert system --- lightrag/api/graph_viewer_webui/src/App.tsx | 10 +- .../graph_viewer_webui/src/GraphViewer.tsx | 2 +- .../graph_viewer_webui/src/api/lightrag.ts | 97 +++++++++++++++++++ .../src/components/BackendMessageAlert.tsx | 22 +++++ .../src/components/GraphSearch.tsx | 2 +- .../src/components/PropertiesView.tsx | 2 +- .../src/components/Settings.tsx | 9 ++ .../src/components/ui/Alert.tsx | 49 ++++++++++ .../src/hooks/useLightragGraph.tsx | 12 ++- .../graph_viewer_webui/src/lib/constants.ts | 2 + .../graph_viewer_webui/src/stores/state.ts | 41 ++++++++ 11 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 lightrag/api/graph_viewer_webui/src/api/lightrag.ts create mode 100644 lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx create mode 100644 lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx create mode 100644 lightrag/api/graph_viewer_webui/src/stores/state.ts diff --git a/lightrag/api/graph_viewer_webui/src/App.tsx b/lightrag/api/graph_viewer_webui/src/App.tsx index 71853405..d859c239 100644 --- a/lightrag/api/graph_viewer_webui/src/App.tsx +++ b/lightrag/api/graph_viewer_webui/src/App.tsx @@ -1,12 +1,18 @@ import ThemeProvider from '@/components/ThemeProvider' +import BackendMessageAlert from '@/components/BackendMessageAlert' import { GraphViewer } from '@/GraphViewer' +import { cn } from '@/lib/utils' +import { useBackendState } from '@/stores/state' function App() { + const health = useBackendState.use.health() + return ( - -
+ +
+ {!health && }
) } diff --git a/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx b/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx index 41606fe4..8cd8d561 100644 --- a/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx +++ b/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx @@ -153,7 +153,7 @@ export const GraphViewer = () => { />
-
+
diff --git a/lightrag/api/graph_viewer_webui/src/api/lightrag.ts b/lightrag/api/graph_viewer_webui/src/api/lightrag.ts new file mode 100644 index 00000000..89398e61 --- /dev/null +++ b/lightrag/api/graph_viewer_webui/src/api/lightrag.ts @@ -0,0 +1,97 @@ +import { backendBaseUrl } from '@/lib/constants' + +export type LightragNodeType = { + id: string + labels: string[] + properties: Record +} + +export type LightragEdgeType = { + id: string + source: string + target: string + type: string + properties: Record +} + +export type LightragGraphType = { + nodes: LightragNodeType[] + edges: LightragEdgeType[] +} + +export type LightragStatus = { + status: 'healthy' + working_directory: string + input_directory: string + indexed_files: string[] + indexed_files_count: number + configuration: { + llm_binding: string + llm_binding_host: string + llm_model: string + embedding_binding: string + embedding_binding_host: string + embedding_model: string + max_tokens: number + kv_storage: string + doc_status_storage: string + graph_storage: string + vector_storage: string + } +} + +export type LightragDocumentsScanProgress = { + is_scanning: boolean + current_file: string + indexed_count: number + total_files: number + progress: number +} + +const checkResponse = (response: Response) => { + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText} ${response.url}`) + } +} + +export const queryGraphs = async (label: string): Promise => { + const response = await fetch(backendBaseUrl + `/graphs?label=${label}`) + checkResponse(response) + return await response.json() +} + +export const getGraphLabels = async (): Promise => { + const response = await fetch(backendBaseUrl + '/graph/label/list') + checkResponse(response) + return await response.json() +} + +export const checkHealth = async (): Promise< + LightragStatus | { status: 'error'; message: string } +> => { + try { + const response = await fetch(backendBaseUrl + '/health') + if (!response.ok) { + return { + status: 'error', + message: `Health check failed. Service is currently unavailable.\n${response.status} ${response.statusText} ${response.url}` + } + } + return await response.json() + } catch (e) { + return { + status: 'error', + message: `${e}` + } + } +} + +export const getDocuments = async (): Promise => { + const response = await fetch(backendBaseUrl + '/documents') + return await response.json() +} + +export const getDocumentsScanProgress = async (): Promise => { + const response = await fetch(backendBaseUrl + '/documents/scan-progress') + return await response.json() +} diff --git a/lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx b/lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx new file mode 100644 index 00000000..f90799f6 --- /dev/null +++ b/lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx @@ -0,0 +1,22 @@ +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/Alert' +import { useBackendState } from '@/stores/state' +import { AlertCircle } from 'lucide-react' + +const BackendMessageAlert = () => { + const health = useBackendState.use.health() + const message = useBackendState.use.message() + const messageTitle = useBackendState.use.messageTitle() + + return ( + + {!health && } + {messageTitle} + {message} + + ) +} + +export default BackendMessageAlert diff --git a/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx b/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx index f115fd5e..b6267f91 100644 --- a/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx @@ -70,7 +70,7 @@ export const GraphSearchInput = ({ return ( item.id} diff --git a/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx b/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx index 99d4af2e..885bfb10 100644 --- a/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx @@ -59,7 +59,7 @@ const PropertiesView = () => { return <> } return ( -
+
{currentType == 'node' ? ( ) : ( diff --git a/lightrag/api/graph_viewer_webui/src/components/Settings.tsx b/lightrag/api/graph_viewer_webui/src/components/Settings.tsx index 2127e44f..e48df673 100644 --- a/lightrag/api/graph_viewer_webui/src/components/Settings.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/Settings.tsx @@ -7,6 +7,8 @@ import { useSettingsStore } from '@/stores/settings' import { SettingsIcon } from 'lucide-react' +import * as Api from '@/api/lightrag' + /** * Component that displays a checkbox with a label. */ @@ -95,6 +97,13 @@ export default function Settings() { onCheckedChange={setShowEdgeLabel} label="Show Edge Label" /> +
diff --git a/lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx b/lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx new file mode 100644 index 00000000..74eef7fb --- /dev/null +++ b/lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx @@ -0,0 +1,49 @@ +import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7', + { + variants: { + variant: { + default: 'bg-background text-foreground', + destructive: + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive' + } + }, + defaultVariants: { + variant: 'default' + } + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = 'Alert' + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +AlertTitle.displayName = 'AlertTitle' + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = 'AlertDescription' + +export { Alert, AlertTitle, AlertDescription } diff --git a/lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx b/lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx index f1fc95c5..44ce820a 100644 --- a/lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx +++ b/lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx @@ -3,6 +3,8 @@ import { useCallback, useEffect, useState } from 'react' import { randomColor } from '@/lib/utils' import * as Constants from '@/lib/constants' import { useGraphStore, RawGraph } from '@/stores/graph' +import { queryGraphs } from '@/api/lightrag' +import { useBackendState } from '@/stores/state' const validateGraph = (graph: RawGraph) => { if (!graph) { @@ -46,8 +48,14 @@ export type NodeType = { export type EdgeType = { label: string } const fetchGraph = async (label: string) => { - const response = await fetch(`/graphs?label=${label}`) - const rawData = await response.json() + let rawData: any = null + + try { + rawData = await queryGraphs(label) + } catch (e) { + useBackendState.getState().setErrorMessage(`${e}`, 'Query Graphs Error!') + return null + } let rawGraph = null diff --git a/lightrag/api/graph_viewer_webui/src/lib/constants.ts b/lightrag/api/graph_viewer_webui/src/lib/constants.ts index b2bc7b03..0d2fb6c5 100644 --- a/lightrag/api/graph_viewer_webui/src/lib/constants.ts +++ b/lightrag/api/graph_viewer_webui/src/lib/constants.ts @@ -1,5 +1,7 @@ import { ButtonVariantType } from '@/components/ui/Button' +export const backendBaseUrl = '' + export const controlButtonVariant: ButtonVariantType = 'ghost' export const labelColorDarkTheme = '#B2EBF2' diff --git a/lightrag/api/graph_viewer_webui/src/stores/state.ts b/lightrag/api/graph_viewer_webui/src/stores/state.ts new file mode 100644 index 00000000..771b34b1 --- /dev/null +++ b/lightrag/api/graph_viewer_webui/src/stores/state.ts @@ -0,0 +1,41 @@ +import { create } from 'zustand' +import { createSelectors } from '@/lib/utils' +import { checkHealth } from '@/api/lightrag' + +interface BackendState { + health: boolean + message: string | null + messageTitle: string | null + + check: () => Promise + clear: () => void + setErrorMessage: (message: string, messageTitle: string) => void +} + +const useBackendStateStoreBase = create()((set) => ({ + health: true, + message: null, + messageTitle: null, + + check: async () => { + const health = await checkHealth() + if (health.status === 'healthy') { + set({ health: true, message: null, messageTitle: null }) + return true + } + set({ health: false, message: health.message, messageTitle: 'Backend Health Check Error!' }) + return false + }, + + clear: () => { + set({ health: true, message: null, messageTitle: null }) + }, + + setErrorMessage: (message: string, messageTitle: string) => { + set({ health: false, message, messageTitle }) + } +})) + +const useBackendState = createSelectors(useBackendStateStoreBase) + +export { useBackendState }