ArnoChen
commited on
Commit
·
5856d68
1
Parent(s):
376f172
implement backend health check and alert system
Browse files- lightrag/api/graph_viewer_webui/src/App.tsx +8 -2
- lightrag/api/graph_viewer_webui/src/GraphViewer.tsx +1 -1
- lightrag/api/graph_viewer_webui/src/api/lightrag.ts +97 -0
- lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx +22 -0
- lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx +1 -1
- lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx +1 -1
- lightrag/api/graph_viewer_webui/src/components/Settings.tsx +9 -0
- lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx +49 -0
- lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx +10 -2
- lightrag/api/graph_viewer_webui/src/lib/constants.ts +2 -0
- lightrag/api/graph_viewer_webui/src/stores/state.ts +41 -0
lightrag/api/graph_viewer_webui/src/App.tsx
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
import ThemeProvider from '@/components/ThemeProvider'
|
|
|
2 |
import { GraphViewer } from '@/GraphViewer'
|
|
|
|
|
3 |
|
4 |
function App() {
|
|
|
|
|
5 |
return (
|
6 |
-
<ThemeProvider
|
7 |
-
<div className=
|
8 |
<GraphViewer />
|
9 |
</div>
|
|
|
10 |
</ThemeProvider>
|
11 |
)
|
12 |
}
|
|
|
1 |
import ThemeProvider from '@/components/ThemeProvider'
|
2 |
+
import BackendMessageAlert from '@/components/BackendMessageAlert'
|
3 |
import { GraphViewer } from '@/GraphViewer'
|
4 |
+
import { cn } from '@/lib/utils'
|
5 |
+
import { useBackendState } from '@/stores/state'
|
6 |
|
7 |
function App() {
|
8 |
+
const health = useBackendState.use.health()
|
9 |
+
|
10 |
return (
|
11 |
+
<ThemeProvider>
|
12 |
+
<div className={cn('h-screen w-screen', !health && 'pointer-events-none')}>
|
13 |
<GraphViewer />
|
14 |
</div>
|
15 |
+
{!health && <BackendMessageAlert />}
|
16 |
</ThemeProvider>
|
17 |
)
|
18 |
}
|
lightrag/api/graph_viewer_webui/src/GraphViewer.tsx
CHANGED
@@ -153,7 +153,7 @@ export const GraphViewer = () => {
|
|
153 |
/>
|
154 |
</div>
|
155 |
|
156 |
-
<div className="bg-background/
|
157 |
<Settings />
|
158 |
<ZoomControl />
|
159 |
<LayoutsControl />
|
|
|
153 |
/>
|
154 |
</div>
|
155 |
|
156 |
+
<div className="bg-background/60 absolute bottom-2 left-2 flex flex-col rounded-xl border-2 backdrop-blur-lg">
|
157 |
<Settings />
|
158 |
<ZoomControl />
|
159 |
<LayoutsControl />
|
lightrag/api/graph_viewer_webui/src/api/lightrag.ts
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { backendBaseUrl } from '@/lib/constants'
|
2 |
+
|
3 |
+
export type LightragNodeType = {
|
4 |
+
id: string
|
5 |
+
labels: string[]
|
6 |
+
properties: Record<string, any>
|
7 |
+
}
|
8 |
+
|
9 |
+
export type LightragEdgeType = {
|
10 |
+
id: string
|
11 |
+
source: string
|
12 |
+
target: string
|
13 |
+
type: string
|
14 |
+
properties: Record<string, any>
|
15 |
+
}
|
16 |
+
|
17 |
+
export type LightragGraphType = {
|
18 |
+
nodes: LightragNodeType[]
|
19 |
+
edges: LightragEdgeType[]
|
20 |
+
}
|
21 |
+
|
22 |
+
export type LightragStatus = {
|
23 |
+
status: 'healthy'
|
24 |
+
working_directory: string
|
25 |
+
input_directory: string
|
26 |
+
indexed_files: string[]
|
27 |
+
indexed_files_count: number
|
28 |
+
configuration: {
|
29 |
+
llm_binding: string
|
30 |
+
llm_binding_host: string
|
31 |
+
llm_model: string
|
32 |
+
embedding_binding: string
|
33 |
+
embedding_binding_host: string
|
34 |
+
embedding_model: string
|
35 |
+
max_tokens: number
|
36 |
+
kv_storage: string
|
37 |
+
doc_status_storage: string
|
38 |
+
graph_storage: string
|
39 |
+
vector_storage: string
|
40 |
+
}
|
41 |
+
}
|
42 |
+
|
43 |
+
export type LightragDocumentsScanProgress = {
|
44 |
+
is_scanning: boolean
|
45 |
+
current_file: string
|
46 |
+
indexed_count: number
|
47 |
+
total_files: number
|
48 |
+
progress: number
|
49 |
+
}
|
50 |
+
|
51 |
+
const checkResponse = (response: Response) => {
|
52 |
+
if (!response.ok) {
|
53 |
+
throw new Error(`${response.status} ${response.statusText} ${response.url}`)
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
|
58 |
+
const response = await fetch(backendBaseUrl + `/graphs?label=${label}`)
|
59 |
+
checkResponse(response)
|
60 |
+
return await response.json()
|
61 |
+
}
|
62 |
+
|
63 |
+
export const getGraphLabels = async (): Promise<string[]> => {
|
64 |
+
const response = await fetch(backendBaseUrl + '/graph/label/list')
|
65 |
+
checkResponse(response)
|
66 |
+
return await response.json()
|
67 |
+
}
|
68 |
+
|
69 |
+
export const checkHealth = async (): Promise<
|
70 |
+
LightragStatus | { status: 'error'; message: string }
|
71 |
+
> => {
|
72 |
+
try {
|
73 |
+
const response = await fetch(backendBaseUrl + '/health')
|
74 |
+
if (!response.ok) {
|
75 |
+
return {
|
76 |
+
status: 'error',
|
77 |
+
message: `Health check failed. Service is currently unavailable.\n${response.status} ${response.statusText} ${response.url}`
|
78 |
+
}
|
79 |
+
}
|
80 |
+
return await response.json()
|
81 |
+
} catch (e) {
|
82 |
+
return {
|
83 |
+
status: 'error',
|
84 |
+
message: `${e}`
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
88 |
+
|
89 |
+
export const getDocuments = async (): Promise<string[]> => {
|
90 |
+
const response = await fetch(backendBaseUrl + '/documents')
|
91 |
+
return await response.json()
|
92 |
+
}
|
93 |
+
|
94 |
+
export const getDocumentsScanProgress = async (): Promise<LightragDocumentsScanProgress> => {
|
95 |
+
const response = await fetch(backendBaseUrl + '/documents/scan-progress')
|
96 |
+
return await response.json()
|
97 |
+
}
|
lightrag/api/graph_viewer_webui/src/components/BackendMessageAlert.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/Alert'
|
2 |
+
import { useBackendState } from '@/stores/state'
|
3 |
+
import { AlertCircle } from 'lucide-react'
|
4 |
+
|
5 |
+
const BackendMessageAlert = () => {
|
6 |
+
const health = useBackendState.use.health()
|
7 |
+
const message = useBackendState.use.message()
|
8 |
+
const messageTitle = useBackendState.use.messageTitle()
|
9 |
+
|
10 |
+
return (
|
11 |
+
<Alert
|
12 |
+
variant={health ? 'default' : 'destructive'}
|
13 |
+
className="absolute top-1/2 left-1/2 w-auto -translate-x-1/2 -translate-y-1/2 transform"
|
14 |
+
>
|
15 |
+
{!health && <AlertCircle className="h-4 w-4" />}
|
16 |
+
<AlertTitle>{messageTitle}</AlertTitle>
|
17 |
+
<AlertDescription>{message}</AlertDescription>
|
18 |
+
</Alert>
|
19 |
+
)
|
20 |
+
}
|
21 |
+
|
22 |
+
export default BackendMessageAlert
|
lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx
CHANGED
@@ -70,7 +70,7 @@ export const GraphSearchInput = ({
|
|
70 |
|
71 |
return (
|
72 |
<AsyncSelect
|
73 |
-
className="bg-background/
|
74 |
fetcher={loadOptions}
|
75 |
renderOption={OptionComponent}
|
76 |
getOptionValue={(item) => item.id}
|
|
|
70 |
|
71 |
return (
|
72 |
<AsyncSelect
|
73 |
+
className="bg-background/60 w-52 rounded-xl border-1 opacity-60 backdrop-blur-lg transition-opacity hover:opacity-100"
|
74 |
fetcher={loadOptions}
|
75 |
renderOption={OptionComponent}
|
76 |
getOptionValue={(item) => item.id}
|
lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx
CHANGED
@@ -59,7 +59,7 @@ const PropertiesView = () => {
|
|
59 |
return <></>
|
60 |
}
|
61 |
return (
|
62 |
-
<div className="bg-background/
|
63 |
{currentType == 'node' ? (
|
64 |
<NodePropertiesView node={currentElement as any} />
|
65 |
) : (
|
|
|
59 |
return <></>
|
60 |
}
|
61 |
return (
|
62 |
+
<div className="bg-background/80 max-w-sm rounded-xl border-2 p-2 backdrop-blur-lg">
|
63 |
{currentType == 'node' ? (
|
64 |
<NodePropertiesView node={currentElement as any} />
|
65 |
) : (
|
lightrag/api/graph_viewer_webui/src/components/Settings.tsx
CHANGED
@@ -7,6 +7,8 @@ import { useSettingsStore } from '@/stores/settings'
|
|
7 |
|
8 |
import { SettingsIcon } from 'lucide-react'
|
9 |
|
|
|
|
|
10 |
/**
|
11 |
* Component that displays a checkbox with a label.
|
12 |
*/
|
@@ -95,6 +97,13 @@ export default function Settings() {
|
|
95 |
onCheckedChange={setShowEdgeLabel}
|
96 |
label="Show Edge Label"
|
97 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
</div>
|
99 |
</PopoverContent>
|
100 |
</Popover>
|
|
|
7 |
|
8 |
import { SettingsIcon } from 'lucide-react'
|
9 |
|
10 |
+
import * as Api from '@/api/lightrag'
|
11 |
+
|
12 |
/**
|
13 |
* Component that displays a checkbox with a label.
|
14 |
*/
|
|
|
97 |
onCheckedChange={setShowEdgeLabel}
|
98 |
label="Show Edge Label"
|
99 |
/>
|
100 |
+
<Button
|
101 |
+
onClick={async () => {
|
102 |
+
console.log(Api.checkHealth())
|
103 |
+
}}
|
104 |
+
>
|
105 |
+
Test Api
|
106 |
+
</Button>
|
107 |
</div>
|
108 |
</PopoverContent>
|
109 |
</Popover>
|
lightrag/api/graph_viewer_webui/src/components/ui/Alert.tsx
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
import { cva, type VariantProps } from 'class-variance-authority'
|
3 |
+
|
4 |
+
import { cn } from '@/lib/utils'
|
5 |
+
|
6 |
+
const alertVariants = cva(
|
7 |
+
'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',
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default: 'bg-background text-foreground',
|
12 |
+
destructive:
|
13 |
+
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive'
|
14 |
+
}
|
15 |
+
},
|
16 |
+
defaultVariants: {
|
17 |
+
variant: 'default'
|
18 |
+
}
|
19 |
+
}
|
20 |
+
)
|
21 |
+
|
22 |
+
const Alert = React.forwardRef<
|
23 |
+
HTMLDivElement,
|
24 |
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
25 |
+
>(({ className, variant, ...props }, ref) => (
|
26 |
+
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
27 |
+
))
|
28 |
+
Alert.displayName = 'Alert'
|
29 |
+
|
30 |
+
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
31 |
+
({ className, ...props }, ref) => (
|
32 |
+
<h5
|
33 |
+
ref={ref}
|
34 |
+
className={cn('mb-1 leading-none font-medium tracking-tight', className)}
|
35 |
+
{...props}
|
36 |
+
/>
|
37 |
+
)
|
38 |
+
)
|
39 |
+
AlertTitle.displayName = 'AlertTitle'
|
40 |
+
|
41 |
+
const AlertDescription = React.forwardRef<
|
42 |
+
HTMLParagraphElement,
|
43 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
44 |
+
>(({ className, ...props }, ref) => (
|
45 |
+
<div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
|
46 |
+
))
|
47 |
+
AlertDescription.displayName = 'AlertDescription'
|
48 |
+
|
49 |
+
export { Alert, AlertTitle, AlertDescription }
|
lightrag/api/graph_viewer_webui/src/hooks/useLightragGraph.tsx
CHANGED
@@ -3,6 +3,8 @@ import { useCallback, useEffect, useState } from 'react'
|
|
3 |
import { randomColor } from '@/lib/utils'
|
4 |
import * as Constants from '@/lib/constants'
|
5 |
import { useGraphStore, RawGraph } from '@/stores/graph'
|
|
|
|
|
6 |
|
7 |
const validateGraph = (graph: RawGraph) => {
|
8 |
if (!graph) {
|
@@ -46,8 +48,14 @@ export type NodeType = {
|
|
46 |
export type EdgeType = { label: string }
|
47 |
|
48 |
const fetchGraph = async (label: string) => {
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
let rawGraph = null
|
53 |
|
|
|
3 |
import { randomColor } from '@/lib/utils'
|
4 |
import * as Constants from '@/lib/constants'
|
5 |
import { useGraphStore, RawGraph } from '@/stores/graph'
|
6 |
+
import { queryGraphs } from '@/api/lightrag'
|
7 |
+
import { useBackendState } from '@/stores/state'
|
8 |
|
9 |
const validateGraph = (graph: RawGraph) => {
|
10 |
if (!graph) {
|
|
|
48 |
export type EdgeType = { label: string }
|
49 |
|
50 |
const fetchGraph = async (label: string) => {
|
51 |
+
let rawData: any = null
|
52 |
+
|
53 |
+
try {
|
54 |
+
rawData = await queryGraphs(label)
|
55 |
+
} catch (e) {
|
56 |
+
useBackendState.getState().setErrorMessage(`${e}`, 'Query Graphs Error!')
|
57 |
+
return null
|
58 |
+
}
|
59 |
|
60 |
let rawGraph = null
|
61 |
|
lightrag/api/graph_viewer_webui/src/lib/constants.ts
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
import { ButtonVariantType } from '@/components/ui/Button'
|
2 |
|
|
|
|
|
3 |
export const controlButtonVariant: ButtonVariantType = 'ghost'
|
4 |
|
5 |
export const labelColorDarkTheme = '#B2EBF2'
|
|
|
1 |
import { ButtonVariantType } from '@/components/ui/Button'
|
2 |
|
3 |
+
export const backendBaseUrl = ''
|
4 |
+
|
5 |
export const controlButtonVariant: ButtonVariantType = 'ghost'
|
6 |
|
7 |
export const labelColorDarkTheme = '#B2EBF2'
|
lightrag/api/graph_viewer_webui/src/stores/state.ts
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { create } from 'zustand'
|
2 |
+
import { createSelectors } from '@/lib/utils'
|
3 |
+
import { checkHealth } from '@/api/lightrag'
|
4 |
+
|
5 |
+
interface BackendState {
|
6 |
+
health: boolean
|
7 |
+
message: string | null
|
8 |
+
messageTitle: string | null
|
9 |
+
|
10 |
+
check: () => Promise<boolean>
|
11 |
+
clear: () => void
|
12 |
+
setErrorMessage: (message: string, messageTitle: string) => void
|
13 |
+
}
|
14 |
+
|
15 |
+
const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
16 |
+
health: true,
|
17 |
+
message: null,
|
18 |
+
messageTitle: null,
|
19 |
+
|
20 |
+
check: async () => {
|
21 |
+
const health = await checkHealth()
|
22 |
+
if (health.status === 'healthy') {
|
23 |
+
set({ health: true, message: null, messageTitle: null })
|
24 |
+
return true
|
25 |
+
}
|
26 |
+
set({ health: false, message: health.message, messageTitle: 'Backend Health Check Error!' })
|
27 |
+
return false
|
28 |
+
},
|
29 |
+
|
30 |
+
clear: () => {
|
31 |
+
set({ health: true, message: null, messageTitle: null })
|
32 |
+
},
|
33 |
+
|
34 |
+
setErrorMessage: (message: string, messageTitle: string) => {
|
35 |
+
set({ health: false, message, messageTitle })
|
36 |
+
}
|
37 |
+
}))
|
38 |
+
|
39 |
+
const useBackendState = createSelectors(useBackendStateStoreBase)
|
40 |
+
|
41 |
+
export { useBackendState }
|