ArnoChen commited on
Commit
a2251a6
Β·
1 Parent(s): 26e5a24

release new webui

Browse files
lightrag/api/lightrag_server.py CHANGED
@@ -254,10 +254,8 @@ def display_splash_screen(args: argparse.Namespace) -> None:
254
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/docs")
255
  ASCIIColors.white(" β”œβ”€ Alternative Documentation (local): ", end="")
256
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/redoc")
257
- ASCIIColors.white(" β”œβ”€ WebUI (local): ", end="")
258
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/webui")
259
- ASCIIColors.white(" └─ Graph Viewer (local): ", end="")
260
- ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/graph-viewer")
261
 
262
  ASCIIColors.yellow("\nπŸ“ Note:")
263
  ASCIIColors.white(""" Since the server is running on 0.0.0.0:
@@ -1814,17 +1812,9 @@ def create_app(args):
1814
  }
1815
 
1816
  # Webui mount webui/index.html
1817
- webui_dir = Path(__file__).parent / "webui"
1818
- app.mount(
1819
- "/graph-viewer",
1820
- StaticFiles(directory=webui_dir, html=True),
1821
- name="webui",
1822
- )
1823
-
1824
- # Serve the static files
1825
- static_dir = Path(__file__).parent / "static"
1826
  static_dir.mkdir(exist_ok=True)
1827
- app.mount("/webui", StaticFiles(directory=static_dir, html=True), name="static")
1828
 
1829
  return app
1830
 
 
254
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/docs")
255
  ASCIIColors.white(" β”œβ”€ Alternative Documentation (local): ", end="")
256
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/redoc")
257
+ ASCIIColors.white(" └─ WebUI (local): ", end="")
258
  ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/webui")
 
 
259
 
260
  ASCIIColors.yellow("\nπŸ“ Note:")
261
  ASCIIColors.white(""" Since the server is running on 0.0.0.0:
 
1812
  }
1813
 
1814
  # Webui mount webui/index.html
1815
+ static_dir = Path(__file__).parent / "webui"
 
 
 
 
 
 
 
 
1816
  static_dir.mkdir(exist_ok=True)
1817
+ app.mount("/webui", StaticFiles(directory=static_dir, html=True), name="webui")
1818
 
1819
  return app
1820
 
lightrag/api/static/README.md DELETED
@@ -1,2 +0,0 @@
1
- # LightRag Webui
2
- A simple webui to interact with the lightrag datalake
 
 
 
lightrag/api/static/favicon.ico DELETED

Git LFS Details

  • SHA256: 26d6dfa1f5357416c10b39969c6e22843f58c518928bc59e828660ba5746ef94
  • Pointer size: 131 Bytes
  • Size of remote file: 751 kB
lightrag/api/static/index.html DELETED
@@ -1,104 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>LightRAG Interface</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
- <style>
10
- .fade-in {
11
- animation: fadeIn 0.3s ease-in;
12
- }
13
-
14
- @keyframes fadeIn {
15
- from { opacity: 0; }
16
- to { opacity: 1; }
17
- }
18
-
19
- .spin {
20
- animation: spin 1s linear infinite;
21
- }
22
-
23
- @keyframes spin {
24
- from { transform: rotate(0deg); }
25
- to { transform: rotate(360deg); }
26
- }
27
-
28
- .slide-in {
29
- animation: slideIn 0.3s ease-out;
30
- }
31
-
32
- @keyframes slideIn {
33
- from { transform: translateX(-100%); }
34
- to { transform: translateX(0); }
35
- }
36
- </style>
37
- </head>
38
- <body class="bg-gray-50">
39
- <div class="flex h-screen">
40
- <!-- Sidebar -->
41
- <div class="w-64 bg-white shadow-lg">
42
- <div class="p-4">
43
- <h1 class="text-xl font-bold text-gray-800 mb-6">LightRAG</h1>
44
- <nav class="space-y-2">
45
- <a href="#" class="nav-item" data-page="file-manager">
46
- <div class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors">
47
- <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
48
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
49
- </svg>
50
- File Manager
51
- </div>
52
- </a>
53
- <a href="#" class="nav-item" data-page="query">
54
- <div class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors">
55
- <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
56
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
57
- </svg>
58
- Query Database
59
- </div>
60
- </a>
61
- <a href="#" class="nav-item" data-page="knowledge-graph">
62
- <div class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors">
63
- <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
65
- </svg>
66
- Knowledge Graph
67
- </div>
68
- </a>
69
- <a href="#" class="nav-item" data-page="status">
70
- <div class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors">
71
- <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
72
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
73
- </svg>
74
- Status
75
- </div>
76
- </a>
77
- <a href="#" class="nav-item" data-page="settings">
78
- <div class="flex items-center p-2 rounded-lg hover:bg-gray-100 transition-colors">
79
- <svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
80
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
81
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
82
- </svg>
83
- Settings
84
- </div>
85
- </a>
86
- </nav>
87
- </div>
88
- </div>
89
-
90
- <!-- Main Content -->
91
- <div class="flex-1 overflow-auto p-6">
92
- <div id="content" class="fade-in"></div>
93
- </div>
94
-
95
- <!-- Toast Notification -->
96
- <div id="toast" class="fixed bottom-4 right-4 hidden">
97
- <div class="bg-gray-800 text-white px-6 py-3 rounded-lg shadow-lg"></div>
98
- </div>
99
- </div>
100
-
101
- <script src="./js/api.js"></script>
102
-
103
- </body>
104
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lightrag/api/static/js/api.js DELETED
@@ -1,408 +0,0 @@
1
- // State management
2
- const state = {
3
- apiKey: localStorage.getItem('apiKey') || '',
4
- files: [],
5
- indexedFiles: [],
6
- currentPage: 'file-manager'
7
- };
8
-
9
- // Utility functions
10
- const showToast = (message, duration = 3000) => {
11
- const toast = document.getElementById('toast');
12
- toast.querySelector('div').textContent = message;
13
- toast.classList.remove('hidden');
14
- setTimeout(() => toast.classList.add('hidden'), duration);
15
- };
16
-
17
- const fetchWithAuth = async (url, options = {}) => {
18
- const headers = {
19
- ...(options.headers || {}),
20
- ...(state.apiKey ? { 'X-API-Key': state.apiKey } : {}) // Use X-API-Key instead of Bearer
21
- };
22
- return fetch(url, { ...options, headers });
23
- };
24
-
25
-
26
- // Page renderers
27
- const pages = {
28
- 'file-manager': () => `
29
- <div class="space-y-6">
30
- <h2 class="text-2xl font-bold text-gray-800">File Manager</h2>
31
-
32
- <div class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-gray-400 transition-colors">
33
- <input type="file" id="fileInput" multiple accept=".txt,.md,.doc,.docx,.pdf,.pptx" class="hidden">
34
- <label for="fileInput" class="cursor-pointer">
35
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
36
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
37
- </svg>
38
- <p class="mt-2 text-gray-600">Drag files here or click to select</p>
39
- <p class="text-sm text-gray-500">Supported formats: TXT, MD, DOC, PDF, PPTX</p>
40
- </label>
41
- </div>
42
-
43
- <div id="fileList" class="space-y-2">
44
- <h3 class="text-lg font-semibold text-gray-700">Selected Files</h3>
45
- <div class="space-y-2"></div>
46
- </div>
47
- <div id="uploadProgress" class="hidden mt-4">
48
- <div class="w-full bg-gray-200 rounded-full h-2.5">
49
- <div class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
50
- </div>
51
- <p class="text-sm text-gray-600 mt-2"><span id="uploadStatus">0</span> files processed</p>
52
- </div>
53
- <div class="flex items-center space-x-4 bg-gray-100 p-4 rounded-lg shadow-md">
54
- <button id="rescanBtn" class="flex items-center bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
55
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="currentColor" class="mr-2">
56
- <path d="M12 4a8 8 0 1 1-8 8H2.5a9.5 9.5 0 1 0 2.8-6.7L2 3v6h6L5.7 6.7A7.96 7.96 0 0 1 12 4z"/>
57
- </svg>
58
- Rescan Files
59
- </button>
60
-
61
- <button id="uploadBtn" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
62
- Upload & Index Files
63
- </button>
64
- </div>
65
-
66
- <div id="indexedFiles" class="space-y-2">
67
- <h3 class="text-lg font-semibold text-gray-700">Indexed Files</h3>
68
- <div class="space-y-2"></div>
69
- </div>
70
-
71
-
72
- </div>
73
- `,
74
-
75
- 'query': () => `
76
- <div class="space-y-6">
77
- <h2 class="text-2xl font-bold text-gray-800">Query Database</h2>
78
-
79
- <div class="space-y-4">
80
- <div>
81
- <label class="block text-sm font-medium text-gray-700">Query Mode</label>
82
- <select id="queryMode" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
83
- <option value="hybrid">Hybrid</option>
84
- <option value="local">Local</option>
85
- <option value="global">Global</option>
86
- <option value="naive">Naive</option>
87
- </select>
88
- </div>
89
-
90
- <div>
91
- <label class="block text-sm font-medium text-gray-700">Query</label>
92
- <textarea id="queryInput" rows="4" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"></textarea>
93
- </div>
94
-
95
- <button id="queryBtn" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
96
- Send Query
97
- </button>
98
-
99
- <div id="queryResult" class="mt-4 p-4 bg-white rounded-lg shadow"></div>
100
- </div>
101
- </div>
102
- `,
103
-
104
- 'knowledge-graph': () => `
105
- <div class="flex items-center justify-center h-full">
106
- <div class="text-center">
107
- <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
108
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
109
- </svg>
110
- <h3 class="mt-2 text-sm font-medium text-gray-900">Under Construction</h3>
111
- <p class="mt-1 text-sm text-gray-500">Knowledge graph visualization will be available in a future update.</p>
112
- </div>
113
- </div>
114
- `,
115
-
116
- 'status': () => `
117
- <div class="space-y-6">
118
- <h2 class="text-2xl font-bold text-gray-800">System Status</h2>
119
- <div id="statusContent" class="grid grid-cols-1 md:grid-cols-2 gap-6">
120
- <div class="p-6 bg-white rounded-lg shadow-sm">
121
- <h3 class="text-lg font-semibold mb-4">System Health</h3>
122
- <div id="healthStatus"></div>
123
- </div>
124
- <div class="p-6 bg-white rounded-lg shadow-sm">
125
- <h3 class="text-lg font-semibold mb-4">Configuration</h3>
126
- <div id="configStatus"></div>
127
- </div>
128
- </div>
129
- </div>
130
- `,
131
-
132
- 'settings': () => `
133
- <div class="space-y-6">
134
- <h2 class="text-2xl font-bold text-gray-800">Settings</h2>
135
-
136
- <div class="max-w-xl">
137
- <div class="space-y-4">
138
- <div>
139
- <label class="block text-sm font-medium text-gray-700">API Key</label>
140
- <input type="password" id="apiKeyInput" value="${state.apiKey}"
141
- class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
142
- </div>
143
-
144
- <button id="saveSettings" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
145
- Save Settings
146
- </button>
147
- </div>
148
- </div>
149
- </div>
150
- `
151
- };
152
-
153
- // Page handlers
154
- const handlers = {
155
- 'file-manager': () => {
156
- const fileInput = document.getElementById('fileInput');
157
- const dropZone = fileInput.parentElement.parentElement;
158
- const fileList = document.querySelector('#fileList div');
159
- const indexedFiles = document.querySelector('#indexedFiles div');
160
- const uploadBtn = document.getElementById('uploadBtn');
161
-
162
- const updateFileList = () => {
163
- fileList.innerHTML = state.files.map(file => `
164
- <div class="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm">
165
- <span>${file.name}</span>
166
- <button class="text-red-600 hover:text-red-700" onclick="removeFile('${file.name}')">
167
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
168
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
169
- </svg>
170
- </button>
171
- </div>
172
- `).join('');
173
- };
174
-
175
- const updateIndexedFiles = async () => {
176
- const response = await fetchWithAuth('/health');
177
- const data = await response.json();
178
- indexedFiles.innerHTML = data.indexed_files.map(file => `
179
- <div class="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm">
180
- <span>${file}</span>
181
- </div>
182
- `).join('');
183
- };
184
-
185
- dropZone.addEventListener('dragover', (e) => {
186
- e.preventDefault();
187
- dropZone.classList.add('border-blue-500');
188
- });
189
-
190
- dropZone.addEventListener('dragleave', () => {
191
- dropZone.classList.remove('border-blue-500');
192
- });
193
-
194
- dropZone.addEventListener('drop', (e) => {
195
- e.preventDefault();
196
- dropZone.classList.remove('border-blue-500');
197
- const files = Array.from(e.dataTransfer.files);
198
- state.files.push(...files);
199
- updateFileList();
200
- });
201
-
202
- fileInput.addEventListener('change', () => {
203
- state.files.push(...Array.from(fileInput.files));
204
- updateFileList();
205
- });
206
-
207
- uploadBtn.addEventListener('click', async () => {
208
- if (state.files.length === 0) {
209
- showToast('Please select files to upload');
210
- return;
211
- }
212
- let apiKey = localStorage.getItem('apiKey') || '';
213
- const progress = document.getElementById('uploadProgress');
214
- const progressBar = progress.querySelector('div');
215
- const statusText = document.getElementById('uploadStatus');
216
- progress.classList.remove('hidden');
217
-
218
- for (let i = 0; i < state.files.length; i++) {
219
- const formData = new FormData();
220
- formData.append('file', state.files[i]);
221
-
222
- try {
223
- await fetch('/documents/upload', {
224
- method: 'POST',
225
- headers: apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {},
226
- body: formData
227
- });
228
-
229
- const percentage = ((i + 1) / state.files.length) * 100;
230
- progressBar.style.width = `${percentage}%`;
231
- statusText.textContent = `${i + 1}/${state.files.length}`;
232
- } catch (error) {
233
- console.error('Upload error:', error);
234
- }
235
- }
236
- progress.classList.add('hidden');
237
- });
238
-
239
- rescanBtn.addEventListener('click', async () => {
240
- const progress = document.getElementById('uploadProgress');
241
- const progressBar = progress.querySelector('div');
242
- const statusText = document.getElementById('uploadStatus');
243
- progress.classList.remove('hidden');
244
-
245
- try {
246
- // Start the scanning process
247
- const scanResponse = await fetch('/documents/scan', {
248
- method: 'POST',
249
- });
250
-
251
- if (!scanResponse.ok) {
252
- throw new Error('Scan failed to start');
253
- }
254
-
255
- // Start polling for progress
256
- const pollInterval = setInterval(async () => {
257
- const progressResponse = await fetch('/documents/scan-progress');
258
- const progressData = await progressResponse.json();
259
-
260
- // Update progress bar
261
- progressBar.style.width = `${progressData.progress}%`;
262
-
263
- // Update status text
264
- if (progressData.total_files > 0) {
265
- statusText.textContent = `Processing ${progressData.current_file} (${progressData.indexed_count}/${progressData.total_files})`;
266
- }
267
-
268
- // Check if scanning is complete
269
- if (!progressData.is_scanning) {
270
- clearInterval(pollInterval);
271
- progress.classList.add('hidden');
272
- statusText.textContent = 'Scan complete!';
273
- }
274
- }, 1000); // Poll every second
275
-
276
- } catch (error) {
277
- console.error('Upload error:', error);
278
- progress.classList.add('hidden');
279
- statusText.textContent = 'Error during scanning process';
280
- }
281
- });
282
-
283
-
284
- updateIndexedFiles();
285
- },
286
-
287
- 'query': () => {
288
- const queryBtn = document.getElementById('queryBtn');
289
- const queryInput = document.getElementById('queryInput');
290
- const queryMode = document.getElementById('queryMode');
291
- const queryResult = document.getElementById('queryResult');
292
-
293
- let apiKey = localStorage.getItem('apiKey') || '';
294
-
295
- queryBtn.addEventListener('click', async () => {
296
- const query = queryInput.value.trim();
297
- if (!query) {
298
- showToast('Please enter a query');
299
- return;
300
- }
301
-
302
- queryBtn.disabled = true;
303
- queryBtn.innerHTML = `
304
- <svg class="animate-spin h-5 w-5 mr-3" viewBox="0 0 24 24">
305
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
306
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
307
- </svg>
308
- Processing...
309
- `;
310
-
311
- try {
312
- const response = await fetchWithAuth('/query', {
313
- method: 'POST',
314
- headers: { 'Content-Type': 'application/json' },
315
- body: JSON.stringify({
316
- query,
317
- mode: queryMode.value,
318
- stream: false,
319
- only_need_context: false
320
- })
321
- });
322
-
323
- const data = await response.json();
324
- queryResult.innerHTML = marked.parse(data.response);
325
- } catch (error) {
326
- showToast('Error processing query');
327
- } finally {
328
- queryBtn.disabled = false;
329
- queryBtn.textContent = 'Send Query';
330
- }
331
- });
332
- },
333
-
334
- 'status': async () => {
335
- const healthStatus = document.getElementById('healthStatus');
336
- const configStatus = document.getElementById('configStatus');
337
-
338
- try {
339
- const response = await fetchWithAuth('/health');
340
- const data = await response.json();
341
-
342
- healthStatus.innerHTML = `
343
- <div class="space-y-2">
344
- <div class="flex items-center">
345
- <div class="w-3 h-3 rounded-full ${data.status === 'healthy' ? 'bg-green-500' : 'bg-red-500'} mr-2"></div>
346
- <span class="font-medium">${data.status}</span>
347
- </div>
348
- <div>
349
- <p class="text-sm text-gray-600">Working Directory: ${data.working_directory}</p>
350
- <p class="text-sm text-gray-600">Input Directory: ${data.input_directory}</p>
351
- <p class="text-sm text-gray-600">Indexed Files: ${data.indexed_files_count}</p>
352
- </div>
353
- </div>
354
- `;
355
-
356
- configStatus.innerHTML = Object.entries(data.configuration)
357
- .map(([key, value]) => `
358
- <div class="mb-2">
359
- <span class="text-sm font-medium text-gray-700">${key}:</span>
360
- <span class="text-sm text-gray-600 ml-2">${value}</span>
361
- </div>
362
- `).join('');
363
- } catch (error) {
364
- showToast('Error fetching status');
365
- }
366
- },
367
-
368
- 'settings': () => {
369
- const saveBtn = document.getElementById('saveSettings');
370
- const apiKeyInput = document.getElementById('apiKeyInput');
371
-
372
- saveBtn.addEventListener('click', () => {
373
- state.apiKey = apiKeyInput.value;
374
- localStorage.setItem('apiKey', state.apiKey);
375
- showToast('Settings saved successfully');
376
- });
377
- }
378
- };
379
-
380
- // Navigation handling
381
- document.querySelectorAll('.nav-item').forEach(item => {
382
- item.addEventListener('click', (e) => {
383
- e.preventDefault();
384
- const page = item.dataset.page;
385
- document.getElementById('content').innerHTML = pages[page]();
386
- if (handlers[page]) handlers[page]();
387
- state.currentPage = page;
388
- });
389
- });
390
-
391
- // Initialize with file manager
392
- document.getElementById('content').innerHTML = pages['file-manager']();
393
- handlers['file-manager']();
394
-
395
- // Global functions
396
- window.removeFile = (fileName) => {
397
- state.files = state.files.filter(file => file.name !== fileName);
398
- document.querySelector('#fileList div').innerHTML = state.files.map(file => `
399
- <div class="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm">
400
- <span>${file.name}</span>
401
- <button class="text-red-600 hover:text-red-700" onclick="removeFile('${file.name}')">
402
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
403
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
404
- </svg>
405
- </button>
406
- </div>
407
- `).join('');
408
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lightrag/api/static/js/graph.js DELETED
@@ -1,211 +0,0 @@
1
- // js/graph.js
2
- function openGraphModal(label) {
3
- const modal = document.getElementById("graph-modal");
4
- const graphTitle = document.getElementById("graph-title");
5
-
6
- if (!modal || !graphTitle) {
7
- console.error("Key element not found");
8
- return;
9
- }
10
-
11
- graphTitle.textContent = `Knowledge Graph - ${label}`;
12
- modal.style.display = "flex";
13
-
14
- renderGraph(label);
15
- }
16
-
17
- function closeGraphModal() {
18
- const modal = document.getElementById("graph-modal");
19
- modal.style.display = "none";
20
- clearGraph();
21
- }
22
-
23
- function clearGraph() {
24
- const svg = document.getElementById("graph-svg");
25
- svg.innerHTML = "";
26
- }
27
-
28
-
29
- async function getGraph(label) {
30
- try {
31
- const response = await fetch(`/graphs?label=${label}`);
32
- const rawData = await response.json();
33
- console.log({data: JSON.parse(JSON.stringify(rawData))});
34
-
35
- const nodes = rawData.nodes
36
-
37
- nodes.forEach(node => {
38
- node.id = Date.now().toString(36) + Math.random().toString(36).substring(2); // 使用 crypto.randomUUID() η”Ÿζˆε”―δΈ€ UUID
39
- });
40
-
41
- // Strictly verify edge data
42
- const edges = (rawData.edges || []).map(edge => {
43
- const sourceNode = nodes.find(n => n.labels.includes(edge.source));
44
- const targetNode = nodes.find(n => n.labels.includes(edge.target)
45
- )
46
- ;
47
- if (!sourceNode || !targetNode) {
48
- console.warn("NOT VALID EDGE:", edge);
49
- return null;
50
- }
51
- return {
52
- source: sourceNode,
53
- target: targetNode,
54
- type: edge.type || ""
55
- };
56
- }).filter(edge => edge !== null);
57
-
58
- return {nodes, edges};
59
- } catch (error) {
60
- console.error("Loading graph failed:", error);
61
- return {nodes: [], edges: []};
62
- }
63
- }
64
-
65
- async function renderGraph(label) {
66
- const data = await getGraph(label);
67
-
68
-
69
- if (!data.nodes || data.nodes.length === 0) {
70
- d3.select("#graph-svg")
71
- .html(`<text x="50%" y="50%" text-anchor="middle">No valid nodes</text>`);
72
- return;
73
- }
74
-
75
-
76
- const svg = d3.select("#graph-svg");
77
- const width = svg.node().clientWidth;
78
- const height = svg.node().clientHeight;
79
-
80
- svg.selectAll("*").remove();
81
-
82
- // Create a force oriented diagram layout
83
- const simulation = d3.forceSimulation(data.nodes)
84
- .force("charge", d3.forceManyBody().strength(-300))
85
- .force("center", d3.forceCenter(width / 2, height / 2));
86
-
87
- // Add a connection (if there are valid edges)
88
- if (data.edges.length > 0) {
89
- simulation.force("link",
90
- d3.forceLink(data.edges)
91
- .id(d => d.id)
92
- .distance(100)
93
- );
94
- }
95
-
96
- // Draw nodes
97
- const nodes = svg.selectAll(".node")
98
- .data(data.nodes)
99
- .enter()
100
- .append("circle")
101
- .attr("class", "node")
102
- .attr("r", 10)
103
- .call(d3.drag()
104
- .on("start", dragStarted)
105
- .on("drag", dragged)
106
- .on("end", dragEnded)
107
- );
108
-
109
-
110
- svg.append("defs")
111
- .append("marker")
112
- .attr("id", "arrow-out")
113
- .attr("viewBox", "0 0 10 10")
114
- .attr("refX", 8)
115
- .attr("refY", 5)
116
- .attr("markerWidth", 6)
117
- .attr("markerHeight", 6)
118
- .attr("orient", "auto")
119
- .append("path")
120
- .attr("d", "M0,0 L10,5 L0,10 Z")
121
- .attr("fill", "#999");
122
-
123
- // Draw edges (with arrows)
124
- const links = svg.selectAll(".link")
125
- .data(data.edges)
126
- .enter()
127
- .append("line")
128
- .attr("class", "link")
129
- .attr("marker-end", "url(#arrow-out)"); // Always draw arrows on the target side
130
-
131
- // Edge style configuration
132
- links
133
- .attr("stroke", "#999")
134
- .attr("stroke-width", 2)
135
- .attr("stroke-opacity", 0.8);
136
-
137
- // Draw label (with background box)
138
- const labels = svg.selectAll(".label")
139
- .data(data.nodes)
140
- .enter()
141
- .append("text")
142
- .attr("class", "label")
143
- .text(d => d.labels[0] || "")
144
- .attr("text-anchor", "start")
145
- .attr("dy", "0.3em")
146
- .attr("fill", "#333");
147
-
148
- // Update Location
149
- simulation.on("tick", () => {
150
- links
151
- .attr("x1", d => {
152
- // Calculate the direction vector from the source node to the target node
153
- const dx = d.target.x - d.source.x;
154
- const dy = d.target.y - d.source.y;
155
- const distance = Math.sqrt(dx * dx + dy * dy);
156
- if (distance === 0) return d.source.x; // 避免陀δ»₯ι›Ά Avoid dividing by zero
157
- // Adjust the starting point coordinates (source node edge) based on radius 10
158
- return d.source.x + (dx / distance) * 10;
159
- })
160
- .attr("y1", d => {
161
- const dx = d.target.x - d.source.x;
162
- const dy = d.target.y - d.source.y;
163
- const distance = Math.sqrt(dx * dx + dy * dy);
164
- if (distance === 0) return d.source.y;
165
- return d.source.y + (dy / distance) * 10;
166
- })
167
- .attr("x2", d => {
168
- // Adjust the endpoint coordinates (target node edge) based on a radius of 10
169
- const dx = d.target.x - d.source.x;
170
- const dy = d.target.y - d.source.y;
171
- const distance = Math.sqrt(dx * dx + dy * dy);
172
- if (distance === 0) return d.target.x;
173
- return d.target.x - (dx / distance) * 10;
174
- })
175
- .attr("y2", d => {
176
- const dx = d.target.x - d.source.x;
177
- const dy = d.target.y - d.source.y;
178
- const distance = Math.sqrt(dx * dx + dy * dy);
179
- if (distance === 0) return d.target.y;
180
- return d.target.y - (dy / distance) * 10;
181
- });
182
-
183
- // Update the position of nodes and labels (keep unchanged)
184
- nodes
185
- .attr("cx", d => d.x)
186
- .attr("cy", d => d.y);
187
-
188
- labels
189
- .attr("x", d => d.x + 12)
190
- .attr("y", d => d.y + 4);
191
- });
192
-
193
- // Drag and drop logic
194
- function dragStarted(event, d) {
195
- if (!event.active) simulation.alphaTarget(0.3).restart();
196
- d.fx = d.x;
197
- d.fy = d.y;
198
- }
199
-
200
- function dragged(event, d) {
201
- d.fx = event.x;
202
- d.fy = event.y;
203
- simulation.alpha(0.3).restart();
204
- }
205
-
206
- function dragEnded(event, d) {
207
- if (!event.active) simulation.alphaTarget(0);
208
- d.fx = null;
209
- d.fy = null;
210
- }
211
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lightrag/api/webui/assets/{index-BAeLPZpd.css β†’ index-BhTFLcnv.css} RENAMED
Binary files a/lightrag/api/webui/assets/index-BAeLPZpd.css and b/lightrag/api/webui/assets/index-BhTFLcnv.css differ
 
lightrag/api/webui/assets/index-CF-pcoIl.js DELETED
Binary file (609 kB)
 
lightrag/api/webui/assets/index-CGBwpbZt.js ADDED
Binary file (826 kB). View file
 
lightrag/api/webui/index.html CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
 
lightrag_webui/bun.lock CHANGED
@@ -43,6 +43,7 @@
43
  "sigma": "^3.0.1",
44
  "sonner": "^1.7.4",
45
  "tailwind-merge": "^3.0.1",
 
46
  "zustand": "^5.0.3",
47
  },
48
  "devDependencies": {
@@ -413,6 +414,8 @@
413
 
414
  "@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
415
 
 
 
416
  "@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
417
 
418
  "@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
@@ -887,6 +890,8 @@
887
 
888
  "prettier-plugin-tailwindcss": ["[email protected]", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
889
 
 
 
890
  "prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
891
 
892
  "proxy-from-env": ["[email protected]", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
@@ -989,6 +994,8 @@
989
 
990
  "tailwind-merge": ["[email protected]", "", {}, "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g=="],
991
 
 
 
992
  "tailwindcss": ["[email protected]", "", {}, "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw=="],
993
 
994
  "tailwindcss-animate": ["[email protected]", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
 
43
  "sigma": "^3.0.1",
44
  "sonner": "^1.7.4",
45
  "tailwind-merge": "^3.0.1",
46
+ "tailwind-scrollbar": "^4.0.0",
47
  "zustand": "^5.0.3",
48
  },
49
  "devDependencies": {
 
414
 
415
  "@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
416
 
417
+ "@types/prismjs": ["@types/[email protected]", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
418
+
419
  "@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
420
 
421
  "@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
 
890
 
891
  "prettier-plugin-tailwindcss": ["[email protected]", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
892
 
893
+ "prism-react-renderer": ["[email protected]", "", { "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": ">=16.0.0" } }, "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig=="],
894
+
895
  "prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
896
 
897
  "proxy-from-env": ["[email protected]", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
 
994
 
995
  "tailwind-merge": ["[email protected]", "", {}, "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g=="],
996
 
997
+ "tailwind-scrollbar": ["[email protected]", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-elqx9m09VHY8gkrMiyimFO09JlS3AyLFXT0eaLaWPi7ImwHlbZj1ce/AxSis2LtR+ewBGEyUV7URNEMcjP1Z2w=="],
998
+
999
  "tailwindcss": ["[email protected]", "", {}, "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw=="],
1000
 
1001
  "tailwindcss-animate": ["[email protected]", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
lightrag_webui/package.json CHANGED
@@ -49,6 +49,7 @@
49
  "sigma": "^3.0.1",
50
  "sonner": "^1.7.4",
51
  "tailwind-merge": "^3.0.1",
 
52
  "zustand": "^5.0.3"
53
  },
54
  "devDependencies": {
 
49
  "sigma": "^3.0.1",
50
  "sonner": "^1.7.4",
51
  "tailwind-merge": "^3.0.1",
52
+ "tailwind-scrollbar": "^4.0.0",
53
  "zustand": "^5.0.3"
54
  },
55
  "devDependencies": {