add document manager and site heaer

clean

format
This commit is contained in:
ArnoChen
2025-02-15 23:22:37 +08:00
parent 4d58ff8bb4
commit 1fe456666a
28 changed files with 1360 additions and 194 deletions

View File

@@ -1,3 +1,4 @@
import axios, { AxiosError } from 'axios'
import { backendBaseUrl } from '@/lib/constants'
import { errorMessage } from '@/lib/utils'
import { useSettingsStore } from '@/stores/settings'
@@ -64,81 +65,64 @@ export type QueryResponse = {
response: string
}
export type DocumentActionResponse = {
status: 'success' | 'partial_success' | 'failure'
message: string
document_count: number
}
export const InvalidApiKeyError = 'Invalid API Key'
export const RequireApiKeError = 'API Key required'
// Helper functions
const getResponseContent = async (response: Response) => {
const contentType = response.headers.get('content-type')
if (contentType) {
if (contentType.includes('application/json')) {
const data = await response.json()
return JSON.stringify(data, undefined, 2)
} else if (contentType.startsWith('text/')) {
return await response.text()
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
return await response.text()
} else if (contentType.includes('application/octet-stream')) {
const buffer = await response.arrayBuffer()
const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true })
return decoder.decode(buffer)
} else {
try {
return await response.text()
} catch (error) {
console.warn('Failed to decode as text, may be binary:', error)
return `[Could not decode response body. Content-Type: ${contentType}]`
}
}
} else {
try {
return await response.text()
} catch (error) {
console.warn('Failed to decode as text, may be binary:', error)
return '[Could not decode response body. No Content-Type header.]'
}
// Axios instance
const axiosInstance = axios.create({
baseURL: backendBaseUrl,
headers: {
'Content-Type': 'application/json'
}
return ''
}
})
const fetchWithAuth = async (url: string, options: RequestInit = {}): Promise<Response> => {
// Interceptoradd api key
axiosInstance.interceptors.request.use((config) => {
const apiKey = useSettingsStore.getState().apiKey
const headers = {
...(options.headers || {}),
...(apiKey ? { 'X-API-Key': apiKey } : {})
if (apiKey) {
config.headers['X-API-Key'] = apiKey
}
return config
})
const response = await fetch(backendBaseUrl + url, {
...options,
headers
})
if (!response.ok) {
throw new Error(
`${response.status} ${response.statusText}\n${await getResponseContent(response)}\n${response.url}`
)
// Interceptorhanle error
axiosInstance.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response) {
throw new Error(
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
error.response.data
)}\n${error.config?.url}`
)
}
throw error
}
return response
}
)
// API methods
export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
const response = await fetchWithAuth(`/graphs?label=${label}`)
return await response.json()
const response = await axiosInstance.get(`/graphs?label=${label}`)
return response.data
}
export const getGraphLabels = async (): Promise<string[]> => {
const response = await fetchWithAuth('/graph/label/list')
return await response.json()
const response = await axiosInstance.get('/graph/label/list')
return response.data
}
export const checkHealth = async (): Promise<
LightragStatus | { status: 'error'; message: string }
> => {
try {
const response = await fetchWithAuth('/health')
return await response.json()
const response = await axiosInstance.get('/health')
return response.data
} catch (e) {
return {
status: 'error',
@@ -148,63 +132,33 @@ export const checkHealth = async (): Promise<
}
export const getDocuments = async (): Promise<string[]> => {
const response = await fetchWithAuth('/documents')
return await response.json()
const response = await axiosInstance.get('/documents')
return response.data
}
export const scanNewDocuments = async (): Promise<{ status: string }> => {
const response = await axiosInstance.post('/documents/scan')
return response.data
}
export const getDocumentsScanProgress = async (): Promise<LightragDocumentsScanProgress> => {
const response = await fetchWithAuth('/documents/scan-progress')
return await response.json()
}
export const uploadDocument = async (
file: File
): Promise<{
status: string
message: string
total_documents: number
}> => {
const formData = new FormData()
formData.append('file', file)
const response = await fetchWithAuth('/documents/upload', {
method: 'POST',
body: formData
})
return await response.json()
}
export const startDocumentScan = async (): Promise<{ status: string }> => {
const response = await fetchWithAuth('/documents/scan', {
method: 'POST'
})
return await response.json()
const response = await axiosInstance.get('/documents/scan-progress')
return response.data
}
export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
const response = await fetchWithAuth('/query', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
})
return await response.json()
const response = await axiosInstance.post('/query', request)
return response.data
}
export const queryTextStream = async (request: QueryRequest, onChunk: (chunk: string) => void) => {
const response = await fetchWithAuth('/query/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
const response = await axiosInstance.post('/query/stream', request, {
responseType: 'stream'
})
const reader = response.body?.getReader()
if (!reader) throw new Error('No response body')
const reader = response.data.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
@@ -226,53 +180,50 @@ export const queryTextStream = async (request: QueryRequest, onChunk: (chunk: st
}
}
// Text insertion API
export const insertText = async (
text: string,
description?: string
): Promise<{
status: string
message: string
document_count: number
}> => {
const response = await fetchWithAuth('/documents/text', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text, description })
})
return await response.json()
): Promise<DocumentActionResponse> => {
const response = await axiosInstance.post('/documents/text', { text, description })
return response.data
}
// Batch file upload API
export const uploadBatchDocuments = async (
files: File[]
): Promise<{
status: string
message: string
document_count: number
}> => {
export const uploadDocument = async (
file: File,
onUploadProgress?: (percentCompleted: number) => void
): Promise<DocumentActionResponse> => {
const formData = new FormData()
files.forEach((file) => {
formData.append('files', file)
})
formData.append('file', file)
const response = await fetchWithAuth('/documents/batch', {
method: 'POST',
body: formData
const response = await axiosInstance.post('/documents/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress:
onUploadProgress !== undefined
? (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total!)
onUploadProgress(percentCompleted)
}
: undefined
})
return await response.json()
return response.data
}
// Clear all documents API
export const clearDocuments = async (): Promise<{
status: string
message: string
document_count: number
}> => {
const response = await fetchWithAuth('/documents', {
method: 'DELETE'
})
return await response.json()
export const batchUploadDocuments = async (
files: File[],
onUploadProgress?: (fileName: string, percentCompleted: number) => void
): Promise<DocumentActionResponse[]> => {
return await Promise.all(
files.map(async (file) => {
return await uploadDocument(file, (percentCompleted) => {
onUploadProgress?.(file.name, percentCompleted)
})
})
)
}
export const clearDocuments = async (): Promise<DocumentActionResponse> => {
const response = await axiosInstance.delete('/documents')
return response.data
}