From a524520b805c7990cbcb38228a5cab23154cf753 Mon Sep 17 00:00:00 2001 From: choizhang Date: Thu, 10 Apr 2025 13:22:21 +0800 Subject: [PATCH 1/3] refactor(lightrag): use fetch replace axios --- lightrag_webui/src/api/lightrag.ts | 138 ++++++++++++++++++----------- 1 file changed, 87 insertions(+), 51 deletions(-) diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index bd49f7dc..57b03574 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -277,65 +277,101 @@ export const queryTextStream = async ( onChunk: (chunk: string) => void, onError?: (error: string) => void ) => { + const apiKey = useSettingsStore.getState().apiKey; + const token = localStorage.getItem('LIGHTRAG-API-TOKEN'); + const headers: HeadersInit = { + 'Content-Type': 'application/json', + 'Accept': 'application/x-ndjson', + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + if (apiKey) { + headers['X-API-Key'] = apiKey; + } + try { - let buffer = '' - await axiosInstance - .post('/query/stream', request, { - responseType: 'text', - headers: { - Accept: 'application/x-ndjson' - }, - transformResponse: [ - (data: string) => { - // Accumulate the data and process complete lines - buffer += data - const lines = buffer.split('\n') - // Keep the last potentially incomplete line in the buffer - buffer = lines.pop() || '' + const response = await fetch(`${backendBaseUrl}/query/stream`, { + method: 'POST', + headers: headers, + body: JSON.stringify(request), + }); - for (const line of lines) { - if (line.trim()) { - try { - const parsed = JSON.parse(line) - if (parsed.response) { - onChunk(parsed.response) - } else if (parsed.error && onError) { - onError(parsed.error) - } - } catch (e) { - console.error('Error parsing stream chunk:', e) - if (onError) onError('Error parsing server response') - } - } - } - return data - } - ] - }) - .catch((error) => { - if (onError) onError(errorMessage(error)) - }) - - // Process any remaining data in the buffer - if (buffer.trim()) { + if (!response.ok) { + // Handle HTTP errors (e.g., 4xx, 5xx) + let errorBody = 'Unknown error'; try { - const parsed = JSON.parse(buffer) - if (parsed.response) { - onChunk(parsed.response) - } else if (parsed.error && onError) { - onError(parsed.error) + errorBody = await response.text(); // Try to get error details from body + } catch (e) { /* ignore */ } + throw new Error(`HTTP error ${response.status}: ${response.statusText}\n${errorBody}`); + } + + if (!response.body) { + throw new Error('Response body is null'); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; // Stream finished + } + + // Decode the chunk and add to buffer + buffer += decoder.decode(value, { stream: true }); // stream: true handles multi-byte chars split across chunks + + // Process complete lines (NDJSON) + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; // Keep potentially incomplete line in buffer + + for (const line of lines) { + if (line.trim()) { + try { + const parsed = JSON.parse(line); + if (parsed.response) { + console.log('Received chunk:', parsed.response); // Log for debugging + onChunk(parsed.response); + } else if (parsed.error && onError) { + onError(parsed.error); + } + } catch (e) { + console.error('Error parsing stream chunk:', line, e); + if (onError) onError(`Error parsing server response: ${line}`); + } } - } catch (e) { - console.error('Error parsing final chunk:', e) - if (onError) onError('Error parsing server response') } } + + // Process any remaining data in the buffer after the stream ends + if (buffer.trim()) { + try { + const parsed = JSON.parse(buffer); + if (parsed.response) { + onChunk(parsed.response); + } else if (parsed.error && onError) { + onError(parsed.error); + } + } catch (e) { + console.error('Error parsing final chunk:', buffer, e); + if (onError) onError(`Error parsing final server response: ${buffer}`); + } + } + } catch (error) { - const message = errorMessage(error) - console.error('Stream request failed:', message) - if (onError) onError(message) + const message = errorMessage(error); + console.error('Stream request failed:', message); + if (onError) { + onError(message); + } else { + // If no specific onError handler, maybe throw or log more prominently + console.error("Unhandled stream error:", message); + } } -} +}; export const insertText = async (text: string): Promise => { const response = await axiosInstance.post('/documents/text', { text }) From 10513d7ca5352943f183e667388f712aa39bdeb8 Mon Sep 17 00:00:00 2001 From: choizhang Date: Thu, 10 Apr 2025 15:14:08 +0800 Subject: [PATCH 2/3] fix(RetrievalTesting): auto scrollToBottom --- lightrag_webui/src/features/RetrievalTesting.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/lightrag_webui/src/features/RetrievalTesting.tsx b/lightrag_webui/src/features/RetrievalTesting.tsx index 1c3fb2db..8e863a7f 100644 --- a/lightrag_webui/src/features/RetrievalTesting.tsx +++ b/lightrag_webui/src/features/RetrievalTesting.tsx @@ -60,6 +60,7 @@ export default function RetrievalTesting() { } return newMessages }) + scrollToBottom() } // Prepare query parameters From 1541b7b4863188fb63bb110aa33286e774dbd04e Mon Sep 17 00:00:00 2001 From: yangdx Date: Fri, 11 Apr 2025 00:31:08 +0800 Subject: [PATCH 3/3] Fix linting --- lightrag_webui/src/api/lightrag.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts index 57b03574..fbe00d23 100644 --- a/lightrag_webui/src/api/lightrag.ts +++ b/lightrag_webui/src/api/lightrag.ts @@ -244,10 +244,10 @@ export const checkHealth = async (): Promise< try { const response = await axiosInstance.get('/health') return response.data - } catch (e) { + } catch (error) { return { status: 'error', - message: errorMessage(e) + message: errorMessage(error) } } } @@ -302,7 +302,7 @@ export const queryTextStream = async ( let errorBody = 'Unknown error'; try { errorBody = await response.text(); // Try to get error details from body - } catch (e) { /* ignore */ } + } catch { /* ignore */ } throw new Error(`HTTP error ${response.status}: ${response.statusText}\n${errorBody}`); } @@ -314,7 +314,6 @@ export const queryTextStream = async ( const decoder = new TextDecoder(); let buffer = ''; - // eslint-disable-next-line no-constant-condition while (true) { const { done, value } = await reader.read(); if (done) { @@ -338,8 +337,8 @@ export const queryTextStream = async ( } else if (parsed.error && onError) { onError(parsed.error); } - } catch (e) { - console.error('Error parsing stream chunk:', line, e); + } catch (error) { + console.error('Error parsing stream chunk:', line, error); if (onError) onError(`Error parsing server response: ${line}`); } } @@ -355,8 +354,8 @@ export const queryTextStream = async ( } else if (parsed.error && onError) { onError(parsed.error); } - } catch (e) { - console.error('Error parsing final chunk:', buffer, e); + } catch (error) { + console.error('Error parsing final chunk:', buffer, error); if (onError) onError(`Error parsing final server response: ${buffer}`); } } @@ -368,7 +367,7 @@ export const queryTextStream = async ( onError(message); } else { // If no specific onError handler, maybe throw or log more prominently - console.error("Unhandled stream error:", message); + console.error('Unhandled stream error:', message); } } };