Merge pull request #792 from ArnoChenFx/light-webui
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- lightrag/api/lightrag_server.py +63 -19
- lightrag/api/static/README.md +0 -2
- lightrag/api/static/favicon.ico +0 -3
- lightrag/api/static/index.html +0 -104
- lightrag/api/static/js/api.js +0 -408
- lightrag/api/static/js/graph.js +0 -211
- lightrag/api/webui/assets/index-BMB0OroL.js +0 -0
- lightrag/api/webui/assets/index-CF-pcoIl.js +0 -0
- lightrag/api/webui/assets/{index-BAeLPZpd.css → index-CLgSwrjG.css} +0 -0
- lightrag/api/webui/index.html +0 -0
- lightrag/lightrag.py +10 -0
- lightrag_webui/README.md +1 -1
- lightrag_webui/bun.lock +77 -10
- lightrag_webui/eslint.config.js +26 -23
- lightrag_webui/index.html +1 -1
- lightrag_webui/package.json +16 -5
- lightrag_webui/src/App.tsx +59 -9
- lightrag_webui/src/api/lightrag.ts +196 -158
- lightrag_webui/src/components/ApiKeyAlert.tsx +77 -0
- lightrag_webui/src/components/MessageAlert.tsx +5 -4
- lightrag_webui/src/components/documents/ClearDocumentsDialog.tsx +52 -0
- lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx +91 -0
- lightrag_webui/src/components/{FocusOnNode.tsx → graph/FocusOnNode.tsx} +0 -0
- lightrag_webui/src/components/{FullScreenControl.tsx → graph/FullScreenControl.tsx} +0 -0
- lightrag_webui/src/components/{GraphControl.tsx → graph/GraphControl.tsx} +0 -0
- lightrag_webui/src/components/{GraphLabels.tsx → graph/GraphLabels.tsx} +0 -0
- lightrag_webui/src/components/{GraphSearch.tsx → graph/GraphSearch.tsx} +0 -0
- lightrag_webui/src/components/{LayoutsControl.tsx → graph/LayoutsControl.tsx} +0 -0
- lightrag_webui/src/components/{PropertiesView.tsx → graph/PropertiesView.tsx} +0 -0
- lightrag_webui/src/components/{Settings.tsx → graph/Settings.tsx} +2 -2
- lightrag_webui/src/components/{StatusCard.tsx → graph/StatusCard.tsx} +0 -2
- lightrag_webui/src/components/{StatusIndicator.tsx → graph/StatusIndicator.tsx} +1 -1
- lightrag_webui/src/components/{ThemeProvider.tsx → graph/ThemeProvider.tsx} +0 -0
- lightrag_webui/src/components/{ThemeToggle.tsx → graph/ThemeToggle.tsx} +2 -0
- lightrag_webui/src/components/{ZoomControl.tsx → graph/ZoomControl.tsx} +0 -0
- lightrag_webui/src/components/retrieval/QuerySettings.tsx +279 -0
- lightrag_webui/src/components/ui/AlertDialog.tsx +115 -0
- lightrag_webui/src/components/ui/AsyncSearch.tsx +1 -1
- lightrag_webui/src/components/ui/Badge.tsx +33 -0
- lightrag_webui/src/components/ui/Button.tsx +2 -1
- lightrag_webui/src/components/ui/Card.tsx +55 -0
- lightrag_webui/src/components/ui/Checkbox.tsx +1 -1
- lightrag_webui/src/components/ui/DataTable.tsx +64 -0
- lightrag_webui/src/components/ui/Dialog.tsx +3 -3
- lightrag_webui/src/components/ui/EmptyCard.tsx +38 -0
- lightrag_webui/src/components/ui/FileUploader.tsx +322 -0
- lightrag_webui/src/components/ui/NumberInput.tsx +131 -0
- lightrag_webui/src/components/ui/Progress.tsx +23 -0
- lightrag_webui/src/components/ui/ScrollArea.tsx +44 -0
- lightrag_webui/src/components/ui/Select.tsx +151 -0
lightrag/api/lightrag_server.py
CHANGED
@@ -19,6 +19,7 @@ from lightrag import LightRAG, QueryParam
|
|
19 |
from lightrag.types import GPTKeywordExtractionFormat
|
20 |
from lightrag.api import __api_version__
|
21 |
from lightrag.utils import EmbeddingFunc
|
|
|
22 |
from enum import Enum
|
23 |
from pathlib import Path
|
24 |
import shutil
|
@@ -253,10 +254,8 @@ def display_splash_screen(args: argparse.Namespace) -> None:
|
|
253 |
ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/docs")
|
254 |
ASCIIColors.white(" ├─ Alternative Documentation (local): ", end="")
|
255 |
ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/redoc")
|
256 |
-
ASCIIColors.white("
|
257 |
ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/webui")
|
258 |
-
ASCIIColors.white(" └─ Graph Viewer (local): ", end="")
|
259 |
-
ASCIIColors.yellow(f"{protocol}://localhost:{args.port}/graph-viewer")
|
260 |
|
261 |
ASCIIColors.yellow("\n📝 Note:")
|
262 |
ASCIIColors.white(""" Since the server is running on 0.0.0.0:
|
@@ -693,6 +692,22 @@ class InsertResponse(BaseModel):
|
|
693 |
message: str
|
694 |
|
695 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
696 |
def QueryRequestToQueryParams(request: QueryRequest):
|
697 |
param = QueryParam(mode=request.mode, stream=request.stream)
|
698 |
if request.only_need_context is not None:
|
@@ -1728,20 +1743,57 @@ def create_app(args):
|
|
1728 |
app.include_router(ollama_api.router, prefix="/api")
|
1729 |
|
1730 |
@app.get("/documents", dependencies=[Depends(optional_api_key)])
|
1731 |
-
async def documents():
|
1732 |
-
"""
|
1733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1734 |
|
1735 |
@app.get("/health", dependencies=[Depends(optional_api_key)])
|
1736 |
async def get_status():
|
1737 |
"""Get current system status"""
|
1738 |
-
files = doc_manager.scan_directory()
|
1739 |
return {
|
1740 |
"status": "healthy",
|
1741 |
"working_directory": str(args.working_dir),
|
1742 |
"input_directory": str(args.input_dir),
|
1743 |
-
"indexed_files": [str(f) for f in files],
|
1744 |
-
"indexed_files_count": len(files),
|
1745 |
"configuration": {
|
1746 |
# LLM configuration binding/host address (if applicable)/model (if applicable)
|
1747 |
"llm_binding": args.llm_binding,
|
@@ -1760,17 +1812,9 @@ def create_app(args):
|
|
1760 |
}
|
1761 |
|
1762 |
# Webui mount webui/index.html
|
1763 |
-
|
1764 |
-
app.mount(
|
1765 |
-
"/graph-viewer",
|
1766 |
-
StaticFiles(directory=webui_dir, html=True),
|
1767 |
-
name="webui",
|
1768 |
-
)
|
1769 |
-
|
1770 |
-
# Serve the static files
|
1771 |
-
static_dir = Path(__file__).parent / "static"
|
1772 |
static_dir.mkdir(exist_ok=True)
|
1773 |
-
app.mount("/webui", StaticFiles(directory=static_dir, html=True), name="
|
1774 |
|
1775 |
return app
|
1776 |
|
|
|
19 |
from lightrag.types import GPTKeywordExtractionFormat
|
20 |
from lightrag.api import __api_version__
|
21 |
from lightrag.utils import EmbeddingFunc
|
22 |
+
from lightrag.base import DocStatus, DocProcessingStatus
|
23 |
from enum import Enum
|
24 |
from pathlib import Path
|
25 |
import shutil
|
|
|
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:
|
|
|
692 |
message: str
|
693 |
|
694 |
|
695 |
+
class DocStatusResponse(BaseModel):
|
696 |
+
id: str
|
697 |
+
content_summary: str
|
698 |
+
content_length: int
|
699 |
+
status: DocStatus
|
700 |
+
created_at: str
|
701 |
+
updated_at: str
|
702 |
+
chunks_count: Optional[int] = None
|
703 |
+
error: Optional[str] = None
|
704 |
+
metadata: Optional[dict[str, Any]] = None
|
705 |
+
|
706 |
+
|
707 |
+
class DocsStatusesResponse(BaseModel):
|
708 |
+
statuses: Dict[DocStatus, List[DocStatusResponse]] = {}
|
709 |
+
|
710 |
+
|
711 |
def QueryRequestToQueryParams(request: QueryRequest):
|
712 |
param = QueryParam(mode=request.mode, stream=request.stream)
|
713 |
if request.only_need_context is not None:
|
|
|
1743 |
app.include_router(ollama_api.router, prefix="/api")
|
1744 |
|
1745 |
@app.get("/documents", dependencies=[Depends(optional_api_key)])
|
1746 |
+
async def documents() -> DocsStatusesResponse:
|
1747 |
+
"""
|
1748 |
+
Get documents statuses
|
1749 |
+
Returns:
|
1750 |
+
DocsStatusesResponse: A response object containing a dictionary where keys are DocStatus
|
1751 |
+
and values are lists of DocStatusResponse objects representing documents in each status category.
|
1752 |
+
"""
|
1753 |
+
try:
|
1754 |
+
statuses = (
|
1755 |
+
DocStatus.PENDING,
|
1756 |
+
DocStatus.PROCESSING,
|
1757 |
+
DocStatus.PROCESSED,
|
1758 |
+
DocStatus.FAILED,
|
1759 |
+
)
|
1760 |
+
|
1761 |
+
tasks = [rag.get_docs_by_status(status) for status in statuses]
|
1762 |
+
results: List[Dict[str, DocProcessingStatus]] = await asyncio.gather(*tasks)
|
1763 |
+
|
1764 |
+
response = DocsStatusesResponse()
|
1765 |
+
|
1766 |
+
for idx, result in enumerate(results):
|
1767 |
+
status = statuses[idx]
|
1768 |
+
for doc_id, doc_status in result.items():
|
1769 |
+
if status not in response.statuses:
|
1770 |
+
response.statuses[status] = []
|
1771 |
+
response.statuses[status].append(
|
1772 |
+
DocStatusResponse(
|
1773 |
+
id=doc_id,
|
1774 |
+
content_summary=doc_status.content_summary,
|
1775 |
+
content_length=doc_status.content_length,
|
1776 |
+
status=doc_status.status,
|
1777 |
+
created_at=doc_status.created_at,
|
1778 |
+
updated_at=doc_status.updated_at,
|
1779 |
+
chunks_count=doc_status.chunks_count,
|
1780 |
+
error=doc_status.error,
|
1781 |
+
metadata=doc_status.metadata,
|
1782 |
+
)
|
1783 |
+
)
|
1784 |
+
return response
|
1785 |
+
except Exception as e:
|
1786 |
+
logging.error(f"Error GET /documents: {str(e)}")
|
1787 |
+
logging.error(traceback.format_exc())
|
1788 |
+
raise HTTPException(status_code=500, detail=str(e))
|
1789 |
|
1790 |
@app.get("/health", dependencies=[Depends(optional_api_key)])
|
1791 |
async def get_status():
|
1792 |
"""Get current system status"""
|
|
|
1793 |
return {
|
1794 |
"status": "healthy",
|
1795 |
"working_directory": str(args.working_dir),
|
1796 |
"input_directory": str(args.input_dir),
|
|
|
|
|
1797 |
"configuration": {
|
1798 |
# LLM configuration binding/host address (if applicable)/model (if applicable)
|
1799 |
"llm_binding": args.llm_binding,
|
|
|
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
|
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-BMB0OroL.js
ADDED
Binary file (832 kB). View file
|
|
lightrag/api/webui/assets/index-CF-pcoIl.js
DELETED
Binary file (609 kB)
|
|
lightrag/api/webui/assets/{index-BAeLPZpd.css → index-CLgSwrjG.css}
RENAMED
Binary files a/lightrag/api/webui/assets/index-BAeLPZpd.css and b/lightrag/api/webui/assets/index-CLgSwrjG.css differ
|
|
lightrag/api/webui/index.html
CHANGED
Binary files a/lightrag/api/webui/index.html and b/lightrag/api/webui/index.html differ
|
|
lightrag/lightrag.py
CHANGED
@@ -1254,6 +1254,16 @@ class LightRAG:
|
|
1254 |
"""
|
1255 |
return await self.doc_status.get_status_counts()
|
1256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1257 |
async def adelete_by_doc_id(self, doc_id: str) -> None:
|
1258 |
"""Delete a document and all its related data
|
1259 |
|
|
|
1254 |
"""
|
1255 |
return await self.doc_status.get_status_counts()
|
1256 |
|
1257 |
+
async def get_docs_by_status(
|
1258 |
+
self, status: DocStatus
|
1259 |
+
) -> dict[str, DocProcessingStatus]:
|
1260 |
+
"""Get documents by status
|
1261 |
+
|
1262 |
+
Returns:
|
1263 |
+
Dict with document id is keys and document status is values
|
1264 |
+
"""
|
1265 |
+
return await self.doc_status.get_docs_by_status(status)
|
1266 |
+
|
1267 |
async def adelete_by_doc_id(self, doc_id: str) -> None:
|
1268 |
"""Delete a document and all its related data
|
1269 |
|
lightrag_webui/README.md
CHANGED
@@ -21,7 +21,7 @@ LightRAG WebUI is a React-based web interface for interacting with the LightRAG
|
|
21 |
Run the following command to build the project:
|
22 |
|
23 |
```bash
|
24 |
-
bun run build
|
25 |
```
|
26 |
|
27 |
This command will bundle the project and output the built files to the `lightrag/api/webui` directory.
|
|
|
21 |
Run the following command to build the project:
|
22 |
|
23 |
```bash
|
24 |
+
bun run build --emptyOutDir
|
25 |
```
|
26 |
|
27 |
This command will bundle the project and output the built files to the `lightrag/api/webui` directory.
|
lightrag_webui/bun.lock
CHANGED
@@ -4,13 +4,19 @@
|
|
4 |
"": {
|
5 |
"name": "lightrag-webui",
|
6 |
"dependencies": {
|
7 |
-
"@faker-js/faker": "^9.
|
|
|
8 |
"@radix-ui/react-checkbox": "^1.1.4",
|
9 |
"@radix-ui/react-dialog": "^1.1.6",
|
10 |
"@radix-ui/react-popover": "^1.1.6",
|
|
|
|
|
|
|
11 |
"@radix-ui/react-separator": "^1.1.2",
|
12 |
"@radix-ui/react-slot": "^1.1.2",
|
|
|
13 |
"@radix-ui/react-tooltip": "^1.1.8",
|
|
|
14 |
"@react-sigma/core": "^5.0.2",
|
15 |
"@react-sigma/graph-search": "^5.0.3",
|
16 |
"@react-sigma/layout-circlepack": "^5.0.2",
|
@@ -22,6 +28,7 @@
|
|
22 |
"@react-sigma/minimap": "^5.0.2",
|
23 |
"@sigma/edge-curve": "^3.1.0",
|
24 |
"@sigma/node-border": "^3.0.0",
|
|
|
25 |
"class-variance-authority": "^0.7.1",
|
26 |
"clsx": "^2.1.1",
|
27 |
"cmdk": "^1.0.4",
|
@@ -31,9 +38,13 @@
|
|
31 |
"minisearch": "^7.1.1",
|
32 |
"react": "^19.0.0",
|
33 |
"react-dom": "^19.0.0",
|
|
|
|
|
34 |
"seedrandom": "^3.0.5",
|
35 |
"sigma": "^3.0.1",
|
|
|
36 |
"tailwind-merge": "^3.0.1",
|
|
|
37 |
"zustand": "^5.0.3",
|
38 |
},
|
39 |
"devDependencies": {
|
@@ -41,19 +52,19 @@
|
|
41 |
"@stylistic/eslint-plugin-js": "^3.1.0",
|
42 |
"@tailwindcss/vite": "^4.0.6",
|
43 |
"@types/bun": "^1.2.2",
|
44 |
-
"@types/node": "^22.13.
|
45 |
"@types/react": "^19.0.8",
|
46 |
"@types/react-dom": "^19.0.3",
|
47 |
"@types/seedrandom": "^3.0.8",
|
48 |
"@vitejs/plugin-react-swc": "^3.8.0",
|
49 |
-
"eslint": "^9.20.
|
50 |
"eslint-config-prettier": "^10.0.1",
|
51 |
"eslint-plugin-react": "^7.37.4",
|
52 |
"eslint-plugin-react-hooks": "^5.1.0",
|
53 |
"eslint-plugin-react-refresh": "^0.4.19",
|
54 |
-
"globals": "^15.
|
55 |
"graphology-types": "^0.24.8",
|
56 |
-
"prettier": "^3.5.
|
57 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
58 |
"tailwindcss": "^4.0.6",
|
59 |
"tailwindcss-animate": "^1.0.7",
|
@@ -172,7 +183,7 @@
|
|
172 |
|
173 |
"@eslint/plugin-kit": ["@eslint/[email protected]", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
|
174 |
|
175 |
-
"@faker-js/faker": ["@faker-js/faker@9.
|
176 |
|
177 |
"@floating-ui/core": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
|
178 |
|
@@ -206,18 +217,26 @@
|
|
206 |
|
207 |
"@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
208 |
|
|
|
|
|
209 |
"@radix-ui/primitive": ["@radix-ui/[email protected]", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
|
210 |
|
|
|
|
|
211 |
"@radix-ui/react-arrow": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
|
212 |
|
213 |
"@radix-ui/react-checkbox": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
|
214 |
|
|
|
|
|
215 |
"@radix-ui/react-compose-refs": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
|
216 |
|
217 |
"@radix-ui/react-context": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
|
218 |
|
219 |
"@radix-ui/react-dialog": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="],
|
220 |
|
|
|
|
|
221 |
"@radix-ui/react-dismissable-layer": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
|
222 |
|
223 |
"@radix-ui/react-focus-guards": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
|
@@ -236,10 +255,20 @@
|
|
236 |
|
237 |
"@radix-ui/react-primitive": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
|
238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
"@radix-ui/react-separator": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
|
240 |
|
241 |
"@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
|
242 |
|
|
|
|
|
243 |
"@radix-ui/react-tooltip": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="],
|
244 |
|
245 |
"@radix-ui/react-use-callback-ref": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
|
@@ -384,10 +413,12 @@
|
|
384 |
|
385 |
"@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
386 |
|
387 |
-
"@types/node": ["@types/[email protected].
|
388 |
|
389 |
"@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
390 |
|
|
|
|
|
391 |
"@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
|
392 |
|
393 |
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
|
@@ -446,8 +477,14 @@
|
|
446 |
|
447 |
"async-function": ["[email protected]", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
448 |
|
|
|
|
|
|
|
|
|
449 |
"available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
450 |
|
|
|
|
|
451 |
"babel-plugin-macros": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
|
452 |
|
453 |
"balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
@@ -478,6 +515,8 @@
|
|
478 |
|
479 |
"color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
480 |
|
|
|
|
|
481 |
"concat-map": ["[email protected]", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
482 |
|
483 |
"convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
@@ -502,6 +541,8 @@
|
|
502 |
|
503 |
"define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
504 |
|
|
|
|
|
505 |
"detect-libc": ["[email protected]", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
506 |
|
507 |
"detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
@@ -536,7 +577,7 @@
|
|
536 |
|
537 |
"escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
538 |
|
539 |
-
"eslint": ["[email protected].
|
540 |
|
541 |
"eslint-config-prettier": ["[email protected]", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "build/bin/cli.js" } }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="],
|
542 |
|
@@ -574,6 +615,8 @@
|
|
574 |
|
575 |
"file-entry-cache": ["[email protected]", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
576 |
|
|
|
|
|
577 |
"fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
578 |
|
579 |
"find-root": ["[email protected]", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
|
@@ -584,8 +627,12 @@
|
|
584 |
|
585 |
"flatted": ["[email protected]", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
|
586 |
|
|
|
|
|
587 |
"for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
|
588 |
|
|
|
|
|
589 |
"fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
590 |
|
591 |
"function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
@@ -604,7 +651,7 @@
|
|
604 |
|
605 |
"glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
606 |
|
607 |
-
"globals": ["globals@15.
|
608 |
|
609 |
"globalthis": ["[email protected]", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
610 |
|
@@ -778,6 +825,10 @@
|
|
778 |
|
779 |
"micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
780 |
|
|
|
|
|
|
|
|
|
781 |
"minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
782 |
|
783 |
"minisearch": ["[email protected]", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
|
@@ -838,12 +889,16 @@
|
|
838 |
|
839 |
"prelude-ls": ["[email protected]", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
840 |
|
841 |
-
"prettier": ["[email protected].
|
842 |
|
843 |
"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=="],
|
844 |
|
|
|
|
|
845 |
"prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
846 |
|
|
|
|
|
847 |
"punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
848 |
|
849 |
"queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
@@ -852,8 +907,12 @@
|
|
852 |
|
853 |
"react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
|
854 |
|
|
|
|
|
855 |
"react-is": ["[email protected]", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
856 |
|
|
|
|
|
857 |
"react-remove-scroll": ["[email protected]", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
|
858 |
|
859 |
"react-remove-scroll-bar": ["[email protected]", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
@@ -912,6 +971,8 @@
|
|
912 |
|
913 |
"sigma": ["[email protected]", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
|
914 |
|
|
|
|
|
915 |
"source-map": ["[email protected]", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
916 |
|
917 |
"source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
@@ -936,6 +997,8 @@
|
|
936 |
|
937 |
"tailwind-merge": ["[email protected]", "", {}, "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g=="],
|
938 |
|
|
|
|
|
939 |
"tailwindcss": ["[email protected]", "", {}, "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw=="],
|
940 |
|
941 |
"tailwindcss-animate": ["[email protected]", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
|
@@ -1006,12 +1069,16 @@
|
|
1006 |
|
1007 |
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
1008 |
|
|
|
|
|
1009 |
"@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
1010 |
|
1011 |
"@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
1012 |
|
1013 |
"babel-plugin-macros/resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
1014 |
|
|
|
|
|
1015 |
"fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
1016 |
|
1017 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
|
|
4 |
"": {
|
5 |
"name": "lightrag-webui",
|
6 |
"dependencies": {
|
7 |
+
"@faker-js/faker": "^9.5.0",
|
8 |
+
"@radix-ui/react-alert-dialog": "^1.1.6",
|
9 |
"@radix-ui/react-checkbox": "^1.1.4",
|
10 |
"@radix-ui/react-dialog": "^1.1.6",
|
11 |
"@radix-ui/react-popover": "^1.1.6",
|
12 |
+
"@radix-ui/react-progress": "^1.1.2",
|
13 |
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
14 |
+
"@radix-ui/react-select": "^2.1.6",
|
15 |
"@radix-ui/react-separator": "^1.1.2",
|
16 |
"@radix-ui/react-slot": "^1.1.2",
|
17 |
+
"@radix-ui/react-tabs": "^1.1.3",
|
18 |
"@radix-ui/react-tooltip": "^1.1.8",
|
19 |
+
"@radix-ui/react-use-controllable-state": "^1.1.0",
|
20 |
"@react-sigma/core": "^5.0.2",
|
21 |
"@react-sigma/graph-search": "^5.0.3",
|
22 |
"@react-sigma/layout-circlepack": "^5.0.2",
|
|
|
28 |
"@react-sigma/minimap": "^5.0.2",
|
29 |
"@sigma/edge-curve": "^3.1.0",
|
30 |
"@sigma/node-border": "^3.0.0",
|
31 |
+
"axios": "^1.7.9",
|
32 |
"class-variance-authority": "^0.7.1",
|
33 |
"clsx": "^2.1.1",
|
34 |
"cmdk": "^1.0.4",
|
|
|
38 |
"minisearch": "^7.1.1",
|
39 |
"react": "^19.0.0",
|
40 |
"react-dom": "^19.0.0",
|
41 |
+
"react-dropzone": "^14.3.5",
|
42 |
+
"react-number-format": "^5.4.3",
|
43 |
"seedrandom": "^3.0.5",
|
44 |
"sigma": "^3.0.1",
|
45 |
+
"sonner": "^1.7.4",
|
46 |
"tailwind-merge": "^3.0.1",
|
47 |
+
"tailwind-scrollbar": "^4.0.0",
|
48 |
"zustand": "^5.0.3",
|
49 |
},
|
50 |
"devDependencies": {
|
|
|
52 |
"@stylistic/eslint-plugin-js": "^3.1.0",
|
53 |
"@tailwindcss/vite": "^4.0.6",
|
54 |
"@types/bun": "^1.2.2",
|
55 |
+
"@types/node": "^22.13.4",
|
56 |
"@types/react": "^19.0.8",
|
57 |
"@types/react-dom": "^19.0.3",
|
58 |
"@types/seedrandom": "^3.0.8",
|
59 |
"@vitejs/plugin-react-swc": "^3.8.0",
|
60 |
+
"eslint": "^9.20.1",
|
61 |
"eslint-config-prettier": "^10.0.1",
|
62 |
"eslint-plugin-react": "^7.37.4",
|
63 |
"eslint-plugin-react-hooks": "^5.1.0",
|
64 |
"eslint-plugin-react-refresh": "^0.4.19",
|
65 |
+
"globals": "^15.15.0",
|
66 |
"graphology-types": "^0.24.8",
|
67 |
+
"prettier": "^3.5.1",
|
68 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
69 |
"tailwindcss": "^4.0.6",
|
70 |
"tailwindcss-animate": "^1.0.7",
|
|
|
183 |
|
184 |
"@eslint/plugin-kit": ["@eslint/[email protected]", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
|
185 |
|
186 |
+
"@faker-js/faker": ["@faker-js/faker@9.5.0", "", {}, "sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw=="],
|
187 |
|
188 |
"@floating-ui/core": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
|
189 |
|
|
|
217 |
|
218 |
"@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
219 |
|
220 |
+
"@radix-ui/number": ["@radix-ui/[email protected]", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="],
|
221 |
+
|
222 |
"@radix-ui/primitive": ["@radix-ui/[email protected]", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
|
223 |
|
224 |
+
"@radix-ui/react-alert-dialog": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="],
|
225 |
+
|
226 |
"@radix-ui/react-arrow": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
|
227 |
|
228 |
"@radix-ui/react-checkbox": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
|
229 |
|
230 |
+
"@radix-ui/react-collection": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="],
|
231 |
+
|
232 |
"@radix-ui/react-compose-refs": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
|
233 |
|
234 |
"@radix-ui/react-context": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
|
235 |
|
236 |
"@radix-ui/react-dialog": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="],
|
237 |
|
238 |
+
"@radix-ui/react-direction": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="],
|
239 |
+
|
240 |
"@radix-ui/react-dismissable-layer": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
|
241 |
|
242 |
"@radix-ui/react-focus-guards": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
|
|
|
255 |
|
256 |
"@radix-ui/react-primitive": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
|
257 |
|
258 |
+
"@radix-ui/react-progress": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA=="],
|
259 |
+
|
260 |
+
"@radix-ui/react-roving-focus": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw=="],
|
261 |
+
|
262 |
+
"@radix-ui/react-scroll-area": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ=="],
|
263 |
+
|
264 |
+
"@radix-ui/react-select": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg=="],
|
265 |
+
|
266 |
"@radix-ui/react-separator": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
|
267 |
|
268 |
"@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
|
269 |
|
270 |
+
"@radix-ui/react-tabs": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng=="],
|
271 |
+
|
272 |
"@radix-ui/react-tooltip": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="],
|
273 |
|
274 |
"@radix-ui/react-use-callback-ref": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
|
|
|
413 |
|
414 |
"@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
415 |
|
416 |
+
"@types/node": ["@types/[email protected].4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="],
|
417 |
|
418 |
"@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
419 |
|
420 |
+
"@types/prismjs": ["@types/[email protected]", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
|
421 |
+
|
422 |
"@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
|
423 |
|
424 |
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
|
|
|
477 |
|
478 |
"async-function": ["[email protected]", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
479 |
|
480 |
+
"asynckit": ["[email protected]", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
481 |
+
|
482 |
+
"attr-accept": ["[email protected]", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
|
483 |
+
|
484 |
"available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
485 |
|
486 |
+
"axios": ["[email protected]", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
|
487 |
+
|
488 |
"babel-plugin-macros": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
|
489 |
|
490 |
"balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
|
|
515 |
|
516 |
"color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
517 |
|
518 |
+
"combined-stream": ["[email protected]", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
519 |
+
|
520 |
"concat-map": ["[email protected]", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
521 |
|
522 |
"convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
|
|
541 |
|
542 |
"define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
543 |
|
544 |
+
"delayed-stream": ["[email protected]", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
545 |
+
|
546 |
"detect-libc": ["[email protected]", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
547 |
|
548 |
"detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
|
|
|
577 |
|
578 |
"escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
579 |
|
580 |
+
"eslint": ["[email protected].1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g=="],
|
581 |
|
582 |
"eslint-config-prettier": ["[email protected]", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "build/bin/cli.js" } }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="],
|
583 |
|
|
|
615 |
|
616 |
"file-entry-cache": ["[email protected]", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
617 |
|
618 |
+
"file-selector": ["[email protected]", "", { "dependencies": { "tslib": "^2.7.0" } }, "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig=="],
|
619 |
+
|
620 |
"fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
621 |
|
622 |
"find-root": ["[email protected]", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
|
|
|
627 |
|
628 |
"flatted": ["[email protected]", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
|
629 |
|
630 |
+
"follow-redirects": ["[email protected]", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
|
631 |
+
|
632 |
"for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
|
633 |
|
634 |
+
"form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
|
635 |
+
|
636 |
"fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
637 |
|
638 |
"function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
|
|
651 |
|
652 |
"glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
653 |
|
654 |
+
"globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
|
655 |
|
656 |
"globalthis": ["[email protected]", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
|
657 |
|
|
|
825 |
|
826 |
"micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
827 |
|
828 |
+
"mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
829 |
+
|
830 |
+
"mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
831 |
+
|
832 |
"minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
833 |
|
834 |
"minisearch": ["[email protected]", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
|
|
|
889 |
|
890 |
"prelude-ls": ["[email protected]", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
891 |
|
892 |
+
"prettier": ["[email protected].1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw=="],
|
893 |
|
894 |
"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=="],
|
895 |
|
896 |
+
"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=="],
|
897 |
+
|
898 |
"prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
899 |
|
900 |
+
"proxy-from-env": ["[email protected]", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
901 |
+
|
902 |
"punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
903 |
|
904 |
"queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
|
|
907 |
|
908 |
"react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
|
909 |
|
910 |
+
"react-dropzone": ["[email protected]", "", { "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.8 || 18.0.0" } }, "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ=="],
|
911 |
+
|
912 |
"react-is": ["[email protected]", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
913 |
|
914 |
+
"react-number-format": ["[email protected]", "", { "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ=="],
|
915 |
+
|
916 |
"react-remove-scroll": ["[email protected]", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
|
917 |
|
918 |
"react-remove-scroll-bar": ["[email protected]", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
|
|
971 |
|
972 |
"sigma": ["[email protected]", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
|
973 |
|
974 |
+
"sonner": ["[email protected]", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
|
975 |
+
|
976 |
"source-map": ["[email protected]", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
977 |
|
978 |
"source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
|
|
997 |
|
998 |
"tailwind-merge": ["[email protected]", "", {}, "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g=="],
|
999 |
|
1000 |
+
"tailwind-scrollbar": ["[email protected]", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-elqx9m09VHY8gkrMiyimFO09JlS3AyLFXT0eaLaWPi7ImwHlbZj1ce/AxSis2LtR+ewBGEyUV7URNEMcjP1Z2w=="],
|
1001 |
+
|
1002 |
"tailwindcss": ["[email protected]", "", {}, "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw=="],
|
1003 |
|
1004 |
"tailwindcss-animate": ["[email protected]", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
|
|
|
1069 |
|
1070 |
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
1071 |
|
1072 |
+
"@types/ws/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
1073 |
+
|
1074 |
"@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
1075 |
|
1076 |
"@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
|
1077 |
|
1078 |
"babel-plugin-macros/resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
1079 |
|
1080 |
+
"bun-types/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
|
1081 |
+
|
1082 |
"fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
1083 |
|
1084 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
lightrag_webui/eslint.config.js
CHANGED
@@ -7,27 +7,30 @@ import tseslint from 'typescript-eslint'
|
|
7 |
import prettier from 'eslint-config-prettier'
|
8 |
import react from 'eslint-plugin-react'
|
9 |
|
10 |
-
export default tseslint.config(
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
32 |
}
|
33 |
-
|
|
|
7 |
import prettier from 'eslint-config-prettier'
|
8 |
import react from 'eslint-plugin-react'
|
9 |
|
10 |
+
export default tseslint.config(
|
11 |
+
{ ignores: ['dist'] },
|
12 |
+
{
|
13 |
+
extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier],
|
14 |
+
files: ['**/*.{ts,tsx,js,jsx}'],
|
15 |
+
languageOptions: {
|
16 |
+
ecmaVersion: 2020,
|
17 |
+
globals: globals.browser
|
18 |
+
},
|
19 |
+
settings: { react: { version: '19.0' } },
|
20 |
+
plugins: {
|
21 |
+
'react-hooks': reactHooks,
|
22 |
+
'react-refresh': reactRefresh,
|
23 |
+
'@stylistic/js': stylisticJs,
|
24 |
+
react
|
25 |
+
},
|
26 |
+
rules: {
|
27 |
+
...reactHooks.configs.recommended.rules,
|
28 |
+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
29 |
+
...react.configs.recommended.rules,
|
30 |
+
...react.configs['jsx-runtime'].rules,
|
31 |
+
'@stylistic/js/indent': ['error', 2],
|
32 |
+
'@stylistic/js/quotes': ['error', 'single'],
|
33 |
+
'@typescript-eslint/no-explicit-any': ['off']
|
34 |
+
}
|
35 |
}
|
36 |
+
)
|
lightrag_webui/index.html
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
<meta charset="UTF-8" />
|
5 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
-
<title>Lightrag
|
8 |
</head>
|
9 |
<body>
|
10 |
<div id="root"></div>
|
|
|
4 |
<meta charset="UTF-8" />
|
5 |
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title>Lightrag</title>
|
8 |
</head>
|
9 |
<body>
|
10 |
<div id="root"></div>
|
lightrag_webui/package.json
CHANGED
@@ -10,13 +10,19 @@
|
|
10 |
"preview": "bunx --bun vite preview"
|
11 |
},
|
12 |
"dependencies": {
|
13 |
-
"@faker-js/faker": "^9.
|
|
|
14 |
"@radix-ui/react-checkbox": "^1.1.4",
|
15 |
"@radix-ui/react-dialog": "^1.1.6",
|
16 |
"@radix-ui/react-popover": "^1.1.6",
|
|
|
|
|
|
|
17 |
"@radix-ui/react-separator": "^1.1.2",
|
18 |
"@radix-ui/react-slot": "^1.1.2",
|
|
|
19 |
"@radix-ui/react-tooltip": "^1.1.8",
|
|
|
20 |
"@react-sigma/core": "^5.0.2",
|
21 |
"@react-sigma/graph-search": "^5.0.3",
|
22 |
"@react-sigma/layout-circlepack": "^5.0.2",
|
@@ -28,6 +34,7 @@
|
|
28 |
"@react-sigma/minimap": "^5.0.2",
|
29 |
"@sigma/edge-curve": "^3.1.0",
|
30 |
"@sigma/node-border": "^3.0.0",
|
|
|
31 |
"class-variance-authority": "^0.7.1",
|
32 |
"clsx": "^2.1.1",
|
33 |
"cmdk": "^1.0.4",
|
@@ -37,9 +44,13 @@
|
|
37 |
"minisearch": "^7.1.1",
|
38 |
"react": "^19.0.0",
|
39 |
"react-dom": "^19.0.0",
|
|
|
|
|
40 |
"seedrandom": "^3.0.5",
|
41 |
"sigma": "^3.0.1",
|
|
|
42 |
"tailwind-merge": "^3.0.1",
|
|
|
43 |
"zustand": "^5.0.3"
|
44 |
},
|
45 |
"devDependencies": {
|
@@ -47,19 +58,19 @@
|
|
47 |
"@stylistic/eslint-plugin-js": "^3.1.0",
|
48 |
"@tailwindcss/vite": "^4.0.6",
|
49 |
"@types/bun": "^1.2.2",
|
50 |
-
"@types/node": "^22.13.
|
51 |
"@types/react": "^19.0.8",
|
52 |
"@types/react-dom": "^19.0.3",
|
53 |
"@types/seedrandom": "^3.0.8",
|
54 |
"@vitejs/plugin-react-swc": "^3.8.0",
|
55 |
-
"eslint": "^9.20.
|
56 |
"eslint-config-prettier": "^10.0.1",
|
57 |
"eslint-plugin-react": "^7.37.4",
|
58 |
"eslint-plugin-react-hooks": "^5.1.0",
|
59 |
"eslint-plugin-react-refresh": "^0.4.19",
|
60 |
-
"globals": "^15.
|
61 |
"graphology-types": "^0.24.8",
|
62 |
-
"prettier": "^3.5.
|
63 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
64 |
"tailwindcss": "^4.0.6",
|
65 |
"tailwindcss-animate": "^1.0.7",
|
|
|
10 |
"preview": "bunx --bun vite preview"
|
11 |
},
|
12 |
"dependencies": {
|
13 |
+
"@faker-js/faker": "^9.5.0",
|
14 |
+
"@radix-ui/react-alert-dialog": "^1.1.6",
|
15 |
"@radix-ui/react-checkbox": "^1.1.4",
|
16 |
"@radix-ui/react-dialog": "^1.1.6",
|
17 |
"@radix-ui/react-popover": "^1.1.6",
|
18 |
+
"@radix-ui/react-progress": "^1.1.2",
|
19 |
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
20 |
+
"@radix-ui/react-select": "^2.1.6",
|
21 |
"@radix-ui/react-separator": "^1.1.2",
|
22 |
"@radix-ui/react-slot": "^1.1.2",
|
23 |
+
"@radix-ui/react-tabs": "^1.1.3",
|
24 |
"@radix-ui/react-tooltip": "^1.1.8",
|
25 |
+
"@radix-ui/react-use-controllable-state": "^1.1.0",
|
26 |
"@react-sigma/core": "^5.0.2",
|
27 |
"@react-sigma/graph-search": "^5.0.3",
|
28 |
"@react-sigma/layout-circlepack": "^5.0.2",
|
|
|
34 |
"@react-sigma/minimap": "^5.0.2",
|
35 |
"@sigma/edge-curve": "^3.1.0",
|
36 |
"@sigma/node-border": "^3.0.0",
|
37 |
+
"axios": "^1.7.9",
|
38 |
"class-variance-authority": "^0.7.1",
|
39 |
"clsx": "^2.1.1",
|
40 |
"cmdk": "^1.0.4",
|
|
|
44 |
"minisearch": "^7.1.1",
|
45 |
"react": "^19.0.0",
|
46 |
"react-dom": "^19.0.0",
|
47 |
+
"react-dropzone": "^14.3.5",
|
48 |
+
"react-number-format": "^5.4.3",
|
49 |
"seedrandom": "^3.0.5",
|
50 |
"sigma": "^3.0.1",
|
51 |
+
"sonner": "^1.7.4",
|
52 |
"tailwind-merge": "^3.0.1",
|
53 |
+
"tailwind-scrollbar": "^4.0.0",
|
54 |
"zustand": "^5.0.3"
|
55 |
},
|
56 |
"devDependencies": {
|
|
|
58 |
"@stylistic/eslint-plugin-js": "^3.1.0",
|
59 |
"@tailwindcss/vite": "^4.0.6",
|
60 |
"@types/bun": "^1.2.2",
|
61 |
+
"@types/node": "^22.13.4",
|
62 |
"@types/react": "^19.0.8",
|
63 |
"@types/react-dom": "^19.0.3",
|
64 |
"@types/seedrandom": "^3.0.8",
|
65 |
"@vitejs/plugin-react-swc": "^3.8.0",
|
66 |
+
"eslint": "^9.20.1",
|
67 |
"eslint-config-prettier": "^10.0.1",
|
68 |
"eslint-plugin-react": "^7.37.4",
|
69 |
"eslint-plugin-react-hooks": "^5.1.0",
|
70 |
"eslint-plugin-react-refresh": "^0.4.19",
|
71 |
+
"globals": "^15.15.0",
|
72 |
"graphology-types": "^0.24.8",
|
73 |
+
"prettier": "^3.5.1",
|
74 |
"prettier-plugin-tailwindcss": "^0.6.11",
|
75 |
"tailwindcss": "^4.0.6",
|
76 |
"tailwindcss-animate": "^1.0.7",
|
lightrag_webui/src/App.tsx
CHANGED
@@ -1,17 +1,30 @@
|
|
1 |
-
import
|
|
|
2 |
import MessageAlert from '@/components/MessageAlert'
|
3 |
-
import
|
4 |
-
import
|
5 |
import { healthCheckInterval } from '@/lib/constants'
|
6 |
import { useBackendState } from '@/stores/state'
|
7 |
import { useSettingsStore } from '@/stores/settings'
|
8 |
import { useEffect } from 'react'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
function App() {
|
11 |
const message = useBackendState.use.message()
|
12 |
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
|
|
|
|
13 |
|
14 |
-
//
|
15 |
useEffect(() => {
|
16 |
if (!enableHealthCheck) return
|
17 |
|
@@ -24,13 +37,50 @@ function App() {
|
|
24 |
return () => clearInterval(interval)
|
25 |
}, [enableHealthCheck])
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
return (
|
28 |
<ThemeProvider>
|
29 |
-
<
|
30 |
-
<
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
</ThemeProvider>
|
35 |
)
|
36 |
}
|
|
|
1 |
+
import { useState, useCallback } from 'react'
|
2 |
+
import ThemeProvider from '@/components/graph/ThemeProvider'
|
3 |
import MessageAlert from '@/components/MessageAlert'
|
4 |
+
import ApiKeyAlert from '@/components/ApiKeyAlert'
|
5 |
+
import StatusIndicator from '@/components/graph/StatusIndicator'
|
6 |
import { healthCheckInterval } from '@/lib/constants'
|
7 |
import { useBackendState } from '@/stores/state'
|
8 |
import { useSettingsStore } from '@/stores/settings'
|
9 |
import { useEffect } from 'react'
|
10 |
+
import { Toaster } from 'sonner'
|
11 |
+
import SiteHeader from '@/features/SiteHeader'
|
12 |
+
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
13 |
+
|
14 |
+
import GraphViewer from '@/features/GraphViewer'
|
15 |
+
import DocumentManager from '@/features/DocumentManager'
|
16 |
+
import RetrievalTesting from '@/features/RetrievalTesting'
|
17 |
+
import ApiSite from '@/features/ApiSite'
|
18 |
+
|
19 |
+
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
20 |
|
21 |
function App() {
|
22 |
const message = useBackendState.use.message()
|
23 |
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
|
24 |
+
const [currentTab] = useState(() => useSettingsStore.getState().currentTab)
|
25 |
+
const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
|
26 |
|
27 |
+
// Health check
|
28 |
useEffect(() => {
|
29 |
if (!enableHealthCheck) return
|
30 |
|
|
|
37 |
return () => clearInterval(interval)
|
38 |
}, [enableHealthCheck])
|
39 |
|
40 |
+
const handleTabChange = useCallback(
|
41 |
+
(tab: string) => useSettingsStore.getState().setCurrentTab(tab as any),
|
42 |
+
[]
|
43 |
+
)
|
44 |
+
|
45 |
+
useEffect(() => {
|
46 |
+
if (message) {
|
47 |
+
if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
|
48 |
+
setApiKeyInvalid(true)
|
49 |
+
return
|
50 |
+
}
|
51 |
+
}
|
52 |
+
setApiKeyInvalid(false)
|
53 |
+
}, [message, setApiKeyInvalid])
|
54 |
+
|
55 |
return (
|
56 |
<ThemeProvider>
|
57 |
+
<main className="flex h-screen w-screen overflow-x-hidden">
|
58 |
+
<Tabs
|
59 |
+
defaultValue={currentTab}
|
60 |
+
className="!m-0 flex grow flex-col !p-0"
|
61 |
+
onValueChange={handleTabChange}
|
62 |
+
>
|
63 |
+
<SiteHeader />
|
64 |
+
<div className="relative grow">
|
65 |
+
<TabsContent value="documents" className="absolute top-0 right-0 bottom-0 left-0">
|
66 |
+
<DocumentManager />
|
67 |
+
</TabsContent>
|
68 |
+
<TabsContent value="knowledge-graph" className="absolute top-0 right-0 bottom-0 left-0">
|
69 |
+
<GraphViewer />
|
70 |
+
</TabsContent>
|
71 |
+
<TabsContent value="retrieval" className="absolute top-0 right-0 bottom-0 left-0">
|
72 |
+
<RetrievalTesting />
|
73 |
+
</TabsContent>
|
74 |
+
<TabsContent value="api" className="absolute top-0 right-0 bottom-0 left-0">
|
75 |
+
<ApiSite />
|
76 |
+
</TabsContent>
|
77 |
+
</div>
|
78 |
+
</Tabs>
|
79 |
+
{enableHealthCheck && <StatusIndicator />}
|
80 |
+
{message !== null && !apiKeyInvalid && <MessageAlert />}
|
81 |
+
{apiKeyInvalid && <ApiKeyAlert />}
|
82 |
+
<Toaster />
|
83 |
+
</main>
|
84 |
</ThemeProvider>
|
85 |
)
|
86 |
}
|
lightrag_webui/src/api/lightrag.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import { backendBaseUrl } from '@/lib/constants'
|
2 |
import { errorMessage } from '@/lib/utils'
|
3 |
import { useSettingsStore } from '@/stores/settings'
|
@@ -26,8 +27,6 @@ export type LightragStatus = {
|
|
26 |
status: 'healthy'
|
27 |
working_directory: string
|
28 |
input_directory: string
|
29 |
-
indexed_files: string[]
|
30 |
-
indexed_files_count: number
|
31 |
configuration: {
|
32 |
llm_binding: string
|
33 |
llm_binding_host: string
|
@@ -51,94 +50,133 @@ export type LightragDocumentsScanProgress = {
|
|
51 |
progress: number
|
52 |
}
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
export type QueryMode = 'naive' | 'local' | 'global' | 'hybrid' | 'mix'
|
55 |
|
|
|
|
|
|
|
|
|
|
|
56 |
export type QueryRequest = {
|
57 |
query: string
|
|
|
58 |
mode: QueryMode
|
59 |
-
|
60 |
only_need_context?: boolean
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
}
|
62 |
|
63 |
export type QueryResponse = {
|
64 |
response: string
|
65 |
}
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
export const InvalidApiKeyError = 'Invalid API Key'
|
68 |
export const RequireApiKeError = 'API Key required'
|
69 |
|
70 |
-
//
|
71 |
-
const
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
const data = await response.json()
|
76 |
-
return JSON.stringify(data, undefined, 2)
|
77 |
-
} else if (contentType.startsWith('text/')) {
|
78 |
-
return await response.text()
|
79 |
-
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
|
80 |
-
return await response.text()
|
81 |
-
} else if (contentType.includes('application/octet-stream')) {
|
82 |
-
const buffer = await response.arrayBuffer()
|
83 |
-
const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true })
|
84 |
-
return decoder.decode(buffer)
|
85 |
-
} else {
|
86 |
-
try {
|
87 |
-
return await response.text()
|
88 |
-
} catch (error) {
|
89 |
-
console.warn('Failed to decode as text, may be binary:', error)
|
90 |
-
return `[Could not decode response body. Content-Type: ${contentType}]`
|
91 |
-
}
|
92 |
-
}
|
93 |
-
} else {
|
94 |
-
try {
|
95 |
-
return await response.text()
|
96 |
-
} catch (error) {
|
97 |
-
console.warn('Failed to decode as text, may be binary:', error)
|
98 |
-
return '[Could not decode response body. No Content-Type header.]'
|
99 |
-
}
|
100 |
}
|
101 |
-
|
102 |
-
}
|
103 |
|
104 |
-
|
|
|
105 |
const apiKey = useSettingsStore.getState().apiKey
|
106 |
-
|
107 |
-
|
108 |
-
...(apiKey ? { 'X-API-Key': apiKey } : {})
|
109 |
}
|
|
|
|
|
110 |
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
120 |
}
|
121 |
-
|
122 |
-
return response
|
123 |
-
}
|
124 |
|
125 |
// API methods
|
126 |
export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
|
127 |
-
const response = await
|
128 |
-
return
|
129 |
}
|
130 |
|
131 |
export const getGraphLabels = async (): Promise<string[]> => {
|
132 |
-
const response = await
|
133 |
-
return
|
134 |
}
|
135 |
|
136 |
export const checkHealth = async (): Promise<
|
137 |
LightragStatus | { status: 'error'; message: string }
|
138 |
> => {
|
139 |
try {
|
140 |
-
const response = await
|
141 |
-
return
|
142 |
} catch (e) {
|
143 |
return {
|
144 |
status: 'error',
|
@@ -147,132 +185,132 @@ export const checkHealth = async (): Promise<
|
|
147 |
}
|
148 |
}
|
149 |
|
150 |
-
export const getDocuments = async (): Promise<
|
151 |
-
const response = await
|
152 |
-
return
|
153 |
}
|
154 |
|
155 |
-
export const
|
156 |
-
const response = await
|
157 |
-
return
|
158 |
}
|
159 |
|
160 |
-
export const
|
161 |
-
|
162 |
-
|
163 |
-
status: string
|
164 |
-
message: string
|
165 |
-
total_documents: number
|
166 |
-
}> => {
|
167 |
-
const formData = new FormData()
|
168 |
-
formData.append('file', file)
|
169 |
-
|
170 |
-
const response = await fetchWithAuth('/documents/upload', {
|
171 |
-
method: 'POST',
|
172 |
-
body: formData
|
173 |
-
})
|
174 |
-
return await response.json()
|
175 |
-
}
|
176 |
-
|
177 |
-
export const startDocumentScan = async (): Promise<{ status: string }> => {
|
178 |
-
const response = await fetchWithAuth('/documents/scan', {
|
179 |
-
method: 'POST'
|
180 |
-
})
|
181 |
-
return await response.json()
|
182 |
}
|
183 |
|
184 |
export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
|
185 |
-
const response = await
|
186 |
-
|
187 |
-
headers: {
|
188 |
-
'Content-Type': 'application/json'
|
189 |
-
},
|
190 |
-
body: JSON.stringify(request)
|
191 |
-
})
|
192 |
-
return await response.json()
|
193 |
}
|
194 |
|
195 |
-
export const queryTextStream = async (
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
if (data.response) {
|
219 |
-
onChunk(data.response)
|
220 |
}
|
221 |
-
|
222 |
-
console.error('Error parsing stream chunk:', e)
|
223 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
}
|
225 |
}
|
|
|
|
|
|
|
|
|
226 |
}
|
227 |
}
|
228 |
|
229 |
-
// Text insertion API
|
230 |
export const insertText = async (
|
231 |
text: string,
|
232 |
description?: string
|
233 |
-
): Promise<{
|
234 |
-
|
235 |
-
|
236 |
-
document_count: number
|
237 |
-
}> => {
|
238 |
-
const response = await fetchWithAuth('/documents/text', {
|
239 |
-
method: 'POST',
|
240 |
-
headers: {
|
241 |
-
'Content-Type': 'application/json'
|
242 |
-
},
|
243 |
-
body: JSON.stringify({ text, description })
|
244 |
-
})
|
245 |
-
return await response.json()
|
246 |
}
|
247 |
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
): Promise<{
|
252 |
-
status: string
|
253 |
-
message: string
|
254 |
-
document_count: number
|
255 |
-
}> => {
|
256 |
const formData = new FormData()
|
257 |
-
|
258 |
-
formData.append('files', file)
|
259 |
-
})
|
260 |
|
261 |
-
const response = await
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
})
|
265 |
-
return
|
266 |
}
|
267 |
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
278 |
}
|
|
|
1 |
+
import axios, { AxiosError } from 'axios'
|
2 |
import { backendBaseUrl } from '@/lib/constants'
|
3 |
import { errorMessage } from '@/lib/utils'
|
4 |
import { useSettingsStore } from '@/stores/settings'
|
|
|
27 |
status: 'healthy'
|
28 |
working_directory: string
|
29 |
input_directory: string
|
|
|
|
|
30 |
configuration: {
|
31 |
llm_binding: string
|
32 |
llm_binding_host: string
|
|
|
50 |
progress: number
|
51 |
}
|
52 |
|
53 |
+
/**
|
54 |
+
* Specifies the retrieval mode:
|
55 |
+
* - "naive": Performs a basic search without advanced techniques.
|
56 |
+
* - "local": Focuses on context-dependent information.
|
57 |
+
* - "global": Utilizes global knowledge.
|
58 |
+
* - "hybrid": Combines local and global retrieval methods.
|
59 |
+
* - "mix": Integrates knowledge graph and vector retrieval.
|
60 |
+
*/
|
61 |
export type QueryMode = 'naive' | 'local' | 'global' | 'hybrid' | 'mix'
|
62 |
|
63 |
+
export type Message = {
|
64 |
+
role: 'user' | 'assistant' | 'system'
|
65 |
+
content: string
|
66 |
+
}
|
67 |
+
|
68 |
export type QueryRequest = {
|
69 |
query: string
|
70 |
+
/** Specifies the retrieval mode. */
|
71 |
mode: QueryMode
|
72 |
+
/** If True, only returns the retrieved context without generating a response. */
|
73 |
only_need_context?: boolean
|
74 |
+
/** If True, only returns the generated prompt without producing a response. */
|
75 |
+
only_need_prompt?: boolean
|
76 |
+
/** Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'. */
|
77 |
+
response_type?: string
|
78 |
+
/** If True, enables streaming output for real-time responses. */
|
79 |
+
stream?: boolean
|
80 |
+
/** Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode. */
|
81 |
+
top_k?: number
|
82 |
+
/** Maximum number of tokens allowed for each retrieved text chunk. */
|
83 |
+
max_token_for_text_unit?: number
|
84 |
+
/** Maximum number of tokens allocated for relationship descriptions in global retrieval. */
|
85 |
+
max_token_for_global_context?: number
|
86 |
+
/** Maximum number of tokens allocated for entity descriptions in local retrieval. */
|
87 |
+
max_token_for_local_context?: number
|
88 |
+
/** List of high-level keywords to prioritize in retrieval. */
|
89 |
+
hl_keywords?: string[]
|
90 |
+
/** List of low-level keywords to refine retrieval focus. */
|
91 |
+
ll_keywords?: string[]
|
92 |
+
/**
|
93 |
+
* Stores past conversation history to maintain context.
|
94 |
+
* Format: [{"role": "user/assistant", "content": "message"}].
|
95 |
+
*/
|
96 |
+
conversation_history?: Message[]
|
97 |
+
/** Number of complete conversation turns (user-assistant pairs) to consider in the response context. */
|
98 |
+
history_turns?: number
|
99 |
}
|
100 |
|
101 |
export type QueryResponse = {
|
102 |
response: string
|
103 |
}
|
104 |
|
105 |
+
export type DocActionResponse = {
|
106 |
+
status: 'success' | 'partial_success' | 'failure'
|
107 |
+
message: string
|
108 |
+
}
|
109 |
+
|
110 |
+
export type DocStatus = 'pending' | 'processing' | 'processed' | 'failed'
|
111 |
+
|
112 |
+
export type DocStatusResponse = {
|
113 |
+
id: string
|
114 |
+
content_summary: string
|
115 |
+
content_length: number
|
116 |
+
status: DocStatus
|
117 |
+
created_at: string
|
118 |
+
updated_at: string
|
119 |
+
chunks_count?: number
|
120 |
+
error?: string
|
121 |
+
metadata?: Record<string, any>
|
122 |
+
}
|
123 |
+
|
124 |
+
export type DocsStatusesResponse = {
|
125 |
+
statuses: Record<DocStatus, DocStatusResponse[]>
|
126 |
+
}
|
127 |
+
|
128 |
export const InvalidApiKeyError = 'Invalid API Key'
|
129 |
export const RequireApiKeError = 'API Key required'
|
130 |
|
131 |
+
// Axios instance
|
132 |
+
const axiosInstance = axios.create({
|
133 |
+
baseURL: backendBaseUrl,
|
134 |
+
headers: {
|
135 |
+
'Content-Type': 'application/json'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
}
|
137 |
+
})
|
|
|
138 |
|
139 |
+
// Interceptor:add api key
|
140 |
+
axiosInstance.interceptors.request.use((config) => {
|
141 |
const apiKey = useSettingsStore.getState().apiKey
|
142 |
+
if (apiKey) {
|
143 |
+
config.headers['X-API-Key'] = apiKey
|
|
|
144 |
}
|
145 |
+
return config
|
146 |
+
})
|
147 |
|
148 |
+
// Interceptor:hanle error
|
149 |
+
axiosInstance.interceptors.response.use(
|
150 |
+
(response) => response,
|
151 |
+
(error: AxiosError) => {
|
152 |
+
if (error.response) {
|
153 |
+
throw new Error(
|
154 |
+
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
|
155 |
+
error.response.data
|
156 |
+
)}\n${error.config?.url}`
|
157 |
+
)
|
158 |
+
}
|
159 |
+
throw error
|
160 |
}
|
161 |
+
)
|
|
|
|
|
162 |
|
163 |
// API methods
|
164 |
export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
|
165 |
+
const response = await axiosInstance.get(`/graphs?label=${label}`)
|
166 |
+
return response.data
|
167 |
}
|
168 |
|
169 |
export const getGraphLabels = async (): Promise<string[]> => {
|
170 |
+
const response = await axiosInstance.get('/graph/label/list')
|
171 |
+
return response.data
|
172 |
}
|
173 |
|
174 |
export const checkHealth = async (): Promise<
|
175 |
LightragStatus | { status: 'error'; message: string }
|
176 |
> => {
|
177 |
try {
|
178 |
+
const response = await axiosInstance.get('/health')
|
179 |
+
return response.data
|
180 |
} catch (e) {
|
181 |
return {
|
182 |
status: 'error',
|
|
|
185 |
}
|
186 |
}
|
187 |
|
188 |
+
export const getDocuments = async (): Promise<DocsStatusesResponse> => {
|
189 |
+
const response = await axiosInstance.get('/documents')
|
190 |
+
return response.data
|
191 |
}
|
192 |
|
193 |
+
export const scanNewDocuments = async (): Promise<{ status: string }> => {
|
194 |
+
const response = await axiosInstance.post('/documents/scan')
|
195 |
+
return response.data
|
196 |
}
|
197 |
|
198 |
+
export const getDocumentsScanProgress = async (): Promise<LightragDocumentsScanProgress> => {
|
199 |
+
const response = await axiosInstance.get('/documents/scan-progress')
|
200 |
+
return response.data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
}
|
202 |
|
203 |
export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
|
204 |
+
const response = await axiosInstance.post('/query', request)
|
205 |
+
return response.data
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
}
|
207 |
|
208 |
+
export const queryTextStream = async (
|
209 |
+
request: QueryRequest,
|
210 |
+
onChunk: (chunk: string) => void,
|
211 |
+
onError?: (error: string) => void
|
212 |
+
) => {
|
213 |
+
try {
|
214 |
+
let buffer = ''
|
215 |
+
await axiosInstance.post('/query/stream', request, {
|
216 |
+
responseType: 'text',
|
217 |
+
headers: {
|
218 |
+
Accept: 'application/x-ndjson'
|
219 |
+
},
|
220 |
+
transformResponse: [
|
221 |
+
(data: string) => {
|
222 |
+
// Accumulate the data and process complete lines
|
223 |
+
buffer += data
|
224 |
+
const lines = buffer.split('\n')
|
225 |
+
// Keep the last potentially incomplete line in the buffer
|
226 |
+
buffer = lines.pop() || ''
|
227 |
|
228 |
+
for (const line of lines) {
|
229 |
+
if (line.trim()) {
|
230 |
+
try {
|
231 |
+
const parsed = JSON.parse(line)
|
232 |
+
if (parsed.response) {
|
233 |
+
onChunk(parsed.response)
|
234 |
+
} else if (parsed.error && onError) {
|
235 |
+
onError(parsed.error)
|
236 |
+
}
|
237 |
+
} catch (e) {
|
238 |
+
console.error('Error parsing stream chunk:', e)
|
239 |
+
if (onError) onError('Error parsing server response')
|
240 |
+
}
|
241 |
+
}
|
|
|
|
|
242 |
}
|
243 |
+
return data
|
|
|
244 |
}
|
245 |
+
]
|
246 |
+
})
|
247 |
+
|
248 |
+
// Process any remaining data in the buffer
|
249 |
+
if (buffer.trim()) {
|
250 |
+
try {
|
251 |
+
const parsed = JSON.parse(buffer)
|
252 |
+
if (parsed.response) {
|
253 |
+
onChunk(parsed.response)
|
254 |
+
} else if (parsed.error && onError) {
|
255 |
+
onError(parsed.error)
|
256 |
+
}
|
257 |
+
} catch (e) {
|
258 |
+
console.error('Error parsing final chunk:', e)
|
259 |
+
if (onError) onError('Error parsing server response')
|
260 |
}
|
261 |
}
|
262 |
+
} catch (error) {
|
263 |
+
const message = errorMessage(error)
|
264 |
+
console.error('Stream request failed:', message)
|
265 |
+
if (onError) onError(message)
|
266 |
}
|
267 |
}
|
268 |
|
|
|
269 |
export const insertText = async (
|
270 |
text: string,
|
271 |
description?: string
|
272 |
+
): Promise<DocActionResponse> => {
|
273 |
+
const response = await axiosInstance.post('/documents/text', { text, description })
|
274 |
+
return response.data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
}
|
276 |
|
277 |
+
export const uploadDocument = async (
|
278 |
+
file: File,
|
279 |
+
onUploadProgress?: (percentCompleted: number) => void
|
280 |
+
): Promise<DocActionResponse> => {
|
|
|
|
|
|
|
|
|
281 |
const formData = new FormData()
|
282 |
+
formData.append('file', file)
|
|
|
|
|
283 |
|
284 |
+
const response = await axiosInstance.post('/documents/upload', formData, {
|
285 |
+
headers: {
|
286 |
+
'Content-Type': 'multipart/form-data'
|
287 |
+
},
|
288 |
+
// prettier-ignore
|
289 |
+
onUploadProgress:
|
290 |
+
onUploadProgress !== undefined
|
291 |
+
? (progressEvent) => {
|
292 |
+
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total!)
|
293 |
+
onUploadProgress(percentCompleted)
|
294 |
+
}
|
295 |
+
: undefined
|
296 |
})
|
297 |
+
return response.data
|
298 |
}
|
299 |
|
300 |
+
export const batchUploadDocuments = async (
|
301 |
+
files: File[],
|
302 |
+
onUploadProgress?: (fileName: string, percentCompleted: number) => void
|
303 |
+
): Promise<DocActionResponse[]> => {
|
304 |
+
return await Promise.all(
|
305 |
+
files.map(async (file) => {
|
306 |
+
return await uploadDocument(file, (percentCompleted) => {
|
307 |
+
onUploadProgress?.(file.name, percentCompleted)
|
308 |
+
})
|
309 |
+
})
|
310 |
+
)
|
311 |
+
}
|
312 |
+
|
313 |
+
export const clearDocuments = async (): Promise<DocActionResponse> => {
|
314 |
+
const response = await axiosInstance.delete('/documents')
|
315 |
+
return response.data
|
316 |
}
|
lightrag_webui/src/components/ApiKeyAlert.tsx
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useCallback, useEffect } from 'react'
|
2 |
+
import {
|
3 |
+
AlertDialog,
|
4 |
+
AlertDialogContent,
|
5 |
+
AlertDialogDescription,
|
6 |
+
AlertDialogHeader,
|
7 |
+
AlertDialogTitle
|
8 |
+
} from '@/components/ui/AlertDialog'
|
9 |
+
import Button from '@/components/ui/Button'
|
10 |
+
import Input from '@/components/ui/Input'
|
11 |
+
import { useSettingsStore } from '@/stores/settings'
|
12 |
+
import { useBackendState } from '@/stores/state'
|
13 |
+
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
14 |
+
|
15 |
+
import { toast } from 'sonner'
|
16 |
+
|
17 |
+
const ApiKeyAlert = () => {
|
18 |
+
const [opened, setOpened] = useState<boolean>(true)
|
19 |
+
const apiKey = useSettingsStore.use.apiKey()
|
20 |
+
const [tempApiKey, setTempApiKey] = useState<string>('')
|
21 |
+
const message = useBackendState.use.message()
|
22 |
+
|
23 |
+
useEffect(() => {
|
24 |
+
setTempApiKey(apiKey || '')
|
25 |
+
}, [apiKey, opened])
|
26 |
+
|
27 |
+
useEffect(() => {
|
28 |
+
if (message) {
|
29 |
+
if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
|
30 |
+
setOpened(true)
|
31 |
+
}
|
32 |
+
}
|
33 |
+
}, [message, setOpened])
|
34 |
+
|
35 |
+
const setApiKey = useCallback(async () => {
|
36 |
+
useSettingsStore.setState({ apiKey: tempApiKey || null })
|
37 |
+
if (await useBackendState.getState().check()) {
|
38 |
+
setOpened(false)
|
39 |
+
return
|
40 |
+
}
|
41 |
+
toast.error('API Key is invalid')
|
42 |
+
}, [tempApiKey])
|
43 |
+
|
44 |
+
const handleTempApiKeyChange = useCallback(
|
45 |
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
46 |
+
setTempApiKey(e.target.value)
|
47 |
+
},
|
48 |
+
[setTempApiKey]
|
49 |
+
)
|
50 |
+
|
51 |
+
return (
|
52 |
+
<AlertDialog open={opened} onOpenChange={setOpened}>
|
53 |
+
<AlertDialogContent>
|
54 |
+
<AlertDialogHeader>
|
55 |
+
<AlertDialogTitle>API Key is required</AlertDialogTitle>
|
56 |
+
<AlertDialogDescription>Please enter your API key</AlertDialogDescription>
|
57 |
+
</AlertDialogHeader>
|
58 |
+
<form className="flex gap-2" onSubmit={(e) => e.preventDefault()}>
|
59 |
+
<Input
|
60 |
+
type="password"
|
61 |
+
value={tempApiKey}
|
62 |
+
onChange={handleTempApiKeyChange}
|
63 |
+
placeholder="Enter your API key"
|
64 |
+
className="max-h-full w-full min-w-0"
|
65 |
+
autoComplete="off"
|
66 |
+
/>
|
67 |
+
|
68 |
+
<Button onClick={setApiKey} variant="outline" size="sm">
|
69 |
+
Save
|
70 |
+
</Button>
|
71 |
+
</form>
|
72 |
+
</AlertDialogContent>
|
73 |
+
</AlertDialog>
|
74 |
+
)
|
75 |
+
}
|
76 |
+
|
77 |
+
export default ApiKeyAlert
|
lightrag_webui/src/components/MessageAlert.tsx
CHANGED
@@ -22,10 +22,11 @@ const MessageAlert = () => {
|
|
22 |
|
23 |
return (
|
24 |
<Alert
|
25 |
-
variant={health ? 'default' : 'destructive'}
|
26 |
className={cn(
|
27 |
-
'bg-background/90 absolute top-
|
28 |
-
isMounted ? 'translate-y-0 opacity-100' : '-translate-y-20 opacity-0'
|
|
|
29 |
)}
|
30 |
>
|
31 |
{!health && (
|
@@ -42,7 +43,7 @@ const MessageAlert = () => {
|
|
42 |
<Button
|
43 |
size="sm"
|
44 |
variant={controlButtonVariant}
|
45 |
-
className="
|
46 |
onClick={() => useBackendState.getState().clear()}
|
47 |
>
|
48 |
Close
|
|
|
22 |
|
23 |
return (
|
24 |
<Alert
|
25 |
+
// variant={health ? 'default' : 'destructive'}
|
26 |
className={cn(
|
27 |
+
'bg-background/90 absolute top-12 left-1/2 flex w-auto max-w-lg -translate-x-1/2 transform items-center gap-4 shadow-md backdrop-blur-lg transition-all duration-500 ease-in-out',
|
28 |
+
isMounted ? 'translate-y-0 opacity-100' : '-translate-y-20 opacity-0',
|
29 |
+
!health && 'bg-red-700 text-white'
|
30 |
)}
|
31 |
>
|
32 |
{!health && (
|
|
|
43 |
<Button
|
44 |
size="sm"
|
45 |
variant={controlButtonVariant}
|
46 |
+
className="border-primary max-h-8 border !p-2 text-xs"
|
47 |
onClick={() => useBackendState.getState().clear()}
|
48 |
>
|
49 |
Close
|
lightrag_webui/src/components/documents/ClearDocumentsDialog.tsx
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useCallback } from 'react'
|
2 |
+
import Button from '@/components/ui/Button'
|
3 |
+
import {
|
4 |
+
Dialog,
|
5 |
+
DialogContent,
|
6 |
+
DialogDescription,
|
7 |
+
DialogHeader,
|
8 |
+
DialogTitle,
|
9 |
+
DialogTrigger
|
10 |
+
} from '@/components/ui/Dialog'
|
11 |
+
import { toast } from 'sonner'
|
12 |
+
import { errorMessage } from '@/lib/utils'
|
13 |
+
import { clearDocuments } from '@/api/lightrag'
|
14 |
+
|
15 |
+
import { EraserIcon } from 'lucide-react'
|
16 |
+
|
17 |
+
export default function ClearDocumentsDialog() {
|
18 |
+
const [open, setOpen] = useState(false)
|
19 |
+
|
20 |
+
const handleClear = useCallback(async () => {
|
21 |
+
try {
|
22 |
+
const result = await clearDocuments()
|
23 |
+
if (result.status === 'success') {
|
24 |
+
toast.success('Documents cleared successfully')
|
25 |
+
setOpen(false)
|
26 |
+
} else {
|
27 |
+
toast.error(`Clear Documents Failed:\n${result.message}`)
|
28 |
+
}
|
29 |
+
} catch (err) {
|
30 |
+
toast.error('Clear Documents Failed:\n' + errorMessage(err))
|
31 |
+
}
|
32 |
+
}, [setOpen])
|
33 |
+
|
34 |
+
return (
|
35 |
+
<Dialog open={open} onOpenChange={setOpen}>
|
36 |
+
<DialogTrigger asChild>
|
37 |
+
<Button variant="outline" side="bottom" tooltip='Clear documents' size="sm">
|
38 |
+
<EraserIcon/> Clear
|
39 |
+
</Button>
|
40 |
+
</DialogTrigger>
|
41 |
+
<DialogContent className="sm:max-w-xl" onCloseAutoFocus={(e) => e.preventDefault()}>
|
42 |
+
<DialogHeader>
|
43 |
+
<DialogTitle>Clear documents</DialogTitle>
|
44 |
+
<DialogDescription>Do you really want to clear all documents?</DialogDescription>
|
45 |
+
</DialogHeader>
|
46 |
+
<Button variant="destructive" onClick={handleClear}>
|
47 |
+
YES
|
48 |
+
</Button>
|
49 |
+
</DialogContent>
|
50 |
+
</Dialog>
|
51 |
+
)
|
52 |
+
}
|
lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState, useCallback } from 'react'
|
2 |
+
import Button from '@/components/ui/Button'
|
3 |
+
import {
|
4 |
+
Dialog,
|
5 |
+
DialogContent,
|
6 |
+
DialogDescription,
|
7 |
+
DialogHeader,
|
8 |
+
DialogTitle,
|
9 |
+
DialogTrigger
|
10 |
+
} from '@/components/ui/Dialog'
|
11 |
+
import FileUploader from '@/components/ui/FileUploader'
|
12 |
+
import { toast } from 'sonner'
|
13 |
+
import { errorMessage } from '@/lib/utils'
|
14 |
+
import { uploadDocument } from '@/api/lightrag'
|
15 |
+
|
16 |
+
import { UploadIcon } from 'lucide-react'
|
17 |
+
|
18 |
+
export default function UploadDocumentsDialog() {
|
19 |
+
const [open, setOpen] = useState(false)
|
20 |
+
const [isUploading, setIsUploading] = useState(false)
|
21 |
+
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
22 |
+
|
23 |
+
const handleDocumentsUpload = useCallback(
|
24 |
+
async (filesToUpload: File[]) => {
|
25 |
+
setIsUploading(true)
|
26 |
+
|
27 |
+
try {
|
28 |
+
await Promise.all(
|
29 |
+
filesToUpload.map(async (file) => {
|
30 |
+
try {
|
31 |
+
const result = await uploadDocument(file, (percentCompleted: number) => {
|
32 |
+
console.debug(`Uploading ${file.name}: ${percentCompleted}%`)
|
33 |
+
setProgresses((pre) => ({
|
34 |
+
...pre,
|
35 |
+
[file.name]: percentCompleted
|
36 |
+
}))
|
37 |
+
})
|
38 |
+
if (result.status === 'success') {
|
39 |
+
toast.success(`Upload Success:\n${file.name} uploaded successfully`)
|
40 |
+
} else {
|
41 |
+
toast.error(`Upload Failed:\n${file.name}\n${result.message}`)
|
42 |
+
}
|
43 |
+
} catch (err) {
|
44 |
+
toast.error(`Upload Failed:\n${file.name}\n${errorMessage(err)}`)
|
45 |
+
}
|
46 |
+
})
|
47 |
+
)
|
48 |
+
} catch (err) {
|
49 |
+
toast.error('Upload Failed\n' + errorMessage(err))
|
50 |
+
} finally {
|
51 |
+
setIsUploading(false)
|
52 |
+
// setOpen(false)
|
53 |
+
}
|
54 |
+
},
|
55 |
+
[setIsUploading, setProgresses]
|
56 |
+
)
|
57 |
+
|
58 |
+
return (
|
59 |
+
<Dialog
|
60 |
+
open={open}
|
61 |
+
onOpenChange={(open) => {
|
62 |
+
if (isUploading && !open) {
|
63 |
+
return
|
64 |
+
}
|
65 |
+
setOpen(open)
|
66 |
+
}}
|
67 |
+
>
|
68 |
+
<DialogTrigger asChild>
|
69 |
+
<Button variant="default" side="bottom" tooltip="Upload documents" size="sm">
|
70 |
+
<UploadIcon /> Upload
|
71 |
+
</Button>
|
72 |
+
</DialogTrigger>
|
73 |
+
<DialogContent className="sm:max-w-xl" onCloseAutoFocus={(e) => e.preventDefault()}>
|
74 |
+
<DialogHeader>
|
75 |
+
<DialogTitle>Upload documents</DialogTitle>
|
76 |
+
<DialogDescription>
|
77 |
+
Drag and drop your documents here or click to browse.
|
78 |
+
</DialogDescription>
|
79 |
+
</DialogHeader>
|
80 |
+
<FileUploader
|
81 |
+
maxFileCount={Infinity}
|
82 |
+
maxSize={200 * 1024 * 1024}
|
83 |
+
description="supported types: TXT, MD, DOC, PDF, PPTX"
|
84 |
+
onUpload={handleDocumentsUpload}
|
85 |
+
progresses={progresses}
|
86 |
+
disabled={isUploading}
|
87 |
+
/>
|
88 |
+
</DialogContent>
|
89 |
+
</Dialog>
|
90 |
+
)
|
91 |
+
}
|
lightrag_webui/src/components/{FocusOnNode.tsx → graph/FocusOnNode.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{FullScreenControl.tsx → graph/FullScreenControl.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{GraphControl.tsx → graph/GraphControl.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{GraphLabels.tsx → graph/GraphLabels.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{GraphSearch.tsx → graph/GraphSearch.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{LayoutsControl.tsx → graph/LayoutsControl.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{PropertiesView.tsx → graph/PropertiesView.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{Settings.tsx → graph/Settings.tsx}
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
2 |
-
import
|
3 |
import Button from '@/components/ui/Button'
|
4 |
import Separator from '@/components/ui/Separator'
|
5 |
import Input from '@/components/ui/Input'
|
@@ -40,7 +40,7 @@ const LabeledCheckBox = ({
|
|
40 |
*/
|
41 |
export default function Settings() {
|
42 |
const [opened, setOpened] = useState<boolean>(false)
|
43 |
-
const [tempApiKey, setTempApiKey] = useState<string>('')
|
44 |
|
45 |
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
|
46 |
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
|
|
|
1 |
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
2 |
+
import Checkbox from '@/components/ui/Checkbox'
|
3 |
import Button from '@/components/ui/Button'
|
4 |
import Separator from '@/components/ui/Separator'
|
5 |
import Input from '@/components/ui/Input'
|
|
|
40 |
*/
|
41 |
export default function Settings() {
|
42 |
const [opened, setOpened] = useState<boolean>(false)
|
43 |
+
const [tempApiKey, setTempApiKey] = useState<string>('')
|
44 |
|
45 |
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
|
46 |
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
|
lightrag_webui/src/components/{StatusCard.tsx → graph/StatusCard.tsx}
RENAMED
@@ -14,8 +14,6 @@ const StatusCard = ({ status }: { status: LightragStatus | null }) => {
|
|
14 |
<span className="truncate">{status.working_directory}</span>
|
15 |
<span>Input Directory:</span>
|
16 |
<span className="truncate">{status.input_directory}</span>
|
17 |
-
<span>Indexed Files:</span>
|
18 |
-
<span>{status.indexed_files_count}</span>
|
19 |
</div>
|
20 |
</div>
|
21 |
|
|
|
14 |
<span className="truncate">{status.working_directory}</span>
|
15 |
<span>Input Directory:</span>
|
16 |
<span className="truncate">{status.input_directory}</span>
|
|
|
|
|
17 |
</div>
|
18 |
</div>
|
19 |
|
lightrag_webui/src/components/{StatusIndicator.tsx → graph/StatusIndicator.tsx}
RENAMED
@@ -2,7 +2,7 @@ import { cn } from '@/lib/utils'
|
|
2 |
import { useBackendState } from '@/stores/state'
|
3 |
import { useEffect, useState } from 'react'
|
4 |
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
5 |
-
import StatusCard from '@/components/StatusCard'
|
6 |
|
7 |
const StatusIndicator = () => {
|
8 |
const health = useBackendState.use.health()
|
|
|
2 |
import { useBackendState } from '@/stores/state'
|
3 |
import { useEffect, useState } from 'react'
|
4 |
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
5 |
+
import StatusCard from '@/components/graph/StatusCard'
|
6 |
|
7 |
const StatusIndicator = () => {
|
8 |
const health = useBackendState.use.health()
|
lightrag_webui/src/components/{ThemeProvider.tsx → graph/ThemeProvider.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/{ThemeToggle.tsx → graph/ThemeToggle.tsx}
RENAMED
@@ -19,6 +19,7 @@ export default function ThemeToggle() {
|
|
19 |
variant={controlButtonVariant}
|
20 |
tooltip="Switch to light theme"
|
21 |
size="icon"
|
|
|
22 |
>
|
23 |
<MoonIcon />
|
24 |
</Button>
|
@@ -30,6 +31,7 @@ export default function ThemeToggle() {
|
|
30 |
variant={controlButtonVariant}
|
31 |
tooltip="Switch to dark theme"
|
32 |
size="icon"
|
|
|
33 |
>
|
34 |
<SunIcon />
|
35 |
</Button>
|
|
|
19 |
variant={controlButtonVariant}
|
20 |
tooltip="Switch to light theme"
|
21 |
size="icon"
|
22 |
+
side="bottom"
|
23 |
>
|
24 |
<MoonIcon />
|
25 |
</Button>
|
|
|
31 |
variant={controlButtonVariant}
|
32 |
tooltip="Switch to dark theme"
|
33 |
size="icon"
|
34 |
+
side="bottom"
|
35 |
>
|
36 |
<SunIcon />
|
37 |
</Button>
|
lightrag_webui/src/components/{ZoomControl.tsx → graph/ZoomControl.tsx}
RENAMED
File without changes
|
lightrag_webui/src/components/retrieval/QuerySettings.tsx
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useCallback } from 'react'
|
2 |
+
import { QueryMode, QueryRequest } from '@/api/lightrag'
|
3 |
+
import Text from '@/components/ui/Text'
|
4 |
+
import Input from '@/components/ui/Input'
|
5 |
+
import Checkbox from '@/components/ui/Checkbox'
|
6 |
+
import NumberInput from '@/components/ui/NumberInput'
|
7 |
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'
|
8 |
+
import {
|
9 |
+
Select,
|
10 |
+
SelectContent,
|
11 |
+
SelectGroup,
|
12 |
+
SelectItem,
|
13 |
+
SelectTrigger,
|
14 |
+
SelectValue
|
15 |
+
} from '@/components/ui/Select'
|
16 |
+
import { useSettingsStore } from '@/stores/settings'
|
17 |
+
|
18 |
+
export default function QuerySettings() {
|
19 |
+
const querySettings = useSettingsStore((state) => state.querySettings)
|
20 |
+
|
21 |
+
const handleChange = useCallback((key: keyof QueryRequest, value: any) => {
|
22 |
+
useSettingsStore.getState().updateQuerySettings({ [key]: value })
|
23 |
+
}, [])
|
24 |
+
|
25 |
+
return (
|
26 |
+
<Card className="flex shrink-0 flex-col">
|
27 |
+
<CardHeader className="px-4 pt-4 pb-2">
|
28 |
+
<CardTitle>Parameters</CardTitle>
|
29 |
+
<CardDescription>Configure your query parameters</CardDescription>
|
30 |
+
</CardHeader>
|
31 |
+
<CardContent className="m-0 flex grow flex-col p-0 text-xs">
|
32 |
+
<div className="relative size-full">
|
33 |
+
<div className="absolute inset-0 flex flex-col gap-2 overflow-auto px-2">
|
34 |
+
{/* Query Mode */}
|
35 |
+
<>
|
36 |
+
<Text
|
37 |
+
className="ml-1"
|
38 |
+
text="Query Mode"
|
39 |
+
tooltip="Select the retrieval strategy:\n• Naive: Basic search without advanced techniques\n• Local: Context-dependent information retrieval\n• Global: Utilizes global knowledge base\n• Hybrid: Combines local and global retrieval\n• Mix: Integrates knowledge graph with vector retrieval"
|
40 |
+
side="left"
|
41 |
+
/>
|
42 |
+
<Select
|
43 |
+
value={querySettings.mode}
|
44 |
+
onValueChange={(v) => handleChange('mode', v as QueryMode)}
|
45 |
+
>
|
46 |
+
<SelectTrigger className="hover:bg-primary/5 h-9 cursor-pointer focus:ring-0 focus:ring-offset-0 focus:outline-0 active:right-0">
|
47 |
+
<SelectValue />
|
48 |
+
</SelectTrigger>
|
49 |
+
<SelectContent>
|
50 |
+
<SelectGroup>
|
51 |
+
<SelectItem value="naive">Naive</SelectItem>
|
52 |
+
<SelectItem value="local">Local</SelectItem>
|
53 |
+
<SelectItem value="global">Global</SelectItem>
|
54 |
+
<SelectItem value="hybrid">Hybrid</SelectItem>
|
55 |
+
<SelectItem value="mix">Mix</SelectItem>
|
56 |
+
</SelectGroup>
|
57 |
+
</SelectContent>
|
58 |
+
</Select>
|
59 |
+
</>
|
60 |
+
|
61 |
+
{/* Response Format */}
|
62 |
+
<>
|
63 |
+
<Text
|
64 |
+
className="ml-1"
|
65 |
+
text="Response Format"
|
66 |
+
tooltip="Defines the response format. Examples:\n• Multiple Paragraphs\n• Single Paragraph\n• Bullet Points"
|
67 |
+
side="left"
|
68 |
+
/>
|
69 |
+
<Select
|
70 |
+
value={querySettings.response_type}
|
71 |
+
onValueChange={(v) => handleChange('response_type', v)}
|
72 |
+
>
|
73 |
+
<SelectTrigger className="hover:bg-primary/5 h-9 cursor-pointer focus:ring-0 focus:ring-offset-0 focus:outline-0 active:right-0">
|
74 |
+
<SelectValue />
|
75 |
+
</SelectTrigger>
|
76 |
+
<SelectContent>
|
77 |
+
<SelectGroup>
|
78 |
+
<SelectItem value="Multiple Paragraphs">Multiple Paragraphs</SelectItem>
|
79 |
+
<SelectItem value="Single Paragraph">Single Paragraph</SelectItem>
|
80 |
+
<SelectItem value="Bullet Points">Bullet Points</SelectItem>
|
81 |
+
</SelectGroup>
|
82 |
+
</SelectContent>
|
83 |
+
</Select>
|
84 |
+
</>
|
85 |
+
|
86 |
+
{/* Top K */}
|
87 |
+
<>
|
88 |
+
<Text
|
89 |
+
className="ml-1"
|
90 |
+
text="Top K Results"
|
91 |
+
tooltip="Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode"
|
92 |
+
side="left"
|
93 |
+
/>
|
94 |
+
<NumberInput
|
95 |
+
id="top_k"
|
96 |
+
stepper={1}
|
97 |
+
value={querySettings.top_k}
|
98 |
+
onValueChange={(v) => handleChange('top_k', v)}
|
99 |
+
min={1}
|
100 |
+
placeholder="Number of results"
|
101 |
+
/>
|
102 |
+
</>
|
103 |
+
|
104 |
+
{/* Max Tokens */}
|
105 |
+
<>
|
106 |
+
<>
|
107 |
+
<Text
|
108 |
+
className="ml-1"
|
109 |
+
text="Max Tokens for Text Unit"
|
110 |
+
tooltip="Maximum number of tokens allowed for each retrieved text chunk"
|
111 |
+
side="left"
|
112 |
+
/>
|
113 |
+
<NumberInput
|
114 |
+
id="max_token_for_text_unit"
|
115 |
+
stepper={500}
|
116 |
+
value={querySettings.max_token_for_text_unit}
|
117 |
+
onValueChange={(v) => handleChange('max_token_for_text_unit', v)}
|
118 |
+
min={1}
|
119 |
+
placeholder="Max tokens for text unit"
|
120 |
+
/>
|
121 |
+
</>
|
122 |
+
|
123 |
+
<>
|
124 |
+
<Text
|
125 |
+
text="Max Tokens for Global Context"
|
126 |
+
tooltip="Maximum number of tokens allocated for relationship descriptions in global retrieval"
|
127 |
+
side="left"
|
128 |
+
/>
|
129 |
+
<NumberInput
|
130 |
+
id="max_token_for_global_context"
|
131 |
+
stepper={500}
|
132 |
+
value={querySettings.max_token_for_global_context}
|
133 |
+
onValueChange={(v) => handleChange('max_token_for_global_context', v)}
|
134 |
+
min={1}
|
135 |
+
placeholder="Max tokens for global context"
|
136 |
+
/>
|
137 |
+
</>
|
138 |
+
|
139 |
+
<>
|
140 |
+
<Text
|
141 |
+
className="ml-1"
|
142 |
+
text="Max Tokens for Local Context"
|
143 |
+
tooltip="Maximum number of tokens allocated for entity descriptions in local retrieval"
|
144 |
+
side="left"
|
145 |
+
/>
|
146 |
+
<NumberInput
|
147 |
+
id="max_token_for_local_context"
|
148 |
+
stepper={500}
|
149 |
+
value={querySettings.max_token_for_local_context}
|
150 |
+
onValueChange={(v) => handleChange('max_token_for_local_context', v)}
|
151 |
+
min={1}
|
152 |
+
placeholder="Max tokens for local context"
|
153 |
+
/>
|
154 |
+
</>
|
155 |
+
</>
|
156 |
+
|
157 |
+
{/* History Turns */}
|
158 |
+
<>
|
159 |
+
<Text
|
160 |
+
className="ml-1"
|
161 |
+
text="History Turns"
|
162 |
+
tooltip="Number of complete conversation turns (user-assistant pairs) to consider in the response context"
|
163 |
+
side="left"
|
164 |
+
/>
|
165 |
+
<NumberInput
|
166 |
+
className="!border-input"
|
167 |
+
id="history_turns"
|
168 |
+
stepper={1}
|
169 |
+
type="text"
|
170 |
+
value={querySettings.history_turns}
|
171 |
+
onValueChange={(v) => handleChange('history_turns', v)}
|
172 |
+
min={0}
|
173 |
+
placeholder="Number of history turns"
|
174 |
+
/>
|
175 |
+
</>
|
176 |
+
|
177 |
+
{/* Keywords */}
|
178 |
+
<>
|
179 |
+
<>
|
180 |
+
<Text
|
181 |
+
className="ml-1"
|
182 |
+
text="High-Level Keywords"
|
183 |
+
tooltip="List of high-level keywords to prioritize in retrieval. Separate with commas"
|
184 |
+
side="left"
|
185 |
+
/>
|
186 |
+
<Input
|
187 |
+
id="hl_keywords"
|
188 |
+
type="text"
|
189 |
+
value={querySettings.hl_keywords?.join(', ')}
|
190 |
+
onChange={(e) => {
|
191 |
+
const keywords = e.target.value
|
192 |
+
.split(',')
|
193 |
+
.map((k) => k.trim())
|
194 |
+
.filter((k) => k !== '')
|
195 |
+
handleChange('hl_keywords', keywords)
|
196 |
+
}}
|
197 |
+
placeholder="Enter keywords"
|
198 |
+
/>
|
199 |
+
</>
|
200 |
+
|
201 |
+
<>
|
202 |
+
<Text
|
203 |
+
className="ml-1"
|
204 |
+
text="Low-Level Keywords"
|
205 |
+
tooltip="List of low-level keywords to refine retrieval focus. Separate with commas"
|
206 |
+
side="left"
|
207 |
+
/>
|
208 |
+
<Input
|
209 |
+
id="ll_keywords"
|
210 |
+
type="text"
|
211 |
+
value={querySettings.ll_keywords?.join(', ')}
|
212 |
+
onChange={(e) => {
|
213 |
+
const keywords = e.target.value
|
214 |
+
.split(',')
|
215 |
+
.map((k) => k.trim())
|
216 |
+
.filter((k) => k !== '')
|
217 |
+
handleChange('ll_keywords', keywords)
|
218 |
+
}}
|
219 |
+
placeholder="Enter keywords"
|
220 |
+
/>
|
221 |
+
</>
|
222 |
+
</>
|
223 |
+
|
224 |
+
{/* Toggle Options */}
|
225 |
+
<>
|
226 |
+
<div className="flex items-center gap-2">
|
227 |
+
<Text
|
228 |
+
className="ml-1"
|
229 |
+
text="Only Need Context"
|
230 |
+
tooltip="If True, only returns the retrieved context without generating a response"
|
231 |
+
side="left"
|
232 |
+
/>
|
233 |
+
<div className="grow" />
|
234 |
+
<Checkbox
|
235 |
+
className="mr-1 cursor-pointer"
|
236 |
+
id="only_need_context"
|
237 |
+
checked={querySettings.only_need_context}
|
238 |
+
onCheckedChange={(checked) => handleChange('only_need_context', checked)}
|
239 |
+
/>
|
240 |
+
</div>
|
241 |
+
|
242 |
+
<div className="flex items-center gap-2">
|
243 |
+
<Text
|
244 |
+
className="ml-1"
|
245 |
+
text="Only Need Prompt"
|
246 |
+
tooltip="If True, only returns the generated prompt without producing a response"
|
247 |
+
side="left"
|
248 |
+
/>
|
249 |
+
<div className="grow" />
|
250 |
+
<Checkbox
|
251 |
+
className="mr-1 cursor-pointer"
|
252 |
+
id="only_need_prompt"
|
253 |
+
checked={querySettings.only_need_prompt}
|
254 |
+
onCheckedChange={(checked) => handleChange('only_need_prompt', checked)}
|
255 |
+
/>
|
256 |
+
</div>
|
257 |
+
|
258 |
+
<div className="flex items-center gap-2">
|
259 |
+
<Text
|
260 |
+
className="ml-1"
|
261 |
+
text="Stream Response"
|
262 |
+
tooltip="If True, enables streaming output for real-time responses"
|
263 |
+
side="left"
|
264 |
+
/>
|
265 |
+
<div className="grow" />
|
266 |
+
<Checkbox
|
267 |
+
className="mr-1 cursor-pointer"
|
268 |
+
id="stream"
|
269 |
+
checked={querySettings.stream}
|
270 |
+
onCheckedChange={(checked) => handleChange('stream', checked)}
|
271 |
+
/>
|
272 |
+
</div>
|
273 |
+
</>
|
274 |
+
</div>
|
275 |
+
</div>
|
276 |
+
</CardContent>
|
277 |
+
</Card>
|
278 |
+
)
|
279 |
+
}
|
lightrag_webui/src/components/ui/AlertDialog.tsx
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
|
3 |
+
|
4 |
+
import { cn } from '@/lib/utils'
|
5 |
+
import { buttonVariants } from '@/components/ui/Button'
|
6 |
+
|
7 |
+
const AlertDialog = AlertDialogPrimitive.Root
|
8 |
+
|
9 |
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
10 |
+
|
11 |
+
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
12 |
+
|
13 |
+
const AlertDialogOverlay = React.forwardRef<
|
14 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Overlay>,
|
15 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
16 |
+
>(({ className, ...props }, ref) => (
|
17 |
+
<AlertDialogPrimitive.Overlay
|
18 |
+
className={cn(
|
19 |
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
20 |
+
className
|
21 |
+
)}
|
22 |
+
{...props}
|
23 |
+
ref={ref}
|
24 |
+
/>
|
25 |
+
))
|
26 |
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
27 |
+
|
28 |
+
const AlertDialogContent = React.forwardRef<
|
29 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Content>,
|
30 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
31 |
+
>(({ className, ...props }, ref) => (
|
32 |
+
<AlertDialogPortal>
|
33 |
+
<AlertDialogOverlay />
|
34 |
+
<AlertDialogPrimitive.Content
|
35 |
+
ref={ref}
|
36 |
+
className={cn(
|
37 |
+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
38 |
+
className
|
39 |
+
)}
|
40 |
+
{...props}
|
41 |
+
/>
|
42 |
+
</AlertDialogPortal>
|
43 |
+
))
|
44 |
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
45 |
+
|
46 |
+
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
47 |
+
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
48 |
+
)
|
49 |
+
AlertDialogHeader.displayName = 'AlertDialogHeader'
|
50 |
+
|
51 |
+
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
52 |
+
<div
|
53 |
+
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
)
|
57 |
+
AlertDialogFooter.displayName = 'AlertDialogFooter'
|
58 |
+
|
59 |
+
const AlertDialogTitle = React.forwardRef<
|
60 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Title>,
|
61 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<AlertDialogPrimitive.Title
|
64 |
+
ref={ref}
|
65 |
+
className={cn('text-lg font-semibold', className)}
|
66 |
+
{...props}
|
67 |
+
/>
|
68 |
+
))
|
69 |
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
70 |
+
|
71 |
+
const AlertDialogDescription = React.forwardRef<
|
72 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Description>,
|
73 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
74 |
+
>(({ className, ...props }, ref) => (
|
75 |
+
<AlertDialogPrimitive.Description
|
76 |
+
ref={ref}
|
77 |
+
className={cn('text-muted-foreground text-sm', className)}
|
78 |
+
{...props}
|
79 |
+
/>
|
80 |
+
))
|
81 |
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
82 |
+
|
83 |
+
const AlertDialogAction = React.forwardRef<
|
84 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Action>,
|
85 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
86 |
+
>(({ className, ...props }, ref) => (
|
87 |
+
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
88 |
+
))
|
89 |
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
90 |
+
|
91 |
+
const AlertDialogCancel = React.forwardRef<
|
92 |
+
React.ComponentRef<typeof AlertDialogPrimitive.Cancel>,
|
93 |
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
94 |
+
>(({ className, ...props }, ref) => (
|
95 |
+
<AlertDialogPrimitive.Cancel
|
96 |
+
ref={ref}
|
97 |
+
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
|
98 |
+
{...props}
|
99 |
+
/>
|
100 |
+
))
|
101 |
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
102 |
+
|
103 |
+
export {
|
104 |
+
AlertDialog,
|
105 |
+
AlertDialogPortal,
|
106 |
+
AlertDialogOverlay,
|
107 |
+
AlertDialogTrigger,
|
108 |
+
AlertDialogContent,
|
109 |
+
AlertDialogHeader,
|
110 |
+
AlertDialogFooter,
|
111 |
+
AlertDialogTitle,
|
112 |
+
AlertDialogDescription,
|
113 |
+
AlertDialogAction,
|
114 |
+
AlertDialogCancel
|
115 |
+
}
|
lightrag_webui/src/components/ui/AsyncSearch.tsx
CHANGED
@@ -193,7 +193,7 @@ export function AsyncSearch<T>({
|
|
193 |
</div>
|
194 |
)}
|
195 |
</div>
|
196 |
-
<CommandList
|
197 |
{error && <div className="text-destructive p-4 text-center">{error}</div>}
|
198 |
{loading && options.length === 0 && (loadingSkeleton || <DefaultLoadingSkeleton />)}
|
199 |
{!loading &&
|
|
|
193 |
</div>
|
194 |
)}
|
195 |
</div>
|
196 |
+
<CommandList hidden={!open || debouncedSearchTerm.length === 0}>
|
197 |
{error && <div className="text-destructive p-4 text-center">{error}</div>}
|
198 |
{loading && options.length === 0 && (loadingSkeleton || <DefaultLoadingSkeleton />)}
|
199 |
{!loading &&
|
lightrag_webui/src/components/ui/Badge.tsx
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 badgeVariants = cva(
|
7 |
+
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
8 |
+
{
|
9 |
+
variants: {
|
10 |
+
variant: {
|
11 |
+
default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
|
12 |
+
secondary:
|
13 |
+
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
14 |
+
destructive:
|
15 |
+
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
16 |
+
outline: 'text-foreground'
|
17 |
+
}
|
18 |
+
},
|
19 |
+
defaultVariants: {
|
20 |
+
variant: 'default'
|
21 |
+
}
|
22 |
+
}
|
23 |
+
)
|
24 |
+
|
25 |
+
export interface BadgeProps
|
26 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
27 |
+
VariantProps<typeof badgeVariants> {}
|
28 |
+
|
29 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
30 |
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
|
31 |
+
}
|
32 |
+
|
33 |
+
export default Badge
|
lightrag_webui/src/components/ui/Button.tsx
CHANGED
@@ -4,7 +4,8 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|
4 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
|
5 |
import { cn } from '@/lib/utils'
|
6 |
|
7 |
-
|
|
|
8 |
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
9 |
{
|
10 |
variants: {
|
|
|
4 |
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
|
5 |
import { cn } from '@/lib/utils'
|
6 |
|
7 |
+
// eslint-disable-next-line react-refresh/only-export-components
|
8 |
+
export const buttonVariants = cva(
|
9 |
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
10 |
{
|
11 |
variants: {
|
lightrag_webui/src/components/ui/Card.tsx
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
|
3 |
+
import { cn } from '@/lib/utils'
|
4 |
+
|
5 |
+
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
6 |
+
({ className, ...props }, ref) => (
|
7 |
+
<div
|
8 |
+
ref={ref}
|
9 |
+
className={cn('bg-card text-card-foreground rounded-xl border shadow', className)}
|
10 |
+
{...props}
|
11 |
+
/>
|
12 |
+
)
|
13 |
+
)
|
14 |
+
Card.displayName = 'Card'
|
15 |
+
|
16 |
+
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
17 |
+
({ className, ...props }, ref) => (
|
18 |
+
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
19 |
+
)
|
20 |
+
)
|
21 |
+
CardHeader.displayName = 'CardHeader'
|
22 |
+
|
23 |
+
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
24 |
+
({ className, ...props }, ref) => (
|
25 |
+
<div
|
26 |
+
ref={ref}
|
27 |
+
className={cn('leading-none font-semibold tracking-tight', className)}
|
28 |
+
{...props}
|
29 |
+
/>
|
30 |
+
)
|
31 |
+
)
|
32 |
+
CardTitle.displayName = 'CardTitle'
|
33 |
+
|
34 |
+
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
35 |
+
({ className, ...props }, ref) => (
|
36 |
+
<div ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
|
37 |
+
)
|
38 |
+
)
|
39 |
+
CardDescription.displayName = 'CardDescription'
|
40 |
+
|
41 |
+
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
42 |
+
({ className, ...props }, ref) => (
|
43 |
+
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
44 |
+
)
|
45 |
+
)
|
46 |
+
CardContent.displayName = 'CardContent'
|
47 |
+
|
48 |
+
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
49 |
+
({ className, ...props }, ref) => (
|
50 |
+
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
51 |
+
)
|
52 |
+
)
|
53 |
+
CardFooter.displayName = 'CardFooter'
|
54 |
+
|
55 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
lightrag_webui/src/components/ui/Checkbox.tsx
CHANGED
@@ -23,4 +23,4 @@ const Checkbox = React.forwardRef<
|
|
23 |
))
|
24 |
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
25 |
|
26 |
-
export
|
|
|
23 |
))
|
24 |
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
25 |
|
26 |
+
export default Checkbox
|
lightrag_webui/src/components/ui/DataTable.tsx
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
2 |
+
|
3 |
+
import {
|
4 |
+
Table,
|
5 |
+
TableBody,
|
6 |
+
TableCell,
|
7 |
+
TableHead,
|
8 |
+
TableHeader,
|
9 |
+
TableRow
|
10 |
+
} from '@/components/ui/Table'
|
11 |
+
|
12 |
+
interface DataTableProps<TData, TValue> {
|
13 |
+
columns: ColumnDef<TData, TValue>[]
|
14 |
+
data: TData[]
|
15 |
+
}
|
16 |
+
|
17 |
+
export default function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
18 |
+
const table = useReactTable({
|
19 |
+
data,
|
20 |
+
columns,
|
21 |
+
getCoreRowModel: getCoreRowModel()
|
22 |
+
})
|
23 |
+
|
24 |
+
return (
|
25 |
+
<div className="rounded-md border">
|
26 |
+
<Table>
|
27 |
+
<TableHeader>
|
28 |
+
{table.getHeaderGroups().map((headerGroup) => (
|
29 |
+
<TableRow key={headerGroup.id}>
|
30 |
+
{headerGroup.headers.map((header) => {
|
31 |
+
return (
|
32 |
+
<TableHead key={header.id}>
|
33 |
+
{header.isPlaceholder
|
34 |
+
? null
|
35 |
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
36 |
+
</TableHead>
|
37 |
+
)
|
38 |
+
})}
|
39 |
+
</TableRow>
|
40 |
+
))}
|
41 |
+
</TableHeader>
|
42 |
+
<TableBody>
|
43 |
+
{table.getRowModel().rows?.length ? (
|
44 |
+
table.getRowModel().rows.map((row) => (
|
45 |
+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
46 |
+
{row.getVisibleCells().map((cell) => (
|
47 |
+
<TableCell key={cell.id}>
|
48 |
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
49 |
+
</TableCell>
|
50 |
+
))}
|
51 |
+
</TableRow>
|
52 |
+
))
|
53 |
+
) : (
|
54 |
+
<TableRow>
|
55 |
+
<TableCell colSpan={columns.length} className="h-24 text-center">
|
56 |
+
No results.
|
57 |
+
</TableCell>
|
58 |
+
</TableRow>
|
59 |
+
)}
|
60 |
+
</TableBody>
|
61 |
+
</Table>
|
62 |
+
</div>
|
63 |
+
)
|
64 |
+
}
|
lightrag_webui/src/components/ui/Dialog.tsx
CHANGED
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
|
|
36 |
<DialogPrimitive.Content
|
37 |
ref={ref}
|
38 |
className={cn(
|
39 |
-
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-
|
40 |
className
|
41 |
)}
|
42 |
{...props}
|
@@ -65,7 +65,7 @@ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivEleme
|
|
65 |
DialogFooter.displayName = 'DialogFooter'
|
66 |
|
67 |
const DialogTitle = React.forwardRef<
|
68 |
-
React.
|
69 |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
70 |
>(({ className, ...props }, ref) => (
|
71 |
<DialogPrimitive.Title
|
@@ -77,7 +77,7 @@ const DialogTitle = React.forwardRef<
|
|
77 |
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
78 |
|
79 |
const DialogDescription = React.forwardRef<
|
80 |
-
React.
|
81 |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
82 |
>(({ className, ...props }, ref) => (
|
83 |
<DialogPrimitive.Description
|
|
|
36 |
<DialogPrimitive.Content
|
37 |
ref={ref}
|
38 |
className={cn(
|
39 |
+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
40 |
className
|
41 |
)}
|
42 |
{...props}
|
|
|
65 |
DialogFooter.displayName = 'DialogFooter'
|
66 |
|
67 |
const DialogTitle = React.forwardRef<
|
68 |
+
React.ComponentRef<typeof DialogPrimitive.Title>,
|
69 |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
70 |
>(({ className, ...props }, ref) => (
|
71 |
<DialogPrimitive.Title
|
|
|
77 |
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
78 |
|
79 |
const DialogDescription = React.forwardRef<
|
80 |
+
React.ComponentRef<typeof DialogPrimitive.Description>,
|
81 |
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
82 |
>(({ className, ...props }, ref) => (
|
83 |
<DialogPrimitive.Description
|
lightrag_webui/src/components/ui/EmptyCard.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { cn } from '@/lib/utils'
|
2 |
+
import { Card, CardDescription, CardTitle } from '@/components/ui/Card'
|
3 |
+
import { FilesIcon } from 'lucide-react'
|
4 |
+
|
5 |
+
interface EmptyCardProps extends React.ComponentPropsWithoutRef<typeof Card> {
|
6 |
+
title: string
|
7 |
+
description?: string
|
8 |
+
action?: React.ReactNode
|
9 |
+
icon?: React.ComponentType<{ className?: string }>
|
10 |
+
}
|
11 |
+
|
12 |
+
export default function EmptyCard({
|
13 |
+
title,
|
14 |
+
description,
|
15 |
+
icon: Icon = FilesIcon,
|
16 |
+
action,
|
17 |
+
className,
|
18 |
+
...props
|
19 |
+
}: EmptyCardProps) {
|
20 |
+
return (
|
21 |
+
<Card
|
22 |
+
className={cn(
|
23 |
+
'flex w-full flex-col items-center justify-center space-y-6 bg-transparent p-16',
|
24 |
+
className
|
25 |
+
)}
|
26 |
+
{...props}
|
27 |
+
>
|
28 |
+
<div className="mr-4 shrink-0 rounded-full border border-dashed p-4">
|
29 |
+
<Icon className="text-muted-foreground size-8" aria-hidden="true" />
|
30 |
+
</div>
|
31 |
+
<div className="flex flex-col items-center gap-1.5 text-center">
|
32 |
+
<CardTitle>{title}</CardTitle>
|
33 |
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
34 |
+
</div>
|
35 |
+
{action ? action : null}
|
36 |
+
</Card>
|
37 |
+
)
|
38 |
+
}
|
lightrag_webui/src/components/ui/FileUploader.tsx
ADDED
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* @see https://github.com/sadmann7/file-uploader
|
3 |
+
*/
|
4 |
+
|
5 |
+
import * as React from 'react'
|
6 |
+
import { FileText, Upload, X } from 'lucide-react'
|
7 |
+
import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'
|
8 |
+
import { toast } from 'sonner'
|
9 |
+
|
10 |
+
import { cn } from '@/lib/utils'
|
11 |
+
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
12 |
+
import Button from '@/components/ui/Button'
|
13 |
+
import Progress from '@/components/ui/Progress'
|
14 |
+
import { ScrollArea } from '@/components/ui/ScrollArea'
|
15 |
+
import { supportedFileTypes } from '@/lib/constants'
|
16 |
+
|
17 |
+
interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
18 |
+
/**
|
19 |
+
* Value of the uploader.
|
20 |
+
* @type File[]
|
21 |
+
* @default undefined
|
22 |
+
* @example value={files}
|
23 |
+
*/
|
24 |
+
value?: File[]
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Function to be called when the value changes.
|
28 |
+
* @type (files: File[]) => void
|
29 |
+
* @default undefined
|
30 |
+
* @example onValueChange={(files) => setFiles(files)}
|
31 |
+
*/
|
32 |
+
onValueChange?: (files: File[]) => void
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Function to be called when files are uploaded.
|
36 |
+
* @type (files: File[]) => Promise<void>
|
37 |
+
* @default undefined
|
38 |
+
* @example onUpload={(files) => uploadFiles(files)}
|
39 |
+
*/
|
40 |
+
onUpload?: (files: File[]) => Promise<void>
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Progress of the uploaded files.
|
44 |
+
* @type Record<string, number> | undefined
|
45 |
+
* @default undefined
|
46 |
+
* @example progresses={{ "file1.png": 50 }}
|
47 |
+
*/
|
48 |
+
progresses?: Record<string, number>
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Accepted file types for the uploader.
|
52 |
+
* @type { [key: string]: string[]}
|
53 |
+
* @default
|
54 |
+
* ```ts
|
55 |
+
* { "text/*": [] }
|
56 |
+
* ```
|
57 |
+
* @example accept={["text/plain", "application/pdf"]}
|
58 |
+
*/
|
59 |
+
accept?: DropzoneProps['accept']
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Maximum file size for the uploader.
|
63 |
+
* @type number | undefined
|
64 |
+
* @default 1024 * 1024 * 200 // 200MB
|
65 |
+
* @example maxSize={1024 * 1024 * 2} // 2MB
|
66 |
+
*/
|
67 |
+
maxSize?: DropzoneProps['maxSize']
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Maximum number of files for the uploader.
|
71 |
+
* @type number | undefined
|
72 |
+
* @default 1
|
73 |
+
* @example maxFileCount={4}
|
74 |
+
*/
|
75 |
+
maxFileCount?: DropzoneProps['maxFiles']
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Whether the uploader should accept multiple files.
|
79 |
+
* @type boolean
|
80 |
+
* @default false
|
81 |
+
* @example multiple
|
82 |
+
*/
|
83 |
+
multiple?: boolean
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Whether the uploader is disabled.
|
87 |
+
* @type boolean
|
88 |
+
* @default false
|
89 |
+
* @example disabled
|
90 |
+
*/
|
91 |
+
disabled?: boolean
|
92 |
+
|
93 |
+
description?: string
|
94 |
+
}
|
95 |
+
|
96 |
+
function formatBytes(
|
97 |
+
bytes: number,
|
98 |
+
opts: {
|
99 |
+
decimals?: number
|
100 |
+
sizeType?: 'accurate' | 'normal'
|
101 |
+
} = {}
|
102 |
+
) {
|
103 |
+
const { decimals = 0, sizeType = 'normal' } = opts
|
104 |
+
|
105 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
106 |
+
const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB']
|
107 |
+
if (bytes === 0) return '0 Byte'
|
108 |
+
const i = Math.floor(Math.log(bytes) / Math.log(1024))
|
109 |
+
return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
|
110 |
+
sizeType === 'accurate' ? (accurateSizes[i] ?? 'Bytes') : (sizes[i] ?? 'Bytes')
|
111 |
+
}`
|
112 |
+
}
|
113 |
+
|
114 |
+
function FileUploader(props: FileUploaderProps) {
|
115 |
+
const {
|
116 |
+
value: valueProp,
|
117 |
+
onValueChange,
|
118 |
+
onUpload,
|
119 |
+
progresses,
|
120 |
+
accept = supportedFileTypes,
|
121 |
+
maxSize = 1024 * 1024 * 200,
|
122 |
+
maxFileCount = 1,
|
123 |
+
multiple = false,
|
124 |
+
disabled = false,
|
125 |
+
description,
|
126 |
+
className,
|
127 |
+
...dropzoneProps
|
128 |
+
} = props
|
129 |
+
|
130 |
+
const [files, setFiles] = useControllableState({
|
131 |
+
prop: valueProp,
|
132 |
+
onChange: onValueChange
|
133 |
+
})
|
134 |
+
|
135 |
+
const onDrop = React.useCallback(
|
136 |
+
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
137 |
+
if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
|
138 |
+
toast.error('Cannot upload more than 1 file at a time')
|
139 |
+
return
|
140 |
+
}
|
141 |
+
|
142 |
+
if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
|
143 |
+
toast.error(`Cannot upload more than ${maxFileCount} files`)
|
144 |
+
return
|
145 |
+
}
|
146 |
+
|
147 |
+
const newFiles = acceptedFiles.map((file) =>
|
148 |
+
Object.assign(file, {
|
149 |
+
preview: URL.createObjectURL(file)
|
150 |
+
})
|
151 |
+
)
|
152 |
+
|
153 |
+
const updatedFiles = files ? [...files, ...newFiles] : newFiles
|
154 |
+
|
155 |
+
setFiles(updatedFiles)
|
156 |
+
|
157 |
+
if (rejectedFiles.length > 0) {
|
158 |
+
rejectedFiles.forEach(({ file }) => {
|
159 |
+
toast.error(`File ${file.name} was rejected`)
|
160 |
+
})
|
161 |
+
}
|
162 |
+
|
163 |
+
if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {
|
164 |
+
const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : 'file'
|
165 |
+
|
166 |
+
toast.promise(onUpload(updatedFiles), {
|
167 |
+
loading: `Uploading ${target}...`,
|
168 |
+
success: () => {
|
169 |
+
setFiles([])
|
170 |
+
return `${target} uploaded`
|
171 |
+
},
|
172 |
+
error: `Failed to upload ${target}`
|
173 |
+
})
|
174 |
+
}
|
175 |
+
},
|
176 |
+
|
177 |
+
[files, maxFileCount, multiple, onUpload, setFiles]
|
178 |
+
)
|
179 |
+
|
180 |
+
function onRemove(index: number) {
|
181 |
+
if (!files) return
|
182 |
+
const newFiles = files.filter((_, i) => i !== index)
|
183 |
+
setFiles(newFiles)
|
184 |
+
onValueChange?.(newFiles)
|
185 |
+
}
|
186 |
+
|
187 |
+
// Revoke preview url when component unmounts
|
188 |
+
React.useEffect(() => {
|
189 |
+
return () => {
|
190 |
+
if (!files) return
|
191 |
+
files.forEach((file) => {
|
192 |
+
if (isFileWithPreview(file)) {
|
193 |
+
URL.revokeObjectURL(file.preview)
|
194 |
+
}
|
195 |
+
})
|
196 |
+
}
|
197 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
198 |
+
}, [])
|
199 |
+
|
200 |
+
const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount
|
201 |
+
|
202 |
+
return (
|
203 |
+
<div className="relative flex flex-col gap-6 overflow-hidden">
|
204 |
+
<Dropzone
|
205 |
+
onDrop={onDrop}
|
206 |
+
accept={accept}
|
207 |
+
maxSize={maxSize}
|
208 |
+
maxFiles={maxFileCount}
|
209 |
+
multiple={maxFileCount > 1 || multiple}
|
210 |
+
disabled={isDisabled}
|
211 |
+
>
|
212 |
+
{({ getRootProps, getInputProps, isDragActive }) => (
|
213 |
+
<div
|
214 |
+
{...getRootProps()}
|
215 |
+
className={cn(
|
216 |
+
'group border-muted-foreground/25 hover:bg-muted/25 relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',
|
217 |
+
'ring-offset-background focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
|
218 |
+
isDragActive && 'border-muted-foreground/50',
|
219 |
+
isDisabled && 'pointer-events-none opacity-60',
|
220 |
+
className
|
221 |
+
)}
|
222 |
+
{...dropzoneProps}
|
223 |
+
>
|
224 |
+
<input {...getInputProps()} />
|
225 |
+
{isDragActive ? (
|
226 |
+
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
227 |
+
<div className="rounded-full border border-dashed p-3">
|
228 |
+
<Upload className="text-muted-foreground size-7" aria-hidden="true" />
|
229 |
+
</div>
|
230 |
+
<p className="text-muted-foreground font-medium">Drop the files here</p>
|
231 |
+
</div>
|
232 |
+
) : (
|
233 |
+
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
|
234 |
+
<div className="rounded-full border border-dashed p-3">
|
235 |
+
<Upload className="text-muted-foreground size-7" aria-hidden="true" />
|
236 |
+
</div>
|
237 |
+
<div className="flex flex-col gap-px">
|
238 |
+
<p className="text-muted-foreground font-medium">
|
239 |
+
Drag and drop files here, or click to select files
|
240 |
+
</p>
|
241 |
+
{description ? (
|
242 |
+
<p className="text-muted-foreground/70 text-sm">{description}</p>
|
243 |
+
) : (
|
244 |
+
<p className="text-muted-foreground/70 text-sm">
|
245 |
+
You can upload
|
246 |
+
{maxFileCount > 1
|
247 |
+
? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}
|
248 |
+
files (up to ${formatBytes(maxSize)} each)`
|
249 |
+
: ` a file with ${formatBytes(maxSize)}`}
|
250 |
+
Supported formats: TXT, MD, DOC, PDF, PPTX
|
251 |
+
</p>
|
252 |
+
)}
|
253 |
+
</div>
|
254 |
+
</div>
|
255 |
+
)}
|
256 |
+
</div>
|
257 |
+
)}
|
258 |
+
</Dropzone>
|
259 |
+
{files?.length ? (
|
260 |
+
<ScrollArea className="h-fit w-full px-3">
|
261 |
+
<div className="flex max-h-48 flex-col gap-4">
|
262 |
+
{files?.map((file, index) => (
|
263 |
+
<FileCard
|
264 |
+
key={index}
|
265 |
+
file={file}
|
266 |
+
onRemove={() => onRemove(index)}
|
267 |
+
progress={progresses?.[file.name]}
|
268 |
+
/>
|
269 |
+
))}
|
270 |
+
</div>
|
271 |
+
</ScrollArea>
|
272 |
+
) : null}
|
273 |
+
</div>
|
274 |
+
)
|
275 |
+
}
|
276 |
+
|
277 |
+
interface FileCardProps {
|
278 |
+
file: File
|
279 |
+
onRemove: () => void
|
280 |
+
progress?: number
|
281 |
+
}
|
282 |
+
|
283 |
+
function FileCard({ file, progress, onRemove }: FileCardProps) {
|
284 |
+
return (
|
285 |
+
<div className="relative flex items-center gap-2.5">
|
286 |
+
<div className="flex flex-1 gap-2.5">
|
287 |
+
{isFileWithPreview(file) ? <FilePreview file={file} /> : null}
|
288 |
+
<div className="flex w-full flex-col gap-2">
|
289 |
+
<div className="flex flex-col gap-px">
|
290 |
+
<p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
|
291 |
+
<p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
|
292 |
+
</div>
|
293 |
+
{progress ? <Progress value={progress} /> : null}
|
294 |
+
</div>
|
295 |
+
</div>
|
296 |
+
<div className="flex items-center gap-2">
|
297 |
+
<Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}>
|
298 |
+
<X className="size-4" aria-hidden="true" />
|
299 |
+
<span className="sr-only">Remove file</span>
|
300 |
+
</Button>
|
301 |
+
</div>
|
302 |
+
</div>
|
303 |
+
)
|
304 |
+
}
|
305 |
+
|
306 |
+
function isFileWithPreview(file: File): file is File & { preview: string } {
|
307 |
+
return 'preview' in file && typeof file.preview === 'string'
|
308 |
+
}
|
309 |
+
|
310 |
+
interface FilePreviewProps {
|
311 |
+
file: File & { preview: string }
|
312 |
+
}
|
313 |
+
|
314 |
+
function FilePreview({ file }: FilePreviewProps) {
|
315 |
+
if (file.type.startsWith('image/')) {
|
316 |
+
return <div className="aspect-square shrink-0 rounded-md object-cover" />
|
317 |
+
}
|
318 |
+
|
319 |
+
return <FileText className="text-muted-foreground size-10" aria-hidden="true" />
|
320 |
+
}
|
321 |
+
|
322 |
+
export default FileUploader
|
lightrag_webui/src/components/ui/NumberInput.tsx
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ChevronDown, ChevronUp } from 'lucide-react'
|
2 |
+
import { forwardRef, useCallback, useEffect, useState } from 'react'
|
3 |
+
import { NumericFormat, NumericFormatProps } from 'react-number-format'
|
4 |
+
import Button from '@/components/ui/Button'
|
5 |
+
import Input from '@/components/ui/Input'
|
6 |
+
import { cn } from '@/lib/utils'
|
7 |
+
|
8 |
+
export interface NumberInputProps extends Omit<NumericFormatProps, 'value' | 'onValueChange'> {
|
9 |
+
stepper?: number
|
10 |
+
thousandSeparator?: string
|
11 |
+
placeholder?: string
|
12 |
+
defaultValue?: number
|
13 |
+
min?: number
|
14 |
+
max?: number
|
15 |
+
value?: number // Controlled value
|
16 |
+
suffix?: string
|
17 |
+
prefix?: string
|
18 |
+
onValueChange?: (value: number | undefined) => void
|
19 |
+
fixedDecimalScale?: boolean
|
20 |
+
decimalScale?: number
|
21 |
+
}
|
22 |
+
|
23 |
+
const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>(
|
24 |
+
(
|
25 |
+
{
|
26 |
+
stepper,
|
27 |
+
thousandSeparator,
|
28 |
+
placeholder,
|
29 |
+
defaultValue,
|
30 |
+
min = -Infinity,
|
31 |
+
max = Infinity,
|
32 |
+
onValueChange,
|
33 |
+
fixedDecimalScale = false,
|
34 |
+
decimalScale = 0,
|
35 |
+
className = undefined,
|
36 |
+
suffix,
|
37 |
+
prefix,
|
38 |
+
value: controlledValue,
|
39 |
+
...props
|
40 |
+
},
|
41 |
+
ref
|
42 |
+
) => {
|
43 |
+
const [value, setValue] = useState<number | undefined>(controlledValue ?? defaultValue)
|
44 |
+
|
45 |
+
const handleIncrement = useCallback(() => {
|
46 |
+
setValue((prev) =>
|
47 |
+
prev === undefined ? (stepper ?? 1) : Math.min(prev + (stepper ?? 1), max)
|
48 |
+
)
|
49 |
+
}, [stepper, max])
|
50 |
+
|
51 |
+
const handleDecrement = useCallback(() => {
|
52 |
+
setValue((prev) =>
|
53 |
+
prev === undefined ? -(stepper ?? 1) : Math.max(prev - (stepper ?? 1), min)
|
54 |
+
)
|
55 |
+
}, [stepper, min])
|
56 |
+
|
57 |
+
useEffect(() => {
|
58 |
+
if (controlledValue !== undefined) {
|
59 |
+
setValue(controlledValue)
|
60 |
+
}
|
61 |
+
}, [controlledValue])
|
62 |
+
|
63 |
+
const handleChange = (values: { value: string; floatValue: number | undefined }) => {
|
64 |
+
const newValue = values.floatValue === undefined ? undefined : values.floatValue
|
65 |
+
setValue(newValue)
|
66 |
+
if (onValueChange) {
|
67 |
+
onValueChange(newValue)
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
const handleBlur = () => {
|
72 |
+
if (value !== undefined) {
|
73 |
+
if (value < min) {
|
74 |
+
setValue(min)
|
75 |
+
;(ref as React.RefObject<HTMLInputElement>).current!.value = String(min)
|
76 |
+
} else if (value > max) {
|
77 |
+
setValue(max)
|
78 |
+
;(ref as React.RefObject<HTMLInputElement>).current!.value = String(max)
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
return (
|
84 |
+
<div className="relative flex">
|
85 |
+
<NumericFormat
|
86 |
+
value={value}
|
87 |
+
onValueChange={handleChange}
|
88 |
+
thousandSeparator={thousandSeparator}
|
89 |
+
decimalScale={decimalScale}
|
90 |
+
fixedDecimalScale={fixedDecimalScale}
|
91 |
+
allowNegative={min < 0}
|
92 |
+
valueIsNumericString
|
93 |
+
onBlur={handleBlur}
|
94 |
+
max={max}
|
95 |
+
min={min}
|
96 |
+
suffix={suffix}
|
97 |
+
prefix={prefix}
|
98 |
+
customInput={(props) => <Input {...props} className={cn('w-full', className)} />}
|
99 |
+
placeholder={placeholder}
|
100 |
+
className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
101 |
+
getInputRef={ref}
|
102 |
+
{...props}
|
103 |
+
/>
|
104 |
+
<div className="absolute top-0 right-0 bottom-0 flex flex-col">
|
105 |
+
<Button
|
106 |
+
aria-label="Increase value"
|
107 |
+
className="border-input h-1/2 rounded-l-none rounded-br-none border-b border-l px-2 focus-visible:relative"
|
108 |
+
variant="outline"
|
109 |
+
onClick={handleIncrement}
|
110 |
+
disabled={value === max}
|
111 |
+
>
|
112 |
+
<ChevronUp size={15} />
|
113 |
+
</Button>
|
114 |
+
<Button
|
115 |
+
aria-label="Decrease value"
|
116 |
+
className="border-input h-1/2 rounded-l-none rounded-tr-none border-b border-l px-2 focus-visible:relative"
|
117 |
+
variant="outline"
|
118 |
+
onClick={handleDecrement}
|
119 |
+
disabled={value === min}
|
120 |
+
>
|
121 |
+
<ChevronDown size={15} />
|
122 |
+
</Button>
|
123 |
+
</div>
|
124 |
+
</div>
|
125 |
+
)
|
126 |
+
}
|
127 |
+
)
|
128 |
+
|
129 |
+
NumberInput.displayName = 'NumberInput'
|
130 |
+
|
131 |
+
export default NumberInput
|
lightrag_webui/src/components/ui/Progress.tsx
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
import * as ProgressPrimitive from '@radix-ui/react-progress'
|
3 |
+
|
4 |
+
import { cn } from '@/lib/utils'
|
5 |
+
|
6 |
+
const Progress = React.forwardRef<
|
7 |
+
React.ComponentRef<typeof ProgressPrimitive.Root>,
|
8 |
+
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
9 |
+
>(({ className, value, ...props }, ref) => (
|
10 |
+
<ProgressPrimitive.Root
|
11 |
+
ref={ref}
|
12 |
+
className={cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className)}
|
13 |
+
{...props}
|
14 |
+
>
|
15 |
+
<ProgressPrimitive.Indicator
|
16 |
+
className="bg-primary h-full w-full flex-1 transition-all"
|
17 |
+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
18 |
+
/>
|
19 |
+
</ProgressPrimitive.Root>
|
20 |
+
))
|
21 |
+
Progress.displayName = ProgressPrimitive.Root.displayName
|
22 |
+
|
23 |
+
export default Progress
|
lightrag_webui/src/components/ui/ScrollArea.tsx
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
3 |
+
|
4 |
+
import { cn } from '@/lib/utils'
|
5 |
+
|
6 |
+
const ScrollArea = React.forwardRef<
|
7 |
+
React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
|
8 |
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
9 |
+
>(({ className, children, ...props }, ref) => (
|
10 |
+
<ScrollAreaPrimitive.Root
|
11 |
+
ref={ref}
|
12 |
+
className={cn('relative overflow-hidden', className)}
|
13 |
+
{...props}
|
14 |
+
>
|
15 |
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
16 |
+
{children}
|
17 |
+
</ScrollAreaPrimitive.Viewport>
|
18 |
+
<ScrollBar />
|
19 |
+
<ScrollAreaPrimitive.Corner />
|
20 |
+
</ScrollAreaPrimitive.Root>
|
21 |
+
))
|
22 |
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
23 |
+
|
24 |
+
const ScrollBar = React.forwardRef<
|
25 |
+
React.ComponentRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
26 |
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
27 |
+
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
28 |
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
29 |
+
ref={ref}
|
30 |
+
orientation={orientation}
|
31 |
+
className={cn(
|
32 |
+
'flex touch-none transition-colors select-none',
|
33 |
+
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
|
34 |
+
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
|
35 |
+
className
|
36 |
+
)}
|
37 |
+
{...props}
|
38 |
+
>
|
39 |
+
<ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
|
40 |
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
41 |
+
))
|
42 |
+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
43 |
+
|
44 |
+
export { ScrollArea, ScrollBar }
|
lightrag_webui/src/components/ui/Select.tsx
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from 'react'
|
2 |
+
import * as SelectPrimitive from '@radix-ui/react-select'
|
3 |
+
import { Check, ChevronDown, ChevronUp } from 'lucide-react'
|
4 |
+
|
5 |
+
import { cn } from '@/lib/utils'
|
6 |
+
|
7 |
+
const Select = SelectPrimitive.Root
|
8 |
+
|
9 |
+
const SelectGroup = SelectPrimitive.Group
|
10 |
+
|
11 |
+
const SelectValue = SelectPrimitive.Value
|
12 |
+
|
13 |
+
const SelectTrigger = React.forwardRef<
|
14 |
+
React.ComponentRef<typeof SelectPrimitive.Trigger>,
|
15 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
16 |
+
>(({ className, children, ...props }, ref) => (
|
17 |
+
<SelectPrimitive.Trigger
|
18 |
+
ref={ref}
|
19 |
+
className={cn(
|
20 |
+
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
21 |
+
className
|
22 |
+
)}
|
23 |
+
{...props}
|
24 |
+
>
|
25 |
+
{children}
|
26 |
+
<SelectPrimitive.Icon asChild>
|
27 |
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
28 |
+
</SelectPrimitive.Icon>
|
29 |
+
</SelectPrimitive.Trigger>
|
30 |
+
))
|
31 |
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
32 |
+
|
33 |
+
const SelectScrollUpButton = React.forwardRef<
|
34 |
+
React.ComponentRef<typeof SelectPrimitive.ScrollUpButton>,
|
35 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
36 |
+
>(({ className, ...props }, ref) => (
|
37 |
+
<SelectPrimitive.ScrollUpButton
|
38 |
+
ref={ref}
|
39 |
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
40 |
+
{...props}
|
41 |
+
>
|
42 |
+
<ChevronUp className="h-4 w-4" />
|
43 |
+
</SelectPrimitive.ScrollUpButton>
|
44 |
+
))
|
45 |
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
46 |
+
|
47 |
+
const SelectScrollDownButton = React.forwardRef<
|
48 |
+
React.ComponentRef<typeof SelectPrimitive.ScrollDownButton>,
|
49 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<SelectPrimitive.ScrollDownButton
|
52 |
+
ref={ref}
|
53 |
+
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
54 |
+
{...props}
|
55 |
+
>
|
56 |
+
<ChevronDown className="h-4 w-4" />
|
57 |
+
</SelectPrimitive.ScrollDownButton>
|
58 |
+
))
|
59 |
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
60 |
+
|
61 |
+
const SelectContent = React.forwardRef<
|
62 |
+
React.ComponentRef<typeof SelectPrimitive.Content>,
|
63 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
64 |
+
>(({ className, children, position = 'popper', ...props }, ref) => (
|
65 |
+
<SelectPrimitive.Portal>
|
66 |
+
<SelectPrimitive.Content
|
67 |
+
ref={ref}
|
68 |
+
className={cn(
|
69 |
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md',
|
70 |
+
position === 'popper' &&
|
71 |
+
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
72 |
+
className
|
73 |
+
)}
|
74 |
+
position={position}
|
75 |
+
{...props}
|
76 |
+
>
|
77 |
+
<SelectScrollUpButton />
|
78 |
+
<SelectPrimitive.Viewport
|
79 |
+
className={cn(
|
80 |
+
'p-1',
|
81 |
+
position === 'popper' &&
|
82 |
+
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
|
83 |
+
)}
|
84 |
+
>
|
85 |
+
{children}
|
86 |
+
</SelectPrimitive.Viewport>
|
87 |
+
<SelectScrollDownButton />
|
88 |
+
</SelectPrimitive.Content>
|
89 |
+
</SelectPrimitive.Portal>
|
90 |
+
))
|
91 |
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
92 |
+
|
93 |
+
const SelectLabel = React.forwardRef<
|
94 |
+
React.ComponentRef<typeof SelectPrimitive.Label>,
|
95 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
96 |
+
>(({ className, ...props }, ref) => (
|
97 |
+
<SelectPrimitive.Label
|
98 |
+
ref={ref}
|
99 |
+
className={cn('py-1.5 pr-2 pl-8 text-sm font-semibold', className)}
|
100 |
+
{...props}
|
101 |
+
/>
|
102 |
+
))
|
103 |
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
104 |
+
|
105 |
+
const SelectItem = React.forwardRef<
|
106 |
+
React.ComponentRef<typeof SelectPrimitive.Item>,
|
107 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
108 |
+
>(({ className, children, ...props }, ref) => (
|
109 |
+
<SelectPrimitive.Item
|
110 |
+
ref={ref}
|
111 |
+
className={cn(
|
112 |
+
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
113 |
+
className
|
114 |
+
)}
|
115 |
+
{...props}
|
116 |
+
>
|
117 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
118 |
+
<SelectPrimitive.ItemIndicator>
|
119 |
+
<Check className="h-4 w-4" />
|
120 |
+
</SelectPrimitive.ItemIndicator>
|
121 |
+
</span>
|
122 |
+
|
123 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
124 |
+
</SelectPrimitive.Item>
|
125 |
+
))
|
126 |
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
127 |
+
|
128 |
+
const SelectSeparator = React.forwardRef<
|
129 |
+
React.ComponentRef<typeof SelectPrimitive.Separator>,
|
130 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
131 |
+
>(({ className, ...props }, ref) => (
|
132 |
+
<SelectPrimitive.Separator
|
133 |
+
ref={ref}
|
134 |
+
className={cn('bg-muted -mx-1 my-1 h-px', className)}
|
135 |
+
{...props}
|
136 |
+
/>
|
137 |
+
))
|
138 |
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
139 |
+
|
140 |
+
export {
|
141 |
+
Select,
|
142 |
+
SelectGroup,
|
143 |
+
SelectValue,
|
144 |
+
SelectTrigger,
|
145 |
+
SelectContent,
|
146 |
+
SelectLabel,
|
147 |
+
SelectItem,
|
148 |
+
SelectSeparator,
|
149 |
+
SelectScrollUpButton,
|
150 |
+
SelectScrollDownButton
|
151 |
+
}
|