PBThuong commited on
Commit
979853c
·
1 Parent(s): 48567a7

Deploy LightRAG AI Assistant with Git LFS for binary assets and fonts

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .clinerules/01-basic.md +257 -0
  2. .dockerignore +69 -0
  3. .env.huggingface +40 -0
  4. .gitattributes +3 -35
  5. .github/CONTRIBUTING.md +163 -0
  6. .github/ISSUE_TEMPLATE/bug_report.yml +61 -0
  7. .github/ISSUE_TEMPLATE/config.yml +1 -0
  8. .github/ISSUE_TEMPLATE/feature_request.yml +26 -0
  9. .github/ISSUE_TEMPLATE/question.yml +26 -0
  10. .github/dependabot.yml +206 -0
  11. .github/pull_request_template.md +32 -0
  12. .github/workflows/copilot-setup-steps.yml +58 -0
  13. .github/workflows/docker-build-lite.yml +91 -0
  14. .github/workflows/docker-build-manual.yml +87 -0
  15. .github/workflows/docker-publish.yml +90 -0
  16. .github/workflows/linting.yaml +134 -0
  17. .github/workflows/pypi-publish.yml +101 -0
  18. .github/workflows/stale.yaml +27 -0
  19. .github/workflows/tests.yml +56 -0
  20. .gitignore +86 -0
  21. .pre-commit-config.yaml +28 -0
  22. AGENTS.md +61 -0
  23. Dockerfile +109 -0
  24. Dockerfile.lite +110 -0
  25. LICENSE +21 -0
  26. MANIFEST.in +4 -0
  27. Makefile +99 -0
  28. README-zh.md +433 -0
  29. android-chrome-512x512.png → README.assets/b2aaf634151b4706892693ffb43d9093.png +2 -2
  30. apple-touch-icon.png → README.assets/iShot_2025-03-23_12.40.08.png +2 -2
  31. README.md +435 -2
  32. SECURITY.md +18 -0
  33. _redirects +0 -1
  34. assets/AIAssistant-CUeVd4uO.css +0 -1
  35. assets/AIAssistant-foLVZGCT.js +0 -1
  36. assets/Dashboard-Bu4sLdM_.js +0 -1
  37. assets/Dashboard-DplGvfIe.css +0 -1
  38. assets/DeviceList-uEjUqpus.js +0 -1
  39. assets/DeviceProfile-CSWlLc0Y.js +0 -1
  40. assets/Devices-Cc89Wtut.js +0 -1
  41. assets/Devices-DMAvR0Dh.css +0 -1
  42. assets/EvidenceLinks-A2VHKnJo.js +0 -2
  43. assets/EvidenceLinks-BVpp2_oz.css +0 -1
  44. assets/GspLog-BEwbqtYT.js +0 -1
  45. assets/GspLog-Bk61_0BY.css +0 -1
  46. assets/LiteWrite.png +3 -0
  47. assets/Login-Cq7VUOkg.css +0 -1
  48. assets/Login-Dv6ikErd.js +0 -1
  49. assets/Operations-D1k_u3uX.js +0 -1
  50. assets/Operations-OxdQ2jh6.css +0 -1
.clinerules/01-basic.md ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LightRAG Project Intelligence (.clinerules)
2
+
3
+ ## Project Overview
4
+ LightRAG is a mature, production-ready Retrieval-Augmented Generation (RAG) system with comprehensive knowledge graph capabilities. The system has evolved from experimental to production-ready status with extensive functionality across all major components.
5
+
6
+ ## Current System State (August 15, 2025)
7
+ - **Status**: Production Ready - Stable and Mature
8
+ - **Configuration**: Gemini 2.5 Flash + BAAI/bge-m3 embeddings via custom endpoints
9
+ - **Storage**: Default in-memory with file persistence (JsonKVStorage, NetworkXStorage, NanoVectorDBStorage)
10
+ - **Language**: Chinese for summaries
11
+ - **Workspace**: `space1` for data isolation
12
+ - **Authentication**: JWT-based with admin/user accounts
13
+
14
+ ## Critical Implementation Patterns
15
+
16
+ ### 1. Embedding Format Compatibility (CRITICAL)
17
+ **Pattern**: Always handle both base64 and raw array embedding formats
18
+ **Location**: `lightrag/llm/openai.py` - `openai_embed` function
19
+ **Issue**: Custom OpenAI-compatible endpoints return embeddings as raw arrays, not base64 strings
20
+ **Solution**:
21
+ ```python
22
+ np.array(dp.embedding, dtype=np.float32) if isinstance(dp.embedding, list)
23
+ else np.frombuffer(base64.b64decode(dp.embedding), dtype=np.float32)
24
+ ```
25
+ **Impact**: Document processing fails completely without this dual format support
26
+
27
+ ### 2. Async Pattern Consistency (CRITICAL)
28
+ **Pattern**: Always await coroutines before calling methods on the result
29
+ **Common Error**: `coroutine.method()` instead of `(await coroutine).method()`
30
+ **Locations**: MongoDB implementations, Neo4j operations
31
+ **Example**: `await self._data.list_indexes()` then `await cursor.to_list()`
32
+
33
+ ### 3. Storage Layer Data Compatibility (CRITICAL)
34
+ **Pattern**: Always filter deprecated/incompatible fields during deserialization
35
+ **Common Fields to Remove**: `content`, `_id` (MongoDB), database-specific fields
36
+ **Implementation**: `data.pop('field_name', None)` before creating dataclass objects
37
+ **Locations**: All storage implementations (JSON, Redis, MongoDB, PostgreSQL)
38
+
39
+ ### 4. Lock Key Generation (CRITICAL)
40
+ **Pattern**: Always sort relationship pairs for consistent lock keys
41
+ **Implementation**: `sorted_key_parts = sorted([src, tgt])` then `f"{sorted_key_parts[0]}-{sorted_key_parts[1]}"`
42
+ **Impact**: Prevents deadlocks in concurrent relationship processing
43
+
44
+ ### 5. Event Loop Management (CRITICAL)
45
+ **Pattern**: Handle event loop mismatches during shutdown gracefully
46
+ **Implementation**: Timeout + specific RuntimeError handling for "attached to a different loop"
47
+ **Location**: Neo4j storage finalization
48
+ **Impact**: Prevents application shutdown failures
49
+
50
+ ### 6. Async Generator Lock Management (CRITICAL)
51
+ **Pattern**: Never hold locks across async generator yields - create snapshots instead
52
+ **Issue**: Holding locks while yielding causes deadlock when consumers need the same lock
53
+ **Location**: `lightrag/tools/migrate_llm_cache.py` - `stream_default_caches_json`
54
+ **Solution**: Create snapshot of data while holding lock, release lock, then iterate over snapshot
55
+ ```python
56
+ # WRONG - Deadlock prone:
57
+ async with storage._storage_lock:
58
+ for key, value in storage._data.items():
59
+ batch[key] = value
60
+ if len(batch) >= batch_size:
61
+ yield batch # Lock still held!
62
+
63
+ # CORRECT - Snapshot approach:
64
+ async with storage._storage_lock:
65
+ matching_items = [(k, v) for k, v in storage._data.items() if condition]
66
+ # Lock released here
67
+ for key, value in matching_items:
68
+ batch[key] = value
69
+ if len(batch) >= batch_size:
70
+ yield batch # No lock held
71
+ ```
72
+ **Impact**: Prevents deadlocks in Json→Json migrations and similar scenarios where source/target share locks
73
+ **Applicable To**: Any async generator that needs to access shared resources while yielding
74
+
75
+ ## Architecture Patterns
76
+
77
+ ### 1. Dependency Injection
78
+ **Pattern**: Pass configuration through object constructors, not direct imports
79
+ **Example**: OllamaAPI receives configuration through LightRAG object
80
+ **Benefit**: Better testability and modularity
81
+
82
+ ### 2. Memory Bank Documentation
83
+ **Pattern**: Maintain comprehensive memory bank for development continuity
84
+ **Structure**: Core files (projectbrief.md, activeContext.md, progress.md, etc.)
85
+ **Purpose**: Essential for context preservation across development sessions
86
+
87
+ ### 3. Configuration Management
88
+ **Pattern**: Centralize defaults in constants.py, use environment variables for runtime config
89
+ **Implementation**: Default values in constants, override via .env file
90
+ **Benefit**: Consistent configuration across components
91
+
92
+ ## Development Workflow Patterns
93
+
94
+ ### 1. Frontend Development (CRITICAL)
95
+ **Package Manager**: **ALWAYS USE BUN** - Never use npm or yarn unless Bun is unavailable
96
+ **Commands**:
97
+ - `bun install` - Install dependencies
98
+ - `bun run dev` - Start development server
99
+ - `bun run build` - Build for production
100
+ - `bun run lint` - Run linting
101
+ - `bun test` - Run tests
102
+ - `bun run preview` - Preview production build
103
+
104
+ **Pattern**: All frontend operations must use Bun commands
105
+ **Fallback**: Only use npm/yarn if Bun installation fails
106
+ **Testing**: Use `bun test` for all frontend testing
107
+
108
+ ### 2. Bug Fix Approach
109
+ 1. **Identify root cause** - Don't just fix symptoms
110
+ 2. **Implement robust solution** - Handle edge cases and format variations
111
+ 3. **Maintain backward compatibility** - Preserve existing functionality
112
+ 4. **Add comprehensive error handling** - Graceful degradation
113
+ 5. **Document the fix** - Update memory bank with technical details
114
+
115
+ ### 3. Feature Implementation
116
+ 1. **Follow existing patterns** - Maintain architectural consistency
117
+ 2. **Use dependency injection** - Avoid direct imports between modules
118
+ 3. **Implement comprehensive error handling** - Handle all failure modes
119
+ 4. **Add proper logging** - Debug and warning messages
120
+ 5. **Update documentation** - Memory bank and code comments
121
+ 6. **Comment Language** - Use English for comments and documentation
122
+
123
+ ### 4. Performance Optimization
124
+ 1. **Profile before optimizing** - Identify actual bottlenecks
125
+ 2. **Maintain algorithmic correctness** - Don't sacrifice functionality for speed
126
+ 3. **Use appropriate data structures** - Match structure to access patterns
127
+ 4. **Implement caching strategically** - Cache expensive operations
128
+ 5. **Monitor memory usage** - Prevent memory leaks
129
+
130
+ ### 5. Testing Workflow (CRITICAL)
131
+ **Pattern**: All tests must use pytest markers for proper CI/CD execution
132
+ **Test Categories**:
133
+ - **Offline Tests**: Use `@pytest.mark.offline` - No external dependencies (runs in CI)
134
+ - **Integration Tests**: Use `@pytest.mark.integration` - Requires databases/APIs (skipped by default)
135
+
136
+ **Commands**:
137
+ - `pytest tests/ -m offline -v` - CI default (~3 seconds for 21 tests)
138
+ - `pytest tests/ --run-integration -v` - Full test suite (all 46 tests)
139
+
140
+ **Best Practices**:
141
+ 1. **Prefer offline tests** - Use mocks for LLM, embeddings, databases
142
+ 2. **Mock external dependencies** - AsyncMock for async functions
143
+ 3. **Test isolation** - Each test should be independent
144
+ 4. **Documentation** - Add docstrings explaining purpose and scope
145
+
146
+ **Configuration**:
147
+ - `tests/pytest.ini` - Marker definitions and test discovery
148
+ - `tests/conftest.py` - Fixtures and custom options
149
+ - `.github/workflows/tests.yml` - CI/CD workflow (Python 3.10/3.11/3.12)
150
+
151
+ **Documentation**: See `memory-bank/testing-guidelines.md` for complete testing guidelines
152
+
153
+ **Impact**: Ensures all tests run reliably in CI without external services while maintaining comprehensive integration test coverage for local development
154
+
155
+ ## Technology Stack Intelligence
156
+
157
+ ### 1. LLM Integration
158
+ - **Primary**: Gemini 2.5 Flash via custom endpoint
159
+ - **Embedding**: BAAI/bge-m3 via custom endpoint
160
+ - **Reranking**: BAAI/bge-reranker-v2-m3
161
+ - **Pattern**: Always handle multiple provider formats
162
+
163
+ ### 2. Storage Backends
164
+ - **Default**: In-memory with file persistence
165
+ - **Production Options**: PostgreSQL, MongoDB, Redis, Neo4j
166
+ - **Pattern**: Abstract storage interface with multiple implementations
167
+
168
+ ### 3. API Architecture
169
+ - **Framework**: FastAPI with Gunicorn for production
170
+ - **Authentication**: JWT-based with role support
171
+ - **Compatibility**: Ollama-compatible endpoints for easy integration
172
+
173
+ ### 4. Frontend
174
+ - **Framework**: React with TypeScript
175
+ - **Package Manager**: **BUN (REQUIRED)** - Always use Bun for all frontend operations
176
+ - **Build Tool**: Vite with Bun runtime
177
+ - **Visualization**: Sigma.js for graph rendering
178
+ - **State Management**: React hooks with context
179
+ - **Internationalization**: i18next for multi-language support
180
+
181
+ ## Common Pitfalls and Solutions
182
+
183
+ ### 1. Embedding Format Issues
184
+ **Pitfall**: Assuming all endpoints return base64-encoded embeddings
185
+ **Solution**: Always check format and handle both base64 and raw arrays
186
+
187
+ ### 2. Async/Await Patterns
188
+ **Pitfall**: Calling methods on coroutines instead of awaited results
189
+ **Solution**: Always await coroutines before accessing their methods
190
+
191
+ ### 3. Data Model Evolution
192
+ **Pitfall**: Breaking changes when removing fields from dataclasses
193
+ **Solution**: Filter deprecated fields during deserialization, don't break storage
194
+
195
+ ### 4. Concurrency Issues
196
+ **Pitfall**: Inconsistent lock key generation causing deadlocks
197
+ **Solution**: Always sort keys for deterministic lock ordering
198
+
199
+ ### 5. Event Loop Management
200
+ **Pitfall**: Event loop mismatches during shutdown
201
+ **Solution**: Implement timeout and specific error handling for loop issues
202
+
203
+ ## Performance Considerations
204
+
205
+ ### 1. Query Context Building
206
+ - **Algorithm**: Linear gradient weighted polling for fair resource allocation
207
+ - **Optimization**: Round-robin merging to eliminate mode bias
208
+ - **Pattern**: Smart chunk selection based on cross-entity occurrence
209
+
210
+ ### 2. Graph Operations
211
+ - **Optimization**: Batch operations where possible
212
+ - **Pattern**: Use appropriate indexing for large datasets
213
+ - **Consideration**: Memory usage with large graphs
214
+
215
+ ### 3. LLM Request Management
216
+ - **Pattern**: Priority-based queue for request ordering
217
+ - **Optimization**: Connection pooling and retry mechanisms
218
+ - **Consideration**: Rate limiting and cost management
219
+
220
+ ## Security Patterns
221
+
222
+ ### 1. Authentication
223
+ - **Implementation**: JWT tokens with role-based access
224
+ - **Pattern**: Stateless authentication with configurable expiration
225
+ - **Security**: Proper token validation and refresh mechanisms
226
+
227
+ ### 2. API Security
228
+ - **Pattern**: Input validation and sanitization
229
+ - **Implementation**: FastAPI dependency injection for auth
230
+ - **Consideration**: Rate limiting and abuse prevention
231
+
232
+ ## Maintenance Guidelines
233
+
234
+ ### 1. Memory Bank Updates
235
+ - **Trigger**: After significant changes or bug fixes
236
+ - **Pattern**: Update activeContext.md and progress.md
237
+ - **Purpose**: Maintain development continuity
238
+
239
+ ### 2. Configuration Management
240
+ - **Pattern**: Environment-based configuration with sensible defaults
241
+ - **Implementation**: .env files with example templates
242
+ - **Consideration**: Security for production deployments
243
+
244
+ ### 3. Error Handling
245
+ - **Pattern**: Comprehensive logging with appropriate levels
246
+ - **Implementation**: Graceful degradation where possible
247
+ - **Consideration**: User-friendly error messages
248
+
249
+ ## Project Evolution Notes
250
+
251
+ The project has evolved from experimental to production-ready status. Key milestones:
252
+ - **Early 2025**: Basic RAG implementation
253
+ - **Mid 2025**: Multiple storage backends and LLM providers
254
+ - **July 2025**: Major query optimization and algorithm improvements
255
+ - **August 2025**: Production-ready stable state
256
+
257
+ The system now supports enterprise-level deployments with comprehensive functionality across all components.
.dockerignore ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-related files and directories
2
+ __pycache__
3
+ .cache
4
+
5
+ # Virtual environment directories
6
+ *.venv
7
+
8
+ # Env
9
+ env/
10
+ *.env*
11
+ .env_example
12
+
13
+ # Distribution / build files
14
+ site
15
+ dist/
16
+ build/
17
+ .eggs/
18
+ *.egg-info/
19
+ *.tgz
20
+ *.tar.gz
21
+
22
+ # Exclude siles and folders
23
+ *.yml
24
+ .dockerignore
25
+ Dockerfile
26
+ Makefile
27
+
28
+ # Exclude other projects
29
+ /tests
30
+ /scripts
31
+ /data
32
+ /dickens
33
+ /reproduce
34
+ /output_complete
35
+ /rag_storage
36
+ /inputs
37
+
38
+ # Python version manager file
39
+ .python-version
40
+
41
+ # Reports
42
+ *.coverage/
43
+ *.log
44
+ log/
45
+ *.logfire
46
+
47
+ # Cache
48
+ .cache/
49
+ .mypy_cache
50
+ .pytest_cache
51
+ .ruff_cache
52
+ .gradio
53
+ .logfire
54
+ temp/
55
+
56
+ # MacOS-related files
57
+ .DS_Store
58
+
59
+ # VS Code settings (local configuration files)
60
+ .vscode
61
+
62
+ # file
63
+ TODO.md
64
+
65
+ # Exclude Git-related files
66
+ .git
67
+ .github
68
+ .gitignore
69
+ .pre-commit-config.yaml
.env.huggingface ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ===== Server =====
2
+ HOST=0.0.0.0
3
+ PORT=9621
4
+ CORS_ORIGINS=*
5
+ SUMMARY_LANGUAGE=Vietnamese
6
+
7
+ # ===== LLM: Gemini Flash =====
8
+ LLM_BINDING=gemini
9
+ LLM_MODEL=gemini-2.0-flash
10
+ LLM_BINDING_API_KEY=${GEMINI_API_KEY}
11
+ LLM_BINDING_HOST=https://generativelanguage.googleapis.com
12
+
13
+ # ===== Embedding: Gemini =====
14
+ EMBEDDING_BINDING=gemini
15
+ EMBEDDING_MODEL=gemini-embedding-001
16
+ EMBEDDING_DIM=1536
17
+ EMBEDDING_TOKEN_LIMIT=2048
18
+ EMBEDDING_BINDING_HOST=https://generativelanguage.googleapis.com
19
+ EMBEDDING_BINDING_API_KEY=${GEMINI_API_KEY}
20
+ EMBEDDING_SEND_DIM=true
21
+
22
+ # ===== Storage: Lightweight (phù hợp HuggingFace free tier) =====
23
+ LIGHTRAG_KV_STORAGE=JsonKVStorage
24
+ LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage
25
+ LIGHTRAG_GRAPH_STORAGE=NetworkXStorage
26
+ LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage
27
+
28
+ # ===== Performance =====
29
+ MAX_ASYNC=4
30
+ MAX_PARALLEL_INSERT=2
31
+ RERANK_BINDING=null
32
+ ENABLE_LLM_CACHE_FOR_EXTRACT=true
33
+
34
+ # ===== Security =====
35
+ # Cấu hình API key trong HuggingFace Secrets, KHÔNG hardcode ở đây
36
+ # LIGHTRAG_API_KEY=${LIGHTRAG_API_KEY}
37
+ # WHITELIST_PATHS=/health,/api/*
38
+
39
+ # ===== Entity types phù hợp y tế =====
40
+ ENTITY_TYPES='["Thiết bị", "Khoa phòng", "Nhà sản xuất", "Quy trình", "Văn bản pháp quy", "Người", "Tổ chức", "Phương pháp", "Vật tư", "Sự kiện"]'
.gitattributes CHANGED
@@ -1,37 +1,5 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  *.png filter=lfs diff=lfs merge=lfs -text
 
37
  *.ico filter=lfs diff=lfs merge=lfs -text
 
1
+ lightrag/api/webui/** binary
2
+ lightrag/api/webui/** linguist-generated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  *.png filter=lfs diff=lfs merge=lfs -text
4
+ *.ttf filter=lfs diff=lfs merge=lfs -text
5
  *.ico filter=lfs diff=lfs merge=lfs -text
.github/CONTRIBUTING.md ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to LightRAG
2
+
3
+ Thank you for your interest in contributing! This guide covers everything you need to get started.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Ways to Contribute](#ways-to-contribute)
8
+ - [Development Setup](#development-setup)
9
+ - [Code Style](#code-style)
10
+ - [Running Tests](#running-tests)
11
+ - [Submitting a Pull Request](#submitting-a-pull-request)
12
+ - [Reporting Bugs](#reporting-bugs)
13
+ - [Requesting Features](#requesting-features)
14
+
15
+ ---
16
+
17
+ ## Ways to Contribute
18
+
19
+ - **Bug reports** — open an [issue](https://github.com/HKUDS/LightRAG/issues) using the Bug Report template
20
+ - **Feature requests** — open an [issue](https://github.com/HKUDS/LightRAG/issues) using the Feature Request template
21
+ - **Documentation** — fix typos, clarify explanations, or add examples
22
+ - **Code** — fix bugs, implement features, or add storage/LLM backends
23
+ - **Testing** — add test coverage for untested code paths
24
+
25
+ ---
26
+
27
+ ## Development Setup
28
+
29
+ ```bash
30
+ # Clone the repository
31
+ git clone https://github.com/HKUDS/LightRAG.git
32
+ cd LightRAG
33
+
34
+ # Install in development mode (requires uv)
35
+ uv sync
36
+ source .venv/bin/activate # Linux/macOS
37
+ # .venv\Scripts\activate # Windows
38
+
39
+ # Install with optional extras as needed
40
+ uv sync --extra api # FastAPI server
41
+ uv sync --extra test # Test dependencies
42
+ uv sync --extra offline-storage # Storage backends
43
+ uv sync --extra offline-llm # Additional LLM providers
44
+
45
+ # Set up pre-commit hooks (run once)
46
+ pip install pre-commit
47
+ pre-commit install
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Code Style
53
+
54
+ This project uses [Ruff](https://docs.astral.sh/ruff/) for formatting and linting, enforced via [pre-commit](https://pre-commit.com/).
55
+
56
+ ### Automatic fixing
57
+
58
+ Running `pre-commit run --all-files` will automatically fix most style issues:
59
+
60
+ ```bash
61
+ # Fix all files
62
+ pre-commit run --all-files
63
+
64
+ # Fix only staged files (faster during development)
65
+ pre-commit run
66
+ ```
67
+
68
+ ### What is checked
69
+
70
+ | Hook | What it does |
71
+ |------|-------------|
72
+ | `trailing-whitespace` | Removes trailing whitespace |
73
+ | `end-of-file-fixer` | Ensures files end with a newline |
74
+ | `requirements-txt-fixer` | Keeps `requirements.txt` entries sorted |
75
+ | `ruff-format` | Formats Python code (Black-compatible) |
76
+ | `ruff` | Fixes Python lint errors |
77
+
78
+ ### CI check
79
+
80
+ The same checks run automatically on every pull request. If the CI check fails, run `pre-commit run --all-files` locally, commit the fixes, and push again.
81
+
82
+ ### Language conventions
83
+
84
+ - **Python code and comments**: English
85
+ - **Frontend (WebUI)**: uses i18next for internationalization — add translation keys rather than hardcoding strings
86
+
87
+ ---
88
+
89
+ ## Running Tests
90
+
91
+ ```bash
92
+ # Run offline tests (no external services required)
93
+ python -m pytest tests
94
+
95
+ # Run integration tests (requires configured external services)
96
+ python -m pytest tests --run-integration
97
+
98
+ # Run a specific test file
99
+ python -m pytest tests/test_lightrag.py
100
+
101
+ # Keep test artifacts for debugging
102
+ python -m pytest tests --keep-artifacts
103
+ ```
104
+
105
+ Set `LIGHTRAG_RUN_INTEGRATION=true` as an environment variable as an alternative to `--run-integration`.
106
+
107
+ ---
108
+
109
+ ## Submitting a Pull Request
110
+
111
+ 1. **Fork** the repository and create a branch from `main`:
112
+ ```bash
113
+ git checkout -b fix/your-descriptive-branch-name
114
+ ```
115
+
116
+ 2. **Make your changes** and ensure:
117
+ - Pre-commit checks pass: `pre-commit run --all-files`
118
+ - Relevant tests pass: `python -m pytest tests`
119
+ - New behavior is covered by tests where applicable
120
+
121
+ 3. **Commit** with a clear message describing *why* the change was made:
122
+ ```bash
123
+ git commit -m "fix: handle permission-only encrypted PDFs without password"
124
+ ```
125
+
126
+ 4. **Push** and open a pull request against `main`. Fill out the pull request template completely.
127
+
128
+ 5. **Respond to review feedback** — a maintainer will review your PR and may request changes.
129
+
130
+ ### Pull request checklist
131
+
132
+ - [ ] Changes tested locally
133
+ - [ ] Pre-commit checks pass (`pre-commit run --all-files`)
134
+ - [ ] Unit/integration tests added or updated where applicable
135
+ - [ ] Documentation updated if behavior changes
136
+ - [ ] PR description explains the *why*, not just the *what*
137
+
138
+ ---
139
+
140
+ ## Reporting Bugs
141
+
142
+ Please use the [Bug Report issue template](https://github.com/HKUDS/LightRAG/issues/new?template=bug_report.yml). Include:
143
+
144
+ - LightRAG version and Python version
145
+ - Storage backend and LLM provider being used
146
+ - Minimal reproducible example
147
+ - Full error traceback
148
+
149
+ ---
150
+
151
+ ## Requesting Features
152
+
153
+ Please use the [Feature Request issue template](https://github.com/HKUDS/LightRAG/issues/new?template=feature_request.yml). Describe:
154
+
155
+ - The problem you're trying to solve
156
+ - Your proposed solution
157
+ - Any alternatives you've considered
158
+
159
+ ---
160
+
161
+ ## Questions
162
+
163
+ For usage questions, check the [Discussions](https://github.com/HKUDS/LightRAG/discussions) tab or open a [Question issue](https://github.com/HKUDS/LightRAG/issues/new?template=question.yml).
.github/ISSUE_TEMPLATE/bug_report.yml ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Bug Report
2
+ description: File a bug report
3
+ title: "[Bug]:"
4
+ labels: ["bug", "triage"]
5
+
6
+ body:
7
+ - type: checkboxes
8
+ id: existingcheck
9
+ attributes:
10
+ label: Do you need to file an issue?
11
+ description: Please help us manage our time by avoiding duplicates and common bugs with the steps below.
12
+ options:
13
+ - label: I have searched the existing issues and this bug is not already filed.
14
+ - label: I believe this is a legitimate bug, not just a question or feature request.
15
+ - type: textarea
16
+ id: description
17
+ attributes:
18
+ label: Describe the bug
19
+ description: A clear and concise description of what the bug is.
20
+ placeholder: What went wrong?
21
+ - type: textarea
22
+ id: reproduce
23
+ attributes:
24
+ label: Steps to reproduce
25
+ description: Steps to reproduce the behavior.
26
+ placeholder: How can we replicate the issue?
27
+ - type: textarea
28
+ id: expected_behavior
29
+ attributes:
30
+ label: Expected Behavior
31
+ description: A clear and concise description of what you expected to happen.
32
+ placeholder: What should have happened?
33
+ - type: textarea
34
+ id: configused
35
+ attributes:
36
+ label: LightRAG Config Used
37
+ description: The LightRAG configuration used for the run.
38
+ placeholder: The settings content or LightRAG configuration
39
+ value: |
40
+ # Paste your config here
41
+ - type: textarea
42
+ id: screenshotslogs
43
+ attributes:
44
+ label: Logs and screenshots
45
+ description: If applicable, add screenshots and logs to help explain your problem.
46
+ placeholder: Add logs and screenshots here
47
+ - type: textarea
48
+ id: additional_information
49
+ attributes:
50
+ label: Additional Information
51
+ description: |
52
+ - LightRAG Version: e.g., v0.1.1
53
+ - Operating System: e.g., Windows 10, Ubuntu 20.04
54
+ - Python Version: e.g., 3.8
55
+ - Related Issues: e.g., #1
56
+ - Any other relevant information.
57
+ value: |
58
+ - LightRAG Version:
59
+ - Operating System:
60
+ - Python Version:
61
+ - Related Issues:
.github/ISSUE_TEMPLATE/config.yml ADDED
@@ -0,0 +1 @@
 
 
1
+ blank_issues_enabled: false
.github/ISSUE_TEMPLATE/feature_request.yml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Feature Request
2
+ description: File a feature request
3
+ labels: ["enhancement"]
4
+ title: "[Feature Request]:"
5
+
6
+ body:
7
+ - type: checkboxes
8
+ id: existingcheck
9
+ attributes:
10
+ label: Do you need to file a feature request?
11
+ description: Please help us manage our time by avoiding duplicates and common feature request with the steps below.
12
+ options:
13
+ - label: I have searched the existing feature request and this feature request is not already filed.
14
+ - label: I believe this is a legitimate feature request, not just a question or bug.
15
+ - type: textarea
16
+ id: feature_request_description
17
+ attributes:
18
+ label: Feature Request Description
19
+ description: A clear and concise description of the feature request you would like.
20
+ placeholder: What this feature request add more or improve?
21
+ - type: textarea
22
+ id: additional_context
23
+ attributes:
24
+ label: Additional Context
25
+ description: Add any other context or screenshots about the feature request here.
26
+ placeholder: Any additional information
.github/ISSUE_TEMPLATE/question.yml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Question
2
+ description: Ask a general question
3
+ labels: ["question"]
4
+ title: "[Question]:"
5
+
6
+ body:
7
+ - type: checkboxes
8
+ id: existingcheck
9
+ attributes:
10
+ label: Do you need to ask a question?
11
+ description: Please help us manage our time by avoiding duplicates and common questions with the steps below.
12
+ options:
13
+ - label: I have searched the existing question and discussions and this question is not already answered.
14
+ - label: I believe this is a legitimate question, not just a bug or feature request.
15
+ - type: textarea
16
+ id: question
17
+ attributes:
18
+ label: Your Question
19
+ description: A clear and concise description of your question.
20
+ placeholder: What is your question?
21
+ - type: textarea
22
+ id: context
23
+ attributes:
24
+ label: Additional Context
25
+ description: Provide any additional context or details that might help us understand your question better.
26
+ placeholder: Add any relevant information here
.github/dependabot.yml ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Keep GitHub Actions up to date with GitHub's Dependabot...
2
+ # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
3
+ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
4
+ version: 2
5
+ updates:
6
+ # ============================================================
7
+ # GitHub Actions
8
+ # PR Strategy:
9
+ # - All updates (major/minor/patch): Grouped into a single PR
10
+ # ============================================================
11
+ - package-ecosystem: github-actions
12
+ directory: /
13
+ groups:
14
+ github-actions:
15
+ patterns:
16
+ - "*" # Group all Actions updates into a single larger pull request
17
+ schedule:
18
+ interval: weekly
19
+ day: monday
20
+ time: "02:00"
21
+ timezone: "Asia/Shanghai"
22
+ labels:
23
+ - "dependencies"
24
+ - "github-actions"
25
+ open-pull-requests-limit: 2
26
+
27
+ # ============================================================
28
+ # Python (pip) Dependencies
29
+ # PR Strategy:
30
+ # - Major updates: Individual PR per package (except numpy which is ignored)
31
+ # - Minor updates: Grouped by category (llm-providers, storage, etc.)
32
+ # - Patch updates: Grouped by category
33
+ # ============================================================
34
+ - package-ecosystem: "pip"
35
+ directory: "/"
36
+ schedule:
37
+ interval: "weekly"
38
+ day: "wednesday"
39
+ time: "02:00"
40
+ timezone: "Asia/Shanghai"
41
+ cooldown:
42
+ default-days: 5
43
+ semver-major-days: 30
44
+ semver-minor-days: 7
45
+ semver-patch-days: 3
46
+ groups:
47
+ # Core dependencies - LLM providers and embeddings
48
+ llm-providers:
49
+ patterns:
50
+ - "openai"
51
+ - "anthropic"
52
+ - "google-*"
53
+ - "boto3"
54
+ - "botocore"
55
+ - "ollama"
56
+ update-types:
57
+ - "minor"
58
+ - "patch"
59
+ # Storage backends
60
+ storage:
61
+ patterns:
62
+ - "neo4j"
63
+ - "pymongo"
64
+ - "redis"
65
+ - "psycopg*"
66
+ - "asyncpg"
67
+ - "milvus*"
68
+ - "qdrant*"
69
+ update-types:
70
+ - "minor"
71
+ - "patch"
72
+ # Data processing and ML
73
+ data-processing:
74
+ patterns:
75
+ - "numpy"
76
+ - "scipy"
77
+ - "pandas"
78
+ - "tiktoken"
79
+ - "transformers"
80
+ - "torch*"
81
+ update-types:
82
+ - "minor"
83
+ - "patch"
84
+ # Web framework and API
85
+ web-framework:
86
+ patterns:
87
+ - "fastapi"
88
+ - "uvicorn"
89
+ - "gunicorn"
90
+ - "starlette"
91
+ - "pydantic*"
92
+ update-types:
93
+ - "minor"
94
+ - "patch"
95
+ # Development and testing tools
96
+ dev-tools:
97
+ patterns:
98
+ - "pytest*"
99
+ - "ruff"
100
+ - "pre-commit"
101
+ - "black"
102
+ - "mypy"
103
+ update-types:
104
+ - "minor"
105
+ - "patch"
106
+ # Minor and patch updates for everything else
107
+ python-minor-patch:
108
+ patterns:
109
+ - "*"
110
+ update-types:
111
+ - "minor"
112
+ - "patch"
113
+ ignore:
114
+ - dependency-name: "numpy"
115
+ update-types:
116
+ - "version-update:semver-major"
117
+ labels:
118
+ - "dependencies"
119
+ - "python"
120
+ open-pull-requests-limit: 5
121
+
122
+ # ============================================================
123
+ # Frontend (bun) Dependencies
124
+ # PR Strategy:
125
+ # - Major updates: Individual PR per package
126
+ # - Minor updates: Grouped by category (react, ui-components, etc.)
127
+ # - Patch updates: Grouped by category
128
+ # ============================================================
129
+ - package-ecosystem: "bun"
130
+ directory: "/lightrag_webui"
131
+ schedule:
132
+ interval: "weekly"
133
+ day: "friday"
134
+ time: "02:00"
135
+ timezone: "Asia/Shanghai"
136
+ cooldown:
137
+ default-days: 5
138
+ semver-major-days: 30
139
+ semver-minor-days: 7
140
+ semver-patch-days: 3
141
+ groups:
142
+ # React ecosystem
143
+ react:
144
+ patterns:
145
+ - "react"
146
+ - "react-dom"
147
+ - "react-router*"
148
+ - "@types/react*"
149
+ update-types:
150
+ - "minor"
151
+ - "patch"
152
+ # UI components and styling
153
+ ui-components:
154
+ patterns:
155
+ - "@radix-ui/*"
156
+ - "tailwind*"
157
+ - "@tailwindcss/*"
158
+ - "lucide-react"
159
+ - "class-variance-authority"
160
+ - "clsx"
161
+ update-types:
162
+ - "minor"
163
+ - "patch"
164
+ # Graph visualization
165
+ graph-viz:
166
+ patterns:
167
+ - "sigma"
168
+ - "@sigma/*"
169
+ - "graphology*"
170
+ update-types:
171
+ - "minor"
172
+ - "patch"
173
+ # Build tools and dev dependencies
174
+ build-tools:
175
+ patterns:
176
+ - "vite"
177
+ - "@vitejs/*"
178
+ - "typescript"
179
+ - "eslint*"
180
+ - "@eslint/*"
181
+ - "typescript-eslint"
182
+ - "prettier"
183
+ - "prettier-*"
184
+ - "@types/bun"
185
+ update-types:
186
+ - "minor"
187
+ - "patch"
188
+ # Content rendering libraries (math, diagrams, etc.)
189
+ content-rendering:
190
+ patterns:
191
+ - "katex"
192
+ - "mermaid"
193
+ update-types:
194
+ - "minor"
195
+ - "patch"
196
+ # All other minor and patch updates
197
+ frontend-minor-patch:
198
+ patterns:
199
+ - "*"
200
+ update-types:
201
+ - "minor"
202
+ - "patch"
203
+ labels:
204
+ - "dependencies"
205
+ - "frontend"
206
+ open-pull-requests-limit: 5
.github/pull_request_template.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!--
2
+ Thanks for contributing to LightRAG!
3
+
4
+ Please ensure your pull request is ready for review before submitting.
5
+
6
+ About this template
7
+
8
+ This template helps contributors provide a clear and concise description of their changes. Feel free to adjust it as needed.
9
+ -->
10
+
11
+ ## Description
12
+
13
+ [Briefly describe the changes made in this pull request.]
14
+
15
+ ## Related Issues
16
+
17
+ [Reference any related issues or tasks addressed by this pull request.]
18
+
19
+ ## Changes Made
20
+
21
+ [List the specific changes made in this pull request.]
22
+
23
+ ## Checklist
24
+
25
+ - [ ] Changes tested locally
26
+ - [ ] Code reviewed
27
+ - [ ] Documentation updated (if necessary)
28
+ - [ ] Unit tests added (if applicable)
29
+
30
+ ## Additional Notes
31
+
32
+ [Add any additional notes or context for the reviewer(s).]
.github/workflows/copilot-setup-steps.yml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "Copilot Setup Steps"
2
+
3
+ # Automatically run the setup steps when they are changed to allow for easy validation, and
4
+ # allow manual testing through the repository's "Actions" tab
5
+ on:
6
+ workflow_dispatch:
7
+ push:
8
+ paths:
9
+ - .github/workflows/copilot-setup-steps.yml
10
+ pull_request:
11
+ paths:
12
+ - .github/workflows/copilot-setup-steps.yml
13
+
14
+ jobs:
15
+ # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
16
+ copilot-setup-steps:
17
+ runs-on: ubuntu-latest
18
+
19
+ # Timeout after 30 minutes (maximum is 59)
20
+ timeout-minutes: 30
21
+
22
+ # You can define any steps you want, and they will run before the agent starts.
23
+ # If you do not check out your code, Copilot will do this for you.
24
+ steps:
25
+ - name: Checkout code
26
+ uses: actions/checkout@v6
27
+
28
+ - name: Set up Python 3.11
29
+ uses: actions/setup-python@v6
30
+ with:
31
+ python-version: '3.11'
32
+
33
+ - name: Cache pip packages
34
+ uses: actions/cache@v5
35
+ with:
36
+ path: ~/.cache/pip
37
+ key: ${{ runner.os }}-pip-copilot-${{ hashFiles('**/pyproject.toml') }}
38
+ restore-keys: |
39
+ ${{ runner.os }}-pip-copilot-
40
+ ${{ runner.os }}-pip-
41
+
42
+ - name: Install Python dependencies
43
+ run: |
44
+ python -m pip install --upgrade pip
45
+ pip install -e ".[api]"
46
+ pip install pytest pytest-asyncio httpx
47
+
48
+ - name: Create minimal frontend stub for Copilot agent
49
+ run: |
50
+ mkdir -p lightrag/api/webui
51
+ echo '<!DOCTYPE html><html><head><title>LightRAG - Copilot Agent</title></head><body><h1>Copilot Agent Mode</h1></body></html>' > lightrag/api/webui/index.html
52
+ echo "Created minimal frontend stub for Copilot agent environment"
53
+
54
+ - name: Verify installation
55
+ run: |
56
+ python --version
57
+ pip list | grep lightrag
58
+ lightrag-server --help || echo "Note: Server requires .env configuration to run"
.github/workflows/docker-build-lite.yml ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Lite Docker Image
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ _notes_:
7
+ description: '⚠️ Create lite Docker images only after non-trivial version releases.'
8
+ required: false
9
+ type: boolean
10
+ default: false
11
+
12
+ permissions:
13
+ contents: read
14
+ packages: write
15
+
16
+ jobs:
17
+ build-and-push-lite:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v6
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.x"
29
+
30
+ - name: Get latest tag
31
+ id: get_tag
32
+ run: |
33
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
34
+ if [ -z "$LATEST_TAG" ]; then
35
+ LATEST_TAG="sha-$(git rev-parse --short HEAD)"
36
+ echo "No tags found, using commit SHA: $LATEST_TAG"
37
+ else
38
+ echo "Latest tag found: $LATEST_TAG"
39
+ fi
40
+ PACKAGE_VERSION="${LATEST_TAG#v}"
41
+ echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
42
+ echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
43
+
44
+ - name: Prepare lite tag
45
+ id: lite_tag
46
+ run: |
47
+ LITE_TAG="${{ steps.get_tag.outputs.tag }}-lite"
48
+ echo "Lite image tag: $LITE_TAG"
49
+ echo "lite_tag=$LITE_TAG" >> $GITHUB_OUTPUT
50
+
51
+ - name: Update version definitions
52
+ run: |
53
+ python scripts/release/set_version.py --core-version "${{ steps.get_tag.outputs.package_version }}"
54
+ grep '__version__ = ' lightrag/_version.py
55
+
56
+ - name: Set up Docker Buildx
57
+ uses: docker/setup-buildx-action@v4
58
+
59
+ - name: Login to GitHub Container Registry
60
+ uses: docker/login-action@v4
61
+ with:
62
+ registry: ghcr.io
63
+ username: ${{ github.actor }}
64
+ password: ${{ secrets.GITHUB_TOKEN }}
65
+
66
+ - name: Extract metadata for Docker
67
+ id: meta
68
+ uses: docker/metadata-action@v6
69
+ with:
70
+ images: ghcr.io/${{ github.repository }}
71
+ tags: |
72
+ type=raw,value=${{ steps.lite_tag.outputs.lite_tag }}
73
+ type=raw,value=lite
74
+
75
+ - name: Build and push lite Docker image
76
+ uses: docker/build-push-action@v7
77
+ with:
78
+ context: .
79
+ file: ./Dockerfile.lite
80
+ platforms: linux/amd64,linux/arm64
81
+ push: true
82
+ tags: ${{ steps.meta.outputs.tags }}
83
+ labels: ${{ steps.meta.outputs.labels }}
84
+ cache-from: type=gha
85
+ cache-to: type=gha,mode=min
86
+
87
+ - name: Output image details
88
+ run: |
89
+ echo "Lite Docker image built and pushed successfully!"
90
+ echo "Image tag: ghcr.io/${{ github.repository }}:${{ steps.lite_tag.outputs.lite_tag }}"
91
+ echo "Base Git tag used: ${{ steps.get_tag.outputs.tag }}"
.github/workflows/docker-build-manual.yml ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Test Docker Image manually
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ _notes_:
7
+ description: '⚠️ Please create a new git tag before building the docker image.'
8
+ required: false
9
+ type: boolean
10
+ default: false
11
+
12
+ permissions:
13
+ contents: read
14
+ packages: write
15
+
16
+ jobs:
17
+ build-and-push:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v6
22
+ with:
23
+ fetch-depth: 0 # Fetch all history for tags
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.x"
29
+
30
+ - name: Get latest tag
31
+ id: get_tag
32
+ run: |
33
+ # Get the latest tag, fallback to commit SHA if no tags exist
34
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
35
+ if [ -z "$LATEST_TAG" ]; then
36
+ LATEST_TAG="sha-$(git rev-parse --short HEAD)"
37
+ echo "No tags found, using commit SHA: $LATEST_TAG"
38
+ else
39
+ echo "Latest tag found: $LATEST_TAG"
40
+ fi
41
+ PACKAGE_VERSION="${LATEST_TAG#v}"
42
+ echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
43
+ echo "image_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
44
+ echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
45
+
46
+ - name: Update version definitions
47
+ run: |
48
+ python scripts/release/set_version.py --core-version "${{ steps.get_tag.outputs.package_version }}"
49
+ echo "Updated version definitions with ${{ steps.get_tag.outputs.package_version }}"
50
+ grep '__version__ = ' lightrag/_version.py
51
+
52
+ - name: Set up Docker Buildx
53
+ uses: docker/setup-buildx-action@v4
54
+
55
+ - name: Login to GitHub Container Registry
56
+ uses: docker/login-action@v4
57
+ with:
58
+ registry: ghcr.io
59
+ username: ${{ github.actor }}
60
+ password: ${{ secrets.GITHUB_TOKEN }}
61
+
62
+ - name: Extract metadata for Docker
63
+ id: meta
64
+ uses: docker/metadata-action@v6
65
+ with:
66
+ images: ghcr.io/${{ github.repository }}
67
+ tags: |
68
+ type=raw,value=${{ steps.get_tag.outputs.tag }}
69
+
70
+ - name: Build and push Docker image
71
+ uses: docker/build-push-action@v7
72
+ with:
73
+ context: .
74
+ file: ./Dockerfile
75
+ platforms: linux/amd64,linux/arm64
76
+ push: true
77
+ tags: ${{ steps.meta.outputs.tags }}
78
+ labels: ${{ steps.meta.outputs.labels }}
79
+ cache-from: type=gha
80
+ cache-to: type=gha,mode=max
81
+
82
+ - name: Output image details
83
+ run: |
84
+ echo "Docker image built and pushed successfully!"
85
+ echo "Image tags:"
86
+ echo " - ghcr.io/${{ github.repository }}:${{ steps.get_tag.outputs.tag }}"
87
+ echo "Latest Git tag used: ${{ steps.get_tag.outputs.tag }}"
.github/workflows/docker-publish.yml ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Latest Docker Image on Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+
12
+ jobs:
13
+ build-and-push:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v6
18
+ with:
19
+ fetch-depth: 0 # Fetch all history for tags
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v6
23
+ with:
24
+ python-version: "3.x"
25
+
26
+ - name: Set up Docker Buildx
27
+ uses: docker/setup-buildx-action@v4
28
+
29
+ - name: Login to GitHub Container Registry
30
+ uses: docker/login-action@v4
31
+ with:
32
+ registry: ghcr.io
33
+ username: ${{ github.actor }}
34
+ password: ${{ secrets.GITHUB_TOKEN }}
35
+
36
+ - name: Get latest tag
37
+ id: get_tag
38
+ run: |
39
+ if [ "${{ github.event_name }}" = "release" ] && [ -n "${{ github.event.release.tag_name }}" ]; then
40
+ TAG="${{ github.event.release.tag_name }}"
41
+ else
42
+ TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
43
+ fi
44
+ if [ -z "$TAG" ]; then
45
+ echo "No git tag found for docker publish"
46
+ exit 1
47
+ fi
48
+ PACKAGE_VERSION="${TAG#v}"
49
+ echo "Found tag: $TAG"
50
+ echo "tag=$TAG" >> $GITHUB_OUTPUT
51
+ echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
52
+
53
+ - name: Check if pre-release
54
+ id: check_prerelease
55
+ run: |
56
+ TAG="${{ steps.get_tag.outputs.tag }}"
57
+ if [[ "$TAG" == *"rc"* ]] || [[ "$TAG" == *"dev"* ]]; then
58
+ echo "is_prerelease=true" >> $GITHUB_OUTPUT
59
+ echo "This is a pre-release version: $TAG"
60
+ else
61
+ echo "is_prerelease=false" >> $GITHUB_OUTPUT
62
+ echo "This is a stable release: $TAG"
63
+ fi
64
+
65
+ - name: Update version definitions
66
+ run: |
67
+ python scripts/release/set_version.py --core-version "${{ steps.get_tag.outputs.package_version }}"
68
+ echo "Updated version definitions with ${{ steps.get_tag.outputs.package_version }}"
69
+ grep '__version__ = ' lightrag/_version.py
70
+
71
+ - name: Extract metadata for Docker
72
+ id: meta
73
+ uses: docker/metadata-action@v6
74
+ with:
75
+ images: ghcr.io/${{ github.repository }}
76
+ tags: |
77
+ type=raw,value=${{ steps.get_tag.outputs.tag }}
78
+ type=raw,value=latest,enable=${{ steps.check_prerelease.outputs.is_prerelease == 'false' }}
79
+
80
+ - name: Build and push Docker image
81
+ uses: docker/build-push-action@v7
82
+ with:
83
+ context: .
84
+ file: ./Dockerfile
85
+ platforms: linux/amd64,linux/arm64
86
+ push: true
87
+ tags: ${{ steps.meta.outputs.tags }}
88
+ labels: ${{ steps.meta.outputs.labels }}
89
+ cache-from: type=gha
90
+ cache-to: type=gha,mode=max
.github/workflows/linting.yaml ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Linting and Formatting
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, dev ]
6
+ pull_request:
7
+ branches: [ main, dev ]
8
+ types: [opened, synchronize, reopened, ready_for_review]
9
+
10
+ permissions:
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ lint-and-format:
15
+ name: Linting and Formatting
16
+ if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
17
+ runs-on: ubuntu-latest
18
+
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v6
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v6
25
+ with:
26
+ python-version: '3.x'
27
+
28
+ - name: Install dependencies
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ pip install pre-commit
32
+
33
+ - name: Run pre-commit
34
+ id: pre-commit
35
+ run: pre-commit run --all-files --show-diff-on-failure
36
+
37
+ - name: Post fix instructions on failure
38
+ if: failure() && steps.pre-commit.outcome == 'failure'
39
+ run: |
40
+ cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
41
+ ## ❌ Linting / Formatting checks failed
42
+
43
+ Pre-commit found issues in your code. Fix them locally and push again:
44
+
45
+ ```bash
46
+ # Install pre-commit (one-time setup)
47
+ pip install pre-commit
48
+ pre-commit install
49
+
50
+ # Auto-fix all issues
51
+ pre-commit run --all-files
52
+
53
+ # Commit the fixes
54
+ git add -u
55
+ git commit -m "fix: apply pre-commit formatting fixes"
56
+ git push
57
+ ```
58
+
59
+ ### What was checked
60
+
61
+ | Hook | Tool | What it fixes |
62
+ |------|------|---------------|
63
+ | `trailing-whitespace` | pre-commit-hooks | Removes trailing whitespace |
64
+ | `end-of-file-fixer` | pre-commit-hooks | Ensures files end with a newline |
65
+ | `requirements-txt-fixer` | pre-commit-hooks | Sorts requirements.txt entries |
66
+ | `ruff-format` | Ruff | Auto-formats Python code (like Black) |
67
+ | `ruff` | Ruff | Fixes Python lint errors (`--fix`) |
68
+
69
+ > See the diff above for the exact changes needed.
70
+ EOF
71
+
72
+ - name: Comment on PR with fix instructions
73
+ if: failure() && steps.pre-commit.outcome == 'failure' && github.event_name == 'pull_request'
74
+ uses: actions/github-script@v8
75
+ with:
76
+ script: |
77
+ const body = `## ❌ Linting / Formatting checks failed
78
+
79
+ Pre-commit found issues in your code. Run the following locally, then push again:
80
+
81
+ \`\`\`bash
82
+ # Install pre-commit (one-time setup)
83
+ pip install pre-commit
84
+ pre-commit install
85
+
86
+ # Auto-fix all issues
87
+ pre-commit run --all-files
88
+
89
+ # Commit the fixes
90
+ git add -u
91
+ git commit -m "fix: apply pre-commit formatting fixes"
92
+ git push
93
+ \`\`\`
94
+
95
+ <details>
96
+ <summary>What was checked</summary>
97
+
98
+ | Hook | Tool | What it fixes |
99
+ |------|------|---------------|
100
+ | \`trailing-whitespace\` | pre-commit-hooks | Removes trailing whitespace |
101
+ | \`end-of-file-fixer\` | pre-commit-hooks | Ensures files end with a newline |
102
+ | \`requirements-txt-fixer\` | pre-commit-hooks | Sorts requirements.txt entries |
103
+ | \`ruff-format\` | Ruff | Auto-formats Python code (like Black) |
104
+ | \`ruff\` | Ruff | Fixes Python lint errors (\`--fix\`) |
105
+
106
+ </details>
107
+
108
+ > See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for the exact diff of required changes.`;
109
+
110
+ // Find existing bot comment to avoid duplicates
111
+ const { data: comments } = await github.rest.issues.listComments({
112
+ owner: context.repo.owner,
113
+ repo: context.repo.repo,
114
+ issue_number: context.issue.number,
115
+ });
116
+ const existing = comments.find(c =>
117
+ c.user.type === 'Bot' && c.body.includes('Linting / Formatting checks failed')
118
+ );
119
+
120
+ if (existing) {
121
+ await github.rest.issues.updateComment({
122
+ owner: context.repo.owner,
123
+ repo: context.repo.repo,
124
+ comment_id: existing.id,
125
+ body,
126
+ });
127
+ } else {
128
+ await github.rest.issues.createComment({
129
+ owner: context.repo.owner,
130
+ repo: context.repo.repo,
131
+ issue_number: context.issue.number,
132
+ body,
133
+ });
134
+ }
.github/workflows/pypi-publish.yml ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Upload LightRAG-hku Package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ release-build:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ with:
18
+ fetch-depth: 0 # Fetch all history for tags
19
+
20
+ # Build frontend WebUI
21
+ - name: Setup Bun
22
+ uses: oven-sh/setup-bun@v2
23
+ with:
24
+ bun-version: latest
25
+
26
+ - name: Build Frontend WebUI
27
+ run: |
28
+ cd lightrag_webui
29
+ bun install --frozen-lockfile
30
+ bun run build
31
+ cd ..
32
+
33
+ - name: Verify Frontend Build
34
+ run: |
35
+ if [ ! -f "lightrag/api/webui/index.html" ]; then
36
+ echo "❌ Error: Frontend build failed - index.html not found"
37
+ exit 1
38
+ fi
39
+ echo "✅ Frontend build verified"
40
+ echo "Frontend files:"
41
+ ls -lh lightrag/api/webui/ | head -10
42
+
43
+ - uses: actions/setup-python@v6
44
+ with:
45
+ python-version: "3.x"
46
+
47
+ - name: Resolve release version
48
+ id: get_version
49
+ run: |
50
+ if [ "${{ github.event_name }}" = "release" ] && [ -n "${{ github.event.release.tag_name }}" ]; then
51
+ TAG="${{ github.event.release.tag_name }}"
52
+ else
53
+ TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
54
+ fi
55
+ if [ -z "$TAG" ]; then
56
+ echo "No git tag found for release build"
57
+ exit 1
58
+ fi
59
+ PACKAGE_VERSION="${TAG#v}"
60
+ echo "Found tag: $TAG"
61
+ echo "Package version: $PACKAGE_VERSION"
62
+ echo "version=$TAG" >> $GITHUB_OUTPUT
63
+ echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
64
+
65
+ - name: Update version definitions
66
+ run: |
67
+ python scripts/release/set_version.py --core-version "${{ steps.get_version.outputs.package_version }}"
68
+ grep '__version__ = ' lightrag/_version.py
69
+
70
+ - name: Build release distributions
71
+ run: |
72
+ python -m pip install build
73
+ python -m build
74
+
75
+ - name: Upload distributions
76
+ uses: actions/upload-artifact@v7
77
+ with:
78
+ name: release-dists
79
+ path: dist/
80
+
81
+ pypi-publish:
82
+ runs-on: ubuntu-latest
83
+ needs:
84
+ - release-build
85
+ permissions:
86
+ id-token: write
87
+
88
+ environment:
89
+ name: pypi
90
+
91
+ steps:
92
+ - name: Retrieve release distributions
93
+ uses: actions/download-artifact@v8
94
+ with:
95
+ name: release-dists
96
+ path: dist/
97
+
98
+ - name: Publish release distributions to PyPI
99
+ uses: pypa/gh-action-pypi-publish@release/v1
100
+ with:
101
+ packages-dir: dist/
.github/workflows/stale.yaml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # .github/workflows/stale.yml
2
+ name: Mark stale issues and pull requests
3
+
4
+ on:
5
+ schedule:
6
+ - cron: '30 22 * * *' # run at 22:30+08 every day
7
+
8
+ permissions:
9
+ issues: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ stale:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/stale@v10
17
+ with:
18
+ days-before-stale: 90 # 90 days
19
+ days-before-close: 7 # 7 days after marked as stale
20
+ stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
21
+ close-issue-message: 'This issue has been automatically closed because it has not had recent activity. Please open a new issue if you still have this problem.'
22
+ stale-pr-message: 'This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
23
+ close-pr-message: 'This pull request has been automatically closed because it has not had recent activity.'
24
+ # If there are specific labels, exempt them from being marked as stale, for example:
25
+ exempt-issue-labels: 'enhancement,tracked'
26
+ # exempt-pr-labels: 'bug,enhancement,help wanted'
27
+ repo-token: ${{ secrets.GITHUB_TOKEN }} # token provided by GitHub
.github/workflows/tests.yml ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Offline Unit Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, dev ]
6
+ pull_request:
7
+ branches: [ main, dev ]
8
+ types: [opened, synchronize, reopened, ready_for_review]
9
+
10
+ jobs:
11
+ offline-tests:
12
+ name: Offline Tests
13
+ if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
14
+ runs-on: ubuntu-latest
15
+
16
+ strategy:
17
+ matrix:
18
+ python-version: ['3.12', '3.14']
19
+
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+
23
+ - name: Set up Python ${{ matrix.python-version }}
24
+ uses: actions/setup-python@v6
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+
28
+ - name: Cache pip packages
29
+ uses: actions/cache@v5
30
+ with:
31
+ path: ~/.cache/pip
32
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt', '**/pyproject.toml') }}
33
+ restore-keys: |
34
+ ${{ runner.os }}-pip-
35
+
36
+ - name: Install dependencies
37
+ run: |
38
+ python -m pip install --upgrade pip
39
+ pip install -e ".[api]"
40
+ pip install pytest pytest-asyncio
41
+
42
+ - name: Run offline tests
43
+ run: |
44
+ # Run only tests marked as 'offline' (no external dependencies)
45
+ # Integration tests requiring databases/APIs are skipped by default
46
+ pytest tests/ -m offline -v --tb=short
47
+
48
+ - name: Upload test results
49
+ if: always()
50
+ uses: actions/upload-artifact@v7
51
+ with:
52
+ name: test-results-py${{ matrix.python-version }}
53
+ path: |
54
+ .pytest_cache/
55
+ test-results.xml
56
+ retention-days: 7
.gitignore ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-related files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ *.tgz
7
+ *.tar.gz
8
+ *.ini
9
+
10
+ # Virtual Environment
11
+ .venv/
12
+ venv/
13
+
14
+ # Enviroment Variable Files
15
+ .env
16
+ .env.backup.*
17
+
18
+ # Generated Docker Compose files (output of setup wizard)
19
+ docker-compose.*.yml
20
+
21
+ # Build / Distribution
22
+ dist/
23
+ build/
24
+ site/
25
+
26
+ # Logs / Reports
27
+ *.log
28
+ *.log.*
29
+ *.logfire
30
+ *.coverage/
31
+ log/
32
+
33
+ # Caches
34
+ .cache/
35
+ .mypy_cache/
36
+ .pytest_cache/
37
+ .ruff_cache/
38
+ .gradio/
39
+ .history/
40
+ temp/
41
+
42
+ # IDE / Editor Files
43
+ .idea/
44
+ .vscode/
45
+ .vscode/settings.json
46
+
47
+ # Framework-specific files
48
+ local_neo4jWorkDir/
49
+ neo4jWorkDir/
50
+
51
+ # Data & Storage
52
+ inputs/
53
+ output/
54
+ rag_storage/
55
+ data/
56
+
57
+ # Evaluation results
58
+ lightrag/evaluation/results/
59
+
60
+ # Miscellaneous
61
+ .DS_Store
62
+ TODO.md
63
+ ignore_this.txt
64
+ *.ignore.*
65
+
66
+ # Project-specific files
67
+ /dickens*/
68
+ /book.txt
69
+ /ag2_demo_workdir/
70
+
71
+ # Frontend build output (built during PyPI release)
72
+ /lightrag/api/webui/
73
+
74
+ # temporary test files in project root
75
+ /test_*
76
+
77
+ # Cline files
78
+ memory-bank
79
+ .claude/CLAUDE.md
80
+ .claude/
81
+
82
+ # Claude Code
83
+ CLAUDE.md
84
+
85
+ # Google Jules
86
+ .jules/
.pre-commit-config.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ exclude: ^lightrag/api/webui/
7
+ - id: end-of-file-fixer
8
+ exclude: ^lightrag/api/webui/
9
+ - id: requirements-txt-fixer
10
+ exclude: ^lightrag/api/webui/
11
+
12
+
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.6.4
15
+ hooks:
16
+ - id: ruff-format
17
+ exclude: ^lightrag/api/webui/
18
+ - id: ruff
19
+ args: [--fix, --ignore=E402]
20
+ exclude: ^lightrag/api/webui/
21
+
22
+
23
+ - repo: https://github.com/mgedmin/check-manifest
24
+ rev: "0.49"
25
+ hooks:
26
+ - id: check-manifest
27
+ stages: [manual]
28
+ exclude: ^lightrag/api/webui/
AGENTS.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Repository Guidelines
2
+
3
+ LightRAG is an advanced Retrieval-Augmented Generation (RAG) framework designed to enhance information retrieval and generation through graph-based knowledge representation.
4
+
5
+ ## Project Structure & Module Organization
6
+ - `lightrag/`: Core Python package with orchestrators (`lightrag/lightrag.py`), storage adapters in `kg/`, LLM bindings in `llm/`, and helpers such as `operate.py` and `utils_*.py`.
7
+ - `lightrag-api/`: FastAPI service (`lightrag_server.py`) with routers under `routers/` and Gunicorn launcher `run_with_gunicorn.py`.
8
+ - `lightrag_webui/`: React 19 + TypeScript client driven by Bun + Vite; UI components live in `src/`.
9
+ - `scripts/setup/`: Interactive environment setup wizard. `setup.sh` orchestrates staged `--base` / `--storage` / `--server` / validation flows, `lib/` holds prompt/validation/file helpers, and `templates/*.yml` contains compose fragments for bundled services.
10
+ - Tests live in `tests/` and root-level `test_*.py`. Working datasets stay in `inputs/`, `rag_storage/`, `temp/`; deployment collateral lives in `docs/`, `k8s-deploy/`, and `docker-compose.yml`.
11
+ - `Makefile`: Canonical entry point for the setup wizard and local developer shortcuts; prefer documented targets over invoking ad hoc shell snippets.
12
+
13
+ ## Build, Test, and Development Commands
14
+ - `python -m venv .venv && source .venv/bin/activate`: set up the Python runtime.
15
+ - `pip install -e .` / `pip install -e .[api]`: install the package and API extras in editable mode.
16
+ - `make env-base`: first-run interactive setup for LLM, embedding, and reranker configuration; writes `.env` and may generate `docker-compose.final.yml`.
17
+ - `make env-storage`, `make env-server`: optional follow-up wizard stages for storage backends and server/security/SSL settings; both reuse the existing `.env`.
18
+ - `make env-validate`, `make env-security-check`, `make env-backup`: validate, audit, or back up the current `.env` via the setup wizard.
19
+ - `lightrag-server` or `uvicorn lightrag.api.lightrag_server:app --reload`: start the API locally; ensure `.env` is present.
20
+ - `python -m pytest tests` (offline markers apply by default) or `python -m pytest tests --run-integration` / `python test_graph_storage.py`: run the full suite, opt into integration coverage, or target an individual script.
21
+ - `ruff check .`: lint Python sources before committing.
22
+ - Front-end workflow uses Bun from `lightrag_webui/`; run UI commands from the repo root as `cd lightrag_webui && bun install`, `cd lightrag_webui && bun run dev`, `cd lightrag_webui && bun run build`, `cd lightrag_webui && bun run lint`, and `cd lightrag_webui && bun test`.
23
+
24
+ ## Coding Style & Naming Conventions
25
+ - Backend code follow PEP 8 with four-space indentation, annotate functions, and reach for dataclasses when modelling state.
26
+ - Use `lightrag.utils.logger` instead of `print`; respect logger configuration flags.
27
+ - Extend storage or pipeline abstractions via `lightrag.base` and keep reusable helpers in the existing `utils_*.py`.
28
+ - Python modules remain lowercase with underscores; React components use `PascalCase.tsx` and hooks-first patterns.
29
+ - Front-end code should remain in TypeScript with two-space indentation, rely on functional React components with hooks, and follow Tailwind utility style.
30
+
31
+ ## Testing Guidelines
32
+ - Keep pytest additions close to the code you touch (`tests/` mirrors feature folders and there are root-level `test_*.py` helpers); functions must start with `test_`.
33
+ - Follow `tests/pytest.ini`: markers include `offline`, `integration`, `requires_db`, and `requires_api`, and the suite runs with `-m "not integration"` by default—pass `--run-integration` (or set `LIGHTRAG_RUN_INTEGRATION=true`) when external services are available.
34
+ - Use the custom CLI toggles from `tests/conftest.py`: `--keep-artifacts`/`LIGHTRAG_KEEP_ARTIFACTS=true`, `--stress-test`/`LIGHTRAG_STRESS_TEST=true`, and `--test-workers N`/`LIGHTRAG_TEST_WORKERS` to dial up workloads or preserve temp files during investigations.
35
+ - Export other required `LIGHTRAG_*` environment variables before running integration or storage tests so adapters can reach configured backends.
36
+ - For UI updates, pair changes with Bun test coverage using `bun:test`; run `cd lightrag_webui && bun test`, and use `cd lightrag_webui && bun test --watch` or `cd lightrag_webui && bun test --coverage` when needed.
37
+
38
+ ## Commit & Pull Request Guidelines
39
+ - Use concise, imperative commit subjects (e.g., `Fix lock key normalization`) and add body context only when necessary.
40
+ - PRs should include a summary, operational impact, linked issues, and screenshots or API samples for user-facing work.
41
+ - Verify `ruff check .`, `python -m pytest`, and affected front-end commands such as `cd lightrag_webui && bun run lint`, `cd lightrag_webui && bun run build`, and `cd lightrag_webui && bun test` succeed before requesting review; note the runs in the PR text.
42
+ - This repo is a fork of `HKUDS/LightRAG`. Always target **`HKUDS/LightRAG:main`** (upstream) when creating PRs, not the fork's own main.
43
+ - Create PR work from a dedicated branch, not `main`. If the CLI sandbox blocks writes under `.git/refs`, request escalation for branch creation or other ref updates instead of retrying blindly.
44
+ - If `gh auth status` is invalid but the GitHub plugin is available, prefer the plugin to create the upstream PR after pushing the branch to the fork; `gh` login state and plugin auth can differ.
45
+ - For `gh` commands that require GitHub network/auth access, prefer running them with escalation from the start instead of first trying the sandboxed path. Use an escalated `gh auth status` check as the source of truth for Codex, not the VSCode terminal. Only abandon the `gh` path when the escalated check still fails; otherwise treat sandbox-only failures as an expected limitation.
46
+ - For lightweight Python validation in fresh shells, prefer `python3` over `python` unless the active environment has already exposed `python`.
47
+
48
+ ## Security & Configuration Tips
49
+ - Copy `.env.example`; never commit secrets or real connection strings.
50
+ - Configure storage backends through `LIGHTRAG_*` variables and validate them with `docker-compose` services when needed.
51
+ - Treat `lightrag.log*` as local artefacts; purge sensitive information before sharing logs or outputs.
52
+
53
+ ## Automation & Agent Workflow
54
+ - Use repo-relative `workdir` arguments for every shell command and prefer `rg`/`rg --files` for searches since they are faster under the CLI harness.
55
+ - Default edits to ASCII, rely on `apply_patch` for single-file changes, and only add concise comments that aid comprehension of complex logic.
56
+ - Honor existing local modifications; never revert or discard user changes (especially via `git reset --hard`) unless explicitly asked.
57
+ - Follow the planning tool guidance: skip it for trivial fixes, but provide multi-step plans for non-trivial work and keep the plan updated as steps progress.
58
+ - Validate changes by running the relevant `ruff`/`pytest`/`bun test` commands whenever feasible, and describe any unrun checks with follow-up guidance.
59
+ - For Codex and other fresh-shell automation, prefer `./scripts/test.sh` instead of bare `pytest`; the script falls back through `PYTHON`, the active virtualenv, `uv`, `.venv`, and `venv` before trying `python` or `python3`.
60
+ - For setup workflow changes, prefer `make env-*` targets over calling `scripts/setup/setup.sh` directly; the `Makefile` resolves a Bash 4+ interpreter for macOS/Linux compatibility.
61
+ - When editing setup logic, keep `.env` host-usable and treat `docker-compose.final.yml` as generated output assembled from `scripts/setup/templates/*.yml`; compose-only overrides belong in the wizard-managed compose layer rather than being persisted back into `.env`.
Dockerfile ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Frontend build stage
4
+ # Build frontend assets on the native build platform to avoid
5
+ # cross-architecture emulation issues during multi-platform builds.
6
+ FROM --platform=$BUILDPLATFORM oven/bun:1 AS frontend-builder
7
+
8
+ WORKDIR /app
9
+
10
+ # Copy frontend source code
11
+ COPY lightrag_webui/ ./lightrag_webui/
12
+
13
+ # Build frontend assets for inclusion in the API package
14
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
15
+ cd lightrag_webui \
16
+ && bun install --frozen-lockfile \
17
+ && bun run build
18
+
19
+ # Python build stage - using uv for faster package installation
20
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
21
+
22
+ ENV DEBIAN_FRONTEND=noninteractive
23
+ ENV UV_SYSTEM_PYTHON=1
24
+ ENV UV_COMPILE_BYTECODE=1
25
+
26
+ WORKDIR /app
27
+
28
+ # Install system deps (Rust is required by some wheels)
29
+ RUN apt-get update \
30
+ && apt-get install -y --no-install-recommends \
31
+ curl \
32
+ build-essential \
33
+ pkg-config \
34
+ && rm -rf /var/lib/apt/lists/* \
35
+ && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
36
+
37
+ ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
38
+
39
+ # Ensure shared data directory exists for uv caches
40
+ RUN mkdir -p /root/.local/share/uv
41
+
42
+ # Copy project metadata and sources
43
+ COPY pyproject.toml .
44
+ COPY setup.py .
45
+ COPY uv.lock .
46
+
47
+ # Install base, API, and offline extras without the project to improve caching
48
+ RUN --mount=type=cache,target=/root/.local/share/uv \
49
+ uv sync --frozen --no-dev --extra api --extra offline --no-install-project --no-editable
50
+
51
+ # Copy project sources after dependency layer
52
+ COPY lightrag/ ./lightrag/
53
+
54
+ # Include pre-built frontend assets from the previous stage
55
+ COPY --from=frontend-builder /app/lightrag/api/webui ./lightrag/api/webui
56
+
57
+ # Sync project in non-editable mode and ensure pip is available for runtime installs
58
+ RUN --mount=type=cache,target=/root/.local/share/uv \
59
+ uv sync --frozen --no-dev --extra api --extra offline --no-editable \
60
+ && /app/.venv/bin/python -m ensurepip --upgrade
61
+
62
+ # Prepare offline cache directory and pre-populate tiktoken data
63
+ # Use uv run to execute commands from the virtual environment
64
+ RUN mkdir -p /app/data/tiktoken \
65
+ && uv run lightrag-download-cache --cache-dir /app/data/tiktoken || status=$?; \
66
+ if [ -n "${status:-}" ] && [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then exit "$status"; fi
67
+
68
+ # Final stage
69
+ FROM python:3.12-slim
70
+
71
+ WORKDIR /app
72
+
73
+ # Install uv for package management
74
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
75
+
76
+ ENV UV_SYSTEM_PYTHON=1
77
+
78
+ # Copy installed packages and application code
79
+ COPY --from=builder /root/.local /root/.local
80
+ COPY --from=builder /app/.venv /app/.venv
81
+ COPY --from=builder /app/lightrag ./lightrag
82
+ COPY pyproject.toml .
83
+ COPY setup.py .
84
+ COPY uv.lock .
85
+
86
+ # Ensure the installed scripts are on PATH
87
+ ENV PATH=/app/.venv/bin:/root/.local/bin:$PATH
88
+
89
+ # Install dependencies with uv sync (uses locked versions from uv.lock)
90
+ # And ensure pip is available for runtime installs
91
+ RUN --mount=type=cache,target=/root/.local/share/uv \
92
+ uv sync --frozen --no-dev --extra api --extra offline --no-editable \
93
+ && /app/.venv/bin/python -m ensurepip --upgrade
94
+
95
+ # Create persistent data directories AFTER package installation
96
+ RUN mkdir -p /app/data/rag_storage /app/data/inputs /app/data/tiktoken
97
+
98
+ # Copy offline cache into the newly created directory
99
+ COPY --from=builder /app/data/tiktoken /app/data/tiktoken
100
+
101
+ # Point to the prepared cache
102
+ ENV TIKTOKEN_CACHE_DIR=/app/data/tiktoken
103
+ ENV WORKING_DIR=/app/data/rag_storage
104
+ ENV INPUT_DIR=/app/data/inputs
105
+
106
+ # Expose API port
107
+ EXPOSE 9621
108
+
109
+ ENTRYPOINT ["python", "-m", "lightrag.api.lightrag_server"]
Dockerfile.lite ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Frontend build stage
4
+ # Build frontend assets on the native build platform to avoid
5
+ # cross-architecture emulation issues during multi-platform builds.
6
+ FROM --platform=$BUILDPLATFORM oven/bun:1 AS frontend-builder
7
+
8
+ WORKDIR /app
9
+
10
+ # Copy frontend source code
11
+ COPY lightrag_webui/ ./lightrag_webui/
12
+
13
+ # Build frontend assets for inclusion in the API package
14
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
15
+ cd lightrag_webui \
16
+ && bun install --frozen-lockfile \
17
+ && bun run build
18
+
19
+ # Python build stage - using uv for package installation
20
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
21
+
22
+ ENV DEBIAN_FRONTEND=noninteractive
23
+ ENV UV_SYSTEM_PYTHON=1
24
+ ENV UV_COMPILE_BYTECODE=1
25
+
26
+ WORKDIR /app
27
+
28
+ # Install system dependencies required by some wheels
29
+ RUN apt-get update \
30
+ && apt-get install -y --no-install-recommends \
31
+ curl \
32
+ build-essential \
33
+ pkg-config \
34
+ && rm -rf /var/lib/apt/lists/* \
35
+ && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
36
+
37
+ ENV PATH="/root/.cargo/bin:/root/.local/bin:${PATH}"
38
+
39
+ # Ensure shared data directory exists for uv caches
40
+ RUN mkdir -p /root/.local/share/uv
41
+
42
+ # Copy project metadata and sources
43
+ COPY pyproject.toml .
44
+ COPY setup.py .
45
+ COPY uv.lock .
46
+
47
+ # Install project dependencies (base + API extras) without the project to improve caching
48
+ RUN --mount=type=cache,target=/root/.local/share/uv \
49
+ uv sync --frozen --no-dev --extra api --no-install-project --no-editable
50
+
51
+ # Copy project sources after dependency layer
52
+ COPY lightrag/ ./lightrag/
53
+
54
+ # Include pre-built frontend assets from the previous stage
55
+ COPY --from=frontend-builder /app/lightrag/api/webui ./lightrag/api/webui
56
+
57
+ # Sync project in non-editable mode and ensure pip is available for runtime installs
58
+ RUN --mount=type=cache,target=/root/.local/share/uv \
59
+ uv sync --frozen --no-dev --extra api --no-editable \
60
+ && /app/.venv/bin/python -m ensurepip --upgrade
61
+
62
+ # Prepare tiktoken cache directory and pre-populate tokenizer data
63
+ # Ignore exit code 2 which indicates assets already cached
64
+ RUN mkdir -p /app/data/tiktoken \
65
+ && uv run lightrag-download-cache --cache-dir /app/data/tiktoken || status=$?; \
66
+ if [ -n "${status:-}" ] && [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then exit "$status"; fi
67
+
68
+ # Final stage
69
+ FROM python:3.12-slim
70
+
71
+ WORKDIR /app
72
+
73
+ # Install uv for package management
74
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
75
+
76
+ ENV UV_SYSTEM_PYTHON=1
77
+
78
+ # Copy installed packages and application code
79
+ COPY --from=builder /root/.local /root/.local
80
+ COPY --from=builder /app/.venv /app/.venv
81
+ COPY --from=builder /app/lightrag ./lightrag
82
+ COPY pyproject.toml .
83
+ COPY setup.py .
84
+ COPY uv.lock .
85
+
86
+ # Ensure the installed scripts are on PATH
87
+ ENV PATH=/app/.venv/bin:/root/.local/bin:$PATH
88
+
89
+ # Sync dependencies inside the final image using uv
90
+ # And ensure pip is available for runtime installs
91
+ RUN --mount=type=cache,target=/root/.local/share/uv \
92
+ uv sync --frozen --no-dev --extra api --no-editable \
93
+ && /app/.venv/bin/python -m ensurepip --upgrade
94
+
95
+ # Create persistent data directories
96
+ RUN mkdir -p /app/data/rag_storage /app/data/inputs /app/data/tiktoken
97
+
98
+ # Copy cached tokenizer assets prepared in the builder stage
99
+ COPY --from=builder /app/data/tiktoken /app/data/tiktoken
100
+
101
+ # Docker data directories
102
+ ENV TIKTOKEN_CACHE_DIR=/app/data/tiktoken
103
+ ENV WORKING_DIR=/app/data/rag_storage
104
+ ENV INPUT_DIR=/app/data/inputs
105
+
106
+ # Expose API port
107
+ EXPOSE 9621
108
+
109
+ # Set entrypoint
110
+ ENTRYPOINT ["python", "-m", "lightrag.api.lightrag_server"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LightRAG Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
MANIFEST.in ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ include requirements.txt
2
+ include lightrag/api/requirements.txt
3
+ recursive-include lightrag/api/webui *
4
+ recursive-include lightrag/api/static *
Makefile ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SHELL := /bin/bash
2
+ SETUP_SCRIPT := scripts/setup/setup.sh
3
+ SETUP_BASH ?= $(or $(firstword $(wildcard /opt/homebrew/bin/bash /usr/local/bin/bash /opt/local/bin/bash)),$(shell command -v bash 2>/dev/null),bash)
4
+ SETUP_OPTS ?=
5
+ COLOR_RESET := \033[0m
6
+ COLOR_BOLD := \033[1m
7
+ COLOR_BLUE := \033[34m
8
+ COLOR_GREEN := \033[32m
9
+ COLOR_YELLOW := \033[33m
10
+
11
+ ifeq ($(NO_COLOR),1)
12
+ COLOR_RESET :=
13
+ COLOR_BOLD :=
14
+ COLOR_BLUE :=
15
+ COLOR_GREEN :=
16
+ COLOR_YELLOW :=
17
+ endif
18
+
19
+ .PHONY: help dev configure env-base env-storage env-server env-validate env-backup env-security-check env-base-rewrite env-storage-rewrite env base storage server validate backup security security-check base-rewrite storage-rewrite
20
+
21
+ help:
22
+ @printf "$(COLOR_BOLD)Interactive setup targets$(COLOR_RESET)\n"
23
+ @printf " $(COLOR_GREEN)make dev$(COLOR_RESET) Bootstrap local dev+test+offline env with uv + bun\n"
24
+ @printf " $(COLOR_GREEN)make env-base$(COLOR_RESET) Configure LLM, embedding, and reranker (run first)\n"
25
+ @printf " $(COLOR_GREEN)make env-storage$(COLOR_RESET) Configure storage backends and databases\n"
26
+ @printf " $(COLOR_GREEN)make env-server$(COLOR_RESET) Configure server, security, and SSL\n"
27
+ @printf " $(COLOR_GREEN)make env-validate$(COLOR_RESET) Validate existing .env\n"
28
+ @printf " $(COLOR_GREEN)make env-security-check$(COLOR_RESET) Audit existing .env for security risks\n"
29
+ @printf " $(COLOR_GREEN)make env-backup$(COLOR_RESET) Backup current .env\n"
30
+ @printf " $(COLOR_GREEN)make env-base-rewrite$(COLOR_RESET) Force-regenerate wizard-managed compose services during base setup\n"
31
+ @printf " $(COLOR_GREEN)make env-storage-rewrite$(COLOR_RESET) Force-regenerate wizard-managed compose services during storage setup\n"
32
+ @printf " $(COLOR_GREEN)make base$(COLOR_RESET) Short form of make env-base (all env prefix can be stripped)\n"
33
+ @printf "\n"
34
+ @printf "$(COLOR_BOLD)Typical workflow$(COLOR_RESET)\n"
35
+ @printf " 1. make dev # install backend/test deps and build frontend\n"
36
+ @printf " 2. make env-base # set LLM/embedding/reranker\n"
37
+ @printf " 3. make env-storage # set storage backends (optional)\n"
38
+ @printf " 4. make env-server # set port/security/SSL (optional)\n\n"
39
+ @printf "$(COLOR_BOLD)Examples$(COLOR_RESET)\n"
40
+ @printf " make dev\n"
41
+ @printf " make env-base\n"
42
+ @printf " make env-storage SETUP_OPTS=--debug\n"
43
+ @printf " make env-server\n\n"
44
+ @printf " make env-storage-rewrite\n\n"
45
+ @printf " make env-security-check\n\n"
46
+ @printf "$(COLOR_BOLD)Compose Output$(COLOR_RESET)\n"
47
+ @printf " Bundled service images are defined in scripts/setup/templates/*.yml.\n"
48
+ @printf " Compose file output: docker-compose.final.yml\n"
49
+
50
+ dev:
51
+ @if ! command -v uv >/dev/null 2>&1; then \
52
+ printf "$(COLOR_YELLOW)uv is required for make dev.$(COLOR_RESET)\n"; \
53
+ printf "Install uv first: https://docs.astral.sh/uv/getting-started/installation/\n"; \
54
+ printf "Unix/macOS: curl -LsSf https://astral.sh/uv/install.sh | sh\n"; \
55
+ printf "Windows: powershell -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n"; \
56
+ exit 1; \
57
+ fi
58
+ @if ! command -v bun >/dev/null 2>&1; then \
59
+ printf "$(COLOR_YELLOW)bun is required for make dev.$(COLOR_RESET)\n"; \
60
+ printf "Install Bun first: https://bun.sh/docs/installation\n"; \
61
+ printf "macOS/Linux: curl -fsSL https://bun.sh/install | bash\n"; \
62
+ printf "Windows: powershell -c \"irm bun.sh/install.ps1 | iex\"\n"; \
63
+ exit 1; \
64
+ fi
65
+ @printf "$(COLOR_BLUE)Syncing backend and test dependencies with uv...$(COLOR_RESET)\n"
66
+ @uv sync --extra test --extra offline
67
+ @printf "$(COLOR_BLUE)Installing frontend dependencies with Bun...$(COLOR_RESET)\n"
68
+ @cd lightrag_webui && bun install --frozen-lockfile
69
+ @printf "$(COLOR_BLUE)Building frontend assets...$(COLOR_RESET)\n"
70
+ @cd lightrag_webui && bun run build
71
+ @printf "$(COLOR_GREEN)Development environment is ready.$(COLOR_RESET)\n"
72
+ @printf "Next steps:\n"
73
+ @printf " source .venv/bin/activate\n"
74
+ @printf " make env-base\n"
75
+ @printf " lightrag-server\n"
76
+
77
+ env-base env base configure:
78
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --base $(SETUP_OPTS)
79
+
80
+ env-storage storage:
81
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --storage $(SETUP_OPTS)
82
+
83
+ env-base-rewrite base-rewrite:
84
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --base --rewrite-compose $(SETUP_OPTS)
85
+
86
+ env-storage-rewrite storage-rewrite:
87
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --storage --rewrite-compose $(SETUP_OPTS)
88
+
89
+ env-server server:
90
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --server $(SETUP_OPTS)
91
+
92
+ env-validate validate:
93
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --validate $(SETUP_OPTS)
94
+
95
+ env-security-check security security-check:
96
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --security-check $(SETUP_OPTS)
97
+
98
+ env-backup backup:
99
+ @$(SETUP_BASH) $(SETUP_SCRIPT) --backup $(SETUP_OPTS)
README-zh.md ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+
3
+ <div style="margin: 20px 0;">
4
+ <img src="./assets/logo.png" width="120" height="120" alt="LightRAG Logo" style="border-radius: 20px; box-shadow: 0 8px 32px rgba(0, 217, 255, 0.3);">
5
+ </div>
6
+
7
+ # 🚀 LightRAG: 简单且快速的检索增强生成(RAG)框架
8
+
9
+ <div align="center">
10
+ <a href="https://trendshift.io/repositories/13043" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13043" alt="HKUDS%2FLightRAG | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
11
+ </div>
12
+
13
+ <div align="center">
14
+ <div style="width: 100%; height: 2px; margin: 20px 0; background: linear-gradient(90deg, transparent, #00d9ff, transparent);"></div>
15
+ </div>
16
+
17
+ <div align="center">
18
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; padding: 25px; text-align: center;">
19
+ <p>
20
+ <a href='https://github.com/HKUDS/LightRAG'><img src='https://img.shields.io/badge/🔥项目-主页-00d9ff?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a2e'></a>
21
+ <a href='https://arxiv.org/abs/2410.05779'><img src='https://img.shields.io/badge/📄arXiv-2410.05779-ff6b6b?style=for-the-badge&logo=arxiv&logoColor=white&labelColor=1a1a2e'></a>
22
+ <a href="https://github.com/HKUDS/LightRAG/stargazers"><img src='https://img.shields.io/github/stars/HKUDS/LightRAG?color=00d9ff&style=for-the-badge&logo=star&logoColor=white&labelColor=1a1a2e' /></a>
23
+ </p>
24
+ <p>
25
+ <img src="https://img.shields.io/badge/🐍Python-3.10-4ecdc4?style=for-the-badge&logo=python&logoColor=white&labelColor=1a1a2e">
26
+ <a href="https://pypi.org/project/lightrag-hku/"><img src="https://img.shields.io/pypi/v/lightrag-hku.svg?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=ff6b6b"></a>
27
+ </p>
28
+ <p>
29
+ <a href="https://discord.gg/yF2MmDJyGJ"><img src="https://img.shields.io/badge/💬Discord-社区-7289da?style=for-the-badge&logo=discord&logoColor=white&labelColor=1a1a2e"></a>
30
+ <a href="https://github.com/HKUDS/LightRAG/issues/285"><img src="https://img.shields.io/badge/💬微信群-交流-07c160?style=for-the-badge&logo=wechat&logoColor=white&labelColor=1a1a2e"></a>
31
+ </p>
32
+ <p>
33
+ <a href="README-zh.md"><img src="https://img.shields.io/badge/🇨🇳中文版-1a1a2e?style=for-the-badge"></a>
34
+ <a href="README.md"><img src="https://img.shields.io/badge/🇺🇸English-1a1a2e?style=for-the-badge"></a>
35
+ </p>
36
+ <p>
37
+ <a href="https://pepy.tech/projects/lightrag-hku"><img src="https://static.pepy.tech/personalized-badge/lightrag-hku?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads"></a>
38
+ </p>
39
+ </div>
40
+ </div>
41
+
42
+ </div>
43
+
44
+ <div align="center" style="margin: 30px 0;">
45
+ <img src="https://user-images.githubusercontent.com/74038190/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif" width="800">
46
+ </div>
47
+
48
+ <div align="center" style="margin: 30px 0;">
49
+ <img src="./README.assets/b2aaf634151b4706892693ffb43d9093.png" width="800" alt="LightRAG Diagram">
50
+ </div>
51
+
52
+ ---
53
+
54
+ <div align="center">
55
+ <table>
56
+ <tr>
57
+ <td style="vertical-align: middle;">
58
+ <img src="./assets/LiteWrite.png"
59
+ width="56"
60
+ height="56"
61
+ alt="LiteWrite"
62
+ style="border-radius: 12px;" />
63
+ </td>
64
+ <td style="vertical-align: middle; padding-left: 12px;">
65
+ <a href="https://litewrite.ai">
66
+ <img src="https://img.shields.io/badge/🚀%20LiteWrite-AI%20原生%20LaTeX%20编辑器-ff6b6b?style=for-the-badge&logoColor=white&labelColor=1a1a2e">
67
+ </a>
68
+ </td>
69
+ </tr>
70
+ </table>
71
+ </div>
72
+
73
+ ---
74
+
75
+ ## 🎉 新闻
76
+ - [2026.03]🎯[新功能]: 集成了 **OpenSearch** 作为统一存储后端,为 LightRAG 的全部四种存储类型提供全面支持。
77
+ - [2026.03]🎯[新功能]: 推出交互式安装向导,支持通过 Docker 在本地部署 Embedding、Reranking 及存储后端服务。
78
+ - [2025.11]🎯[新功能]: 集成了 **RAGAS 评估**和 **Langfuse 追踪**。更新了 API 以在查询结果中返回召回上下文,支持上下文精度指标。
79
+ - [2025.10]🎯[可扩展性增强]: 消除了处理瓶颈,以高效支持**大规模数据集**。
80
+ - [2025.09]🎯[新功能]: 显著提升了 Qwen3-30B-A3B 等**开源 LLM** 的知识图谱提取准确性。
81
+ - [2025.08]🎯[新功能]: 现已支持 **Reranker**,显著提升混合查询性能(已设为默认查询模式)。
82
+ - [2025.08]🎯[新功能]: 添加了**文档删除**功能,并支持自动重新生成知识图谱,以确保最佳查询性能。
83
+ - [2025.06]🎯[新发布]: 我们的团队发布了 [RAG-Anything](https://github.com/HKUDS/RAG-Anything) —— 一个用于无缝处理文本、图像、表格和方程式的**全功能多模态 RAG** 系统。
84
+ - [2025.06]🎯[新功能]: LightRAG 现已集成 [RAG-Anything](https://github.com/HKUDS/RAG-Anything),支持全面的多模态数据处理,实现对 PDF、图像、Office 文档、表格和公式等多种���式的无缝文档解析和 RAG 能力。详见[多模态文档处理部分](https://github.com/HKUDS/LightRAG/?tab=readme-ov-file#multimodal-document-processing-rag-anything-integration)。
85
+ - [2025.03]🎯[新功能]: LightRAG 现已支持引用功能,实现了准确的源归因和增强的文档可追溯性。
86
+ - [2025.02]🎯[新功能]: 现在您可以使用 MongoDB 作为一体化存储解决方案,实现统一的数据管理。
87
+ - [2025.02]🎯[新发布]: 我们的团队发布了 [VideoRAG](https://github.com/HKUDS/VideoRAG) —— 一个用于理解超长上下文视频的 RAG 系统。
88
+ - [2025.01]🎯[新发布]: 我们的团队发布了 [MiniRAG](https://github.com/HKUDS/MiniRAG),使用小型模型简化 RAG。
89
+ - [2025.01]🎯现在您可以使用 PostgreSQL 作为一体化存储解决方案进行数据管理。
90
+ - [2024.11]🎯[新资源]: LightRAG 的综合指南现已在 [LearnOpenCV](https://learnopencv.com/lightrag) 上发布 —— 探索深入的教程和最佳实践。非常感谢博客作者的杰出贡献!
91
+ - [2024.11]🎯[新功能]: 推出 LightRAG WebUI —— 一个允许您通过直观的 Web 界面插入、查询和可视化 LightRAG 知识的仪表板。
92
+ - [2024.11]🎯[新功能]: 现在您可以[使用 Neo4J 进行存储](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#using-neo4j-for-storage) —— 开启图数据库支持。
93
+ - [2024.10]🎯[新功能]: 我们添加了 [LightRAG 介绍视频](https://youtu.be/oageL-1I0GE) 的链接 —— 演示 LightRAG 的各项功能。感谢作者的杰出贡献!
94
+ - [2024.10]🎯[新频道]: 我们创建了一个 [Discord 频道](https://discord.gg/yF2MmDJyGJ)!💬 欢迎加入我们的社区进行分享、讨论和协作! 🎉🎉
95
+
96
+ <details>
97
+ <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
98
+ 算法流程图
99
+ </summary>
100
+
101
+ ![LightRAG索引流程图](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-VectorDB-Json-KV-Store-Indexing-Flowchart-scaled.jpg)
102
+ *图1:LightRAG索引流程图 - 图片来源:[Source](https://learnopencv.com/lightrag/)*
103
+ ![LightRAG检索和查询流程图](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-Querying-Flowchart-Dual-Level-Retrieval-Generation-Knowledge-Graphs-scaled.jpg)
104
+ *图2:LightRAG检索和查询流程图 - 图片来源:[Source](https://learnopencv.com/lightrag/)*
105
+
106
+ </details>
107
+
108
+ ## 安装
109
+
110
+ **💡 使用 uv 进行包管理**: 本项目使用 [uv](https://docs.astral.sh/uv/) 进行快速可靠的 Python 包管理。首先安装 uv: `curl -LsSf https://astral.sh/uv/install.sh | sh` (Unix/macOS) 或 `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"` (Windows)
111
+
112
+ > **注意**:如果您愿意,也可以使用 pip,但为了获得更好的性能 and 更可靠的依赖管理,建议使用 uv。
113
+ >
114
+ > **📦 离线部署**: 对于离线或隔离环境,请参阅[离线部署指南](./docs/OfflineDeployment.md),了解预安装所有依赖项和缓存文件的说明。
115
+
116
+ ### 安装LightRAG服务器
117
+
118
+ LightRAG服务器旨在提供Web UI和API支持。Web UI便于文档索引、知识图谱探索和简单的RAG查询界面。LightRAG服务器还提供兼容Ollama的接口,旨在将LightRAG模拟为Ollama聊天模型。这使得AI聊天机器人(如Open WebUI)可以轻松访问LightRAG。
119
+
120
+ * 从PyPI安装
121
+
122
+ ```bash
123
+ ### 使用 uv 安装 LightRAG 服务器(作为工具,推荐)
124
+ uv tool install "lightrag-hku[api]"
125
+
126
+ ### 或使用 pip
127
+ # python -m venv .venv
128
+ # source .venv/bin/activate # Windows: .venv\Scripts\activate
129
+ # pip install "lightrag-hku[api]"
130
+
131
+ ### 构建前端代码
132
+ cd lightrag_webui
133
+ bun install --frozen-lockfile
134
+ bun run build
135
+ cd ..
136
+
137
+ # 配置 env 文件
138
+ # 从 GitHub 仓库的根目录上下载 env.example 文件
139
+ # 或从本地检出的源代码中获取 env.example 文件
140
+ cp env.example .env # 使用你的LLM和Embedding模型访问参数更新.env文件
141
+ # 启动API-WebUI服务
142
+ lightrag-server
143
+ ```
144
+
145
+ * 从源代码安装
146
+
147
+ ```bash
148
+ git clone https://github.com/HKUDS/LightRAG.git
149
+ cd LightRAG
150
+
151
+ # 一键初始化开发环境(推荐)
152
+ make dev
153
+ source .venv/bin/activate # 激活虚拟环境 (Linux/macOS)
154
+ # Windows 系统: .venv\Scripts\activate
155
+
156
+ # make dev 会安装测试工具链以及完整的离线依赖栈
157
+ # (API、存储后端与各类 Provider 集成),并构建前端;不会生成 .env。
158
+ # 启动服务前请先运行 make env-base,或手动从 env.example 复制并配置 .env。
159
+
160
+ # 使用 uv 的等价手动步骤
161
+ # 注意: uv sync 会自动在 .venv/ 目录创建虚拟环境
162
+ uv sync --extra test --extra offline
163
+ source .venv/bin/activate # 激活虚拟环境 (Linux/macOS)
164
+ # Windows 系统: .venv\Scripts\activate
165
+
166
+ ### 或使用 pip 和虚拟环境
167
+ # python -m venv .venv
168
+ # source .venv/bin/activate # Windows: .venv\Scripts\activate
169
+ # pip install -e ".[test,offline]"
170
+
171
+ # 构建前端代码
172
+ cd lightrag_webui
173
+ bun install --frozen-lockfile
174
+ bun run build
175
+ cd ..
176
+
177
+ # 配置 env 文件
178
+ make env-base # 或: cp env.example .env 后手动修改
179
+ # 启动API-WebUI服务
180
+ lightrag-server
181
+ ```
182
+
183
+ * 使用 Docker Compose 启动 LightRAG 服务器
184
+
185
+ ```bash
186
+ git clone https://github.com/HKUDS/LightRAG.git
187
+ cd LightRAG
188
+ cp env.example .env # 使用你的LLM和Embedding模型访问参数更新.env文件
189
+ # modify LLM and Embedding settings in .env
190
+ docker compose up
191
+ ```
192
+
193
+ > 在此获取LightRAG docker镜像历史版本: [LightRAG Docker Images]( https://github.com/HKUDS/LightRAG/pkgs/container/lightrag)
194
+
195
+ ### 使用 Setup 工具创建 .env 文件
196
+
197
+ 除了手动编辑 `env.example` 之外,您还可以使用交互式向导生成配置好的 `.env`,并在需要时生成 `docker-compose.final.yml`:
198
+
199
+ ```bash
200
+ make env-base # 必跑第一步:配置 LLM、Embedding、Reranker
201
+ make env-storage # 可选:配置存储后端和数据库服务
202
+ make env-server # 可选:配置服务端口、鉴权和 SSL
203
+ make env-base-rewrite # 可选:强制重建向导托管的 compose 服务块
204
+ make env-storage-rewrite # 可选:强制重建向导托管的 compose 服务块
205
+ make env-security-check # 可选:审计当前 .env 中的安全风险
206
+ ```
207
+
208
+ 每个目标的详细说明请参阅 [docs/InteractiveSetup.md](./docs/InteractiveSetup.md)。
209
+ 这些 setup 向导只负责更新配置;如需在部署前审计当前 `.env` 的安全风险,请额外运行
210
+ `make env-security-check`。
211
+ 默认情况下,重新运行 setup 会保留未变化的向导托管 compose 服务块;只有在需要按模板强制重建这些托管块时,才使用
212
+ `*-rewrite` 目标。
213
+
214
+ ### 安装LightRAG Core
215
+
216
+ * 从源代码安装(推荐)
217
+
218
+ ```bash
219
+ cd LightRAG
220
+ # 注意: uv sync 会自动在 .venv/ 目录创建虚拟环境
221
+ uv sync
222
+ source .venv/bin/activate # 激活虚拟环境 (Linux/macOS)
223
+ # Windows 系统: .venv\Scripts\activate
224
+
225
+ # 或: pip install -e .
226
+ ```
227
+
228
+ * 从PyPI安装
229
+
230
+ ```bash
231
+ uv pip install lightrag-hku
232
+ # 或: pip install lightrag-hku
233
+ ```
234
+
235
+ ## 快速开始
236
+
237
+ ### LightRAG的LLM及配套技术栈要求
238
+
239
+ LightRAG对大型语言模型(LLM)的能力要求远高于传统RAG,因为它需要LLM执行文档中的实体关系抽取任务。配置合适的Embedding和Reranker模型对提高查询表现也至关重要。
240
+
241
+ - **LLM选型**:
242
+ - 推荐选用参数量至少为32B的LLM。
243
+ - 上下文长度至少为32KB,推荐达到64KB。
244
+ - 在文档索引阶段不建议选择推理模型。
245
+ - 在查询阶段建议选择比索引阶段能力更强的模型,以达到更高的查询效果。
246
+ - **Embedding模型**:
247
+ - 高性能的Embedding模型对RAG至关重要。
248
+ - 推荐使用主流的多语言Embedding模型,例如:BAAI/bge-m3 和 text-embedding-3-large。
249
+ - **重要提示**:在文档索引前必须确定使用的Embedding模型,且在文档查询阶段必须沿用与索引阶段相同的模型。有些存储(例如PostgreSQL)在首次建立数表的时候需要确定向量维度,因此更换Embedding模型后需要删除向量相关库表,以便让LightRAG重建新的库表。
250
+ - **Reranker模型配置**:
251
+ - 配置Reranker模型能够显著提升LightRAG的检索效果。
252
+ - 启用Reranker模型后,推荐将“mix模式”设为默认查询模式。
253
+ - 推荐选用主流的Reranker模型,例如:BAAI/bge-reranker-v2-m3 或 Jina 等服务商提供的模型。
254
+
255
+ ### 使用LightRAG服务器
256
+
257
+ LightRAG 服务器旨在提供 Web UI 和 API 支持,同时提供了全面的知识图谱可视化功能,支持各种重力布局、节点查询、子图过滤等。有关LightRAG服务器的更多信息,请参阅[LightRAG服务器](./docs/LightRAG-API-Server-zh.md)。
258
+
259
+ ![iShot_2025-03-23_12.40.08](./README.assets/iShot_2025-03-23_12.40.08.png)
260
+
261
+
262
+ ### 使用LightRAG Core
263
+
264
+ LightRAG核心功能的示例代码请参见`examples`目录。您还可参照[视频](https://www.youtube.com/watch?v=g21royNJ4fw)视频完成环境配置。若已持有OpenAI API密钥,可以通过以下命令运行演示代码:
265
+
266
+ ```bash
267
+ ### you should run the demo code with project folder
268
+ cd LightRAG
269
+ ### provide your API-KEY for OpenAI
270
+ export OPENAI_API_KEY="sk-...your_opeai_key..."
271
+ ### download the demo document of "A Christmas Carol" by Charles Dickens
272
+ curl https://raw.githubusercontent.com/gusye1234/nano-graphrag/main/tests/mock_data.txt > ./book.txt
273
+ ### run the demo code
274
+ python examples/lightrag_openai_demo.py
275
+ ```
276
+
277
+ 如需流式响应示例的实现代码,请参阅 `examples/lightrag_openai_compatible_demo.py`。运行前,请确保根据需求修改示例代码中的LLM及嵌入模型配置。
278
+
279
+ **注意1**:在运行demo程序的时候需要注意,不同的测试程序可能使用的是不同的embedding模型,更换不同的embeding模型的时候需要把清空数据目录(`./dickens`),否则层序执行会出错。如果你想保留LLM缓存,可以在清除数据目录时保留`kv_store_llm_response_cache.json`文件。
280
+
281
+ **注意2**:官方支持的示例代码仅为 `lightrag_openai_demo.py` 和 `lightrag_openai_compatible_demo.py` 两个文件。其他���例文件均为社区贡献内容,尚未经过完整测试与优化。
282
+
283
+ ## 使用LightRAG Core进行编程
284
+
285
+ 完整的 Core API 参考 —— 包括初始化参数、`QueryParam`、各 LLM/Embedding 接入示例(OpenAI、Ollama、Azure、Gemini、HuggingFace、LlamaIndex)、Rerank 注入、插入操作、实体/关系管理、删除与合并 —— 详见 **[docs/ProgramingWithCore.md](./docs/ProgramingWithCore.md)**(英文)。
286
+
287
+ > ⚠️ **如果您希望将LightRAG集成到您的项目中,建议您使用LightRAG Server提供的REST API**。LightRAG Core通常用于嵌入式应用,或供希望进行研究与评估的学者使用。
288
+
289
+ ### 高级功能
290
+
291
+ LightRAG 提供 Token 用量追踪、知识图谱数据导出、LLM 缓存管理、Langfuse 可观测性集成和基于 RAGAS 的评估框架。详见 **[docs/AdvancedFeatures.md](./docs/AdvancedFeatures.md)**(英文)。
292
+
293
+ ### 多模态文档处理(RAG-Anything 集成)
294
+
295
+ LightRAG 与 [RAG-Anything](https://github.com/HKUDS/RAG-Anything) 集成,支持对 PDF、Office 文档、图像、表格和公式的端到端多模态 RAG。详见 **[docs/AdvancedFeatures.md](./docs/AdvancedFeatures.md)**(英文)。
296
+
297
+ > LightRAG Server 将会在不久的将来把 RAG-Anything 的多模态处理能力整合到其文件件处理流水线中。敬请期待。
298
+
299
+ ## 重现论文结果
300
+
301
+ LightRAG 在农业、计算机科学、法律和混合等领域均显著优于 NaiveRAG、RQ-RAG、HyDE 和 GraphRAG。完整评估方法论、提示词和复现步骤详见 **[docs/Reproduce.md](./docs/Reproduce.md)**(英文)。
302
+
303
+ ### 总体性能表
304
+
305
+ ||**农业**||**计算机科学**||**法律**||**混合**||
306
+ |----------------------|---------------|------------|------|------------|---------|------------|-------|------------|
307
+ ||NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|
308
+ |**全面性**|32.4%|**67.6%**|38.4%|**61.6%**|16.4%|**83.6%**|38.8%|**61.2%**|
309
+ |**多样性**|23.6%|**76.4%**|38.0%|**62.0%**|13.6%|**86.4%**|32.4%|**67.6%**|
310
+ |**赋能性**|32.4%|**67.6%**|38.8%|**61.2%**|16.4%|**83.6%**|42.8%|**57.2%**|
311
+ |**总体**|32.4%|**67.6%**|38.8%|**61.2%**|15.2%|**84.8%**|40.0%|**60.0%**|
312
+ ||RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|
313
+ |**全面性**|31.6%|**68.4%**|38.8%|**61.2%**|15.2%|**84.8%**|39.2%|**60.8%**|
314
+ |**多样性**|29.2%|**70.8%**|39.2%|**60.8%**|11.6%|**88.4%**|30.8%|**69.2%**|
315
+ |**赋能性**|31.6%|**68.4%**|36.4%|**63.6%**|15.2%|**84.8%**|42.4%|**57.6%**|
316
+ |**总体**|32.4%|**67.6%**|38.0%|**62.0%**|14.4%|**85.6%**|40.0%|**60.0%**|
317
+ ||HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|
318
+ |**全面性**|26.0%|**74.0%**|41.6%|**58.4%**|26.8%|**73.2%**|40.4%|**59.6%**|
319
+ |**多样性**|24.0%|**76.0%**|38.8%|**61.2%**|20.0%|**80.0%**|32.4%|**67.6%**|
320
+ |**赋能性**|25.2%|**74.8%**|40.8%|**59.2%**|26.0%|**74.0%**|46.0%|**54.0%**|
321
+ |**总体**|24.8%|**75.2%**|41.6%|**58.4%**|26.4%|**73.6%**|42.4%|**57.6%**|
322
+ ||GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|
323
+ |**全面性**|45.6%|**54.4%**|48.4%|**51.6%**|48.4%|**51.6%**|**50.4%**|49.6%|
324
+ |**多样性**|22.8%|**77.2%**|40.8%|**59.2%**|26.4%|**73.6%**|36.0%|**64.0%**|
325
+ |**赋能性**|41.2%|**58.8%**|45.2%|**54.8%**|43.6%|**56.4%**|**50.8%**|49.2%|
326
+ |**总体**|45.2%|**54.8%**|48.0%|**52.0%**|47.2%|**52.8%**|**50.4%**|49.6%|
327
+
328
+
329
+ ## 🔗 相关项目
330
+
331
+ *生态与扩展*
332
+
333
+ <div align="center">
334
+ <table>
335
+ <tr>
336
+ <td align="center">
337
+ <a href="https://github.com/HKUDS/RAG-Anything">
338
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
339
+ <span style="font-size: 32px;">📸</span>
340
+ </div>
341
+ <b>RAG-Anything</b><br>
342
+ <sub>多模态 RAG</sub>
343
+ </a>
344
+ </td>
345
+ <td align="center">
346
+ <a href="https://github.com/HKUDS/VideoRAG">
347
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
348
+ <span style="font-size: 32px;">🎥</span>
349
+ </div>
350
+ <b>VideoRAG</b><br>
351
+ <sub>极端长上下文视频 RAG</sub>
352
+ </a>
353
+ </td>
354
+ <td align="center">
355
+ <a href="https://github.com/HKUDS/MiniRAG">
356
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
357
+ <span style="font-size: 32px;">✨</span>
358
+ </div>
359
+ <b>MiniRAG</b><br>
360
+ <sub>极简 RAG</sub>
361
+ </a>
362
+ </td>
363
+ </tr>
364
+ </table>
365
+ </div>
366
+
367
+ ---
368
+
369
+ ## ⭐ Star 历史
370
+
371
+ [![Star History Chart](https://api.star-history.com/svg?repos=HKUDS/LightRAG&type=Date)](https://star-history.com/#HKUDS/LightRAG&Date)
372
+
373
+ ## 🤝 贡献
374
+
375
+ <div align="center">
376
+ 我们欢迎各种形式的贡献——Bug 修复、新功能、文档改进等。<br>
377
+ 提交 Pull Request 前,请阅读 <a href=".github/CONTRIBUTING.md"><strong>贡献指南</strong></a>。
378
+ </div>
379
+
380
+ <br>
381
+
382
+ <div align="center">
383
+ 我们感谢所有贡献者做出的宝贵贡献。
384
+ </div>
385
+
386
+ <div align="center">
387
+ <a href="https://github.com/HKUDS/LightRAG/graphs/contributors">
388
+ <img src="https://contrib.rocks/image?repo=HKUDS/LightRAG" style="border-radius: 15px; box-shadow: 0 0 20px rgba(0, 217, 255, 0.3);" />
389
+ </a>
390
+ </div>
391
+
392
+
393
+ ## 📖 引用
394
+
395
+ ```python
396
+ @article{guo2024lightrag,
397
+ title={LightRAG: Simple and Fast Retrieval-Augmented Generation},
398
+ author={Zirui Guo and Lianghao Xia and Yanhua Yu and Tu Ao and Chao Huang},
399
+ year={2024},
400
+ eprint={2410.05779},
401
+ archivePrefix={arXiv},
402
+ primaryClass={cs.IR}
403
+ }
404
+ ```
405
+
406
+ ---
407
+
408
+ <div align="center" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; padding: 30px; margin: 30px 0;">
409
+ <div>
410
+ <img src="https://user-images.githubusercontent.com/74038190/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif" width="500">
411
+ </div>
412
+ <div style="margin-top: 20px;">
413
+ <a href="https://github.com/HKUDS/LightRAG" style="text-decoration: none;">
414
+ <img src="https://img.shields.io/badge/⭐%20在%20GitHub%20上点亮星星-1a1a2e?style=for-the-badge&logo=github&logoColor=white">
415
+ </a>
416
+ <a href="https://github.com/HKUDS/LightRAG/issues" style="text-decoration: none;">
417
+ <img src="https://img.shields.io/badge/🐛%20报告问题-ff6b6b?style=for-the-badge&logo=github&logoColor=white">
418
+ </a>
419
+ <a href="https://github.com/HKUDS/LightRAG/discussions" style="text-decoration: none;">
420
+ <img src="https://img.shields.io/badge/💬%20讨论-4ecdc4?style=for-the-badge&logo=github&logoColor=white">
421
+ </a>
422
+ </div>
423
+ </div>
424
+
425
+ <div align="center">
426
+ <div style="width: 100%; max-width: 600px; margin: 20px auto; padding: 20px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2);">
427
+ <div style="display: flex; justify-content: center; align-items: center; gap: 15px;">
428
+ <span style="font-size: 24px;">⭐</span>
429
+ <span style="color: #00d9ff; font-size: 18px;">感谢您访问 LightRAG!</span>
430
+ <span style="font-size: 24px;">⭐</span>
431
+ </div>
432
+ </div>
433
+ </div>
android-chrome-512x512.png → README.assets/b2aaf634151b4706892693ffb43d9093.png RENAMED
File without changes
apple-touch-icon.png → README.assets/iShot_2025-03-23_12.40.08.png RENAMED
File without changes
README.md CHANGED
@@ -3,8 +3,441 @@ title: AI
3
  emoji: 👀
4
  colorFrom: green
5
  colorTo: pink
6
- sdk: static
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  emoji: 👀
4
  colorFrom: green
5
  colorTo: pink
6
+ sdk: docker
7
+ app_port: 9621
8
  pinned: false
9
  ---
10
 
11
+ <div align="center">
12
+
13
+ <div style="margin: 20px 0;">
14
+ <img src="./assets/logo.png" width="120" height="120" alt="LightRAG Logo" style="border-radius: 20px; box-shadow: 0 8px 32px rgba(0, 217, 255, 0.3);">
15
+ </div>
16
+
17
+ # 🚀 LightRAG: Simple and Fast Retrieval-Augmented Generation
18
+
19
+ <div align="center">
20
+ <a href="https://trendshift.io/repositories/13043" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13043" alt="HKUDS%2FLightRAG | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
21
+ </div>
22
+
23
+ <div align="center">
24
+ <div style="width: 100%; height: 2px; margin: 20px 0; background: linear-gradient(90deg, transparent, #00d9ff, transparent);"></div>
25
+ </div>
26
+
27
+ <div align="center">
28
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; padding: 25px; text-align: center;">
29
+ <p>
30
+ <a href='https://github.com/HKUDS/LightRAG'><img src='https://img.shields.io/badge/🔥Project-Page-00d9ff?style=for-the-badge&logo=github&logoColor=white&labelColor=1a1a2e'></a>
31
+ <a href='https://arxiv.org/abs/2410.05779'><img src='https://img.shields.io/badge/📄arXiv-2410.05779-ff6b6b?style=for-the-badge&logo=arxiv&logoColor=white&labelColor=1a1a2e'></a>
32
+ <a href="https://github.com/HKUDS/LightRAG/stargazers"><img src='https://img.shields.io/github/stars/HKUDS/LightRAG?color=00d9ff&style=for-the-badge&logo=star&logoColor=white&labelColor=1a1a2e' /></a>
33
+ </p>
34
+ <p>
35
+ <img src="https://img.shields.io/badge/🐍Python-3.10-4ecdc4?style=for-the-badge&logo=python&logoColor=white&labelColor=1a1a2e">
36
+ <a href="https://pypi.org/project/lightrag-hku/"><img src="https://img.shields.io/pypi/v/lightrag-hku.svg?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=ff6b6b"></a>
37
+ </p>
38
+ <p>
39
+ <a href="https://discord.gg/yF2MmDJyGJ"><img src="https://img.shields.io/badge/💬Discord-Community-7289da?style=for-the-badge&logo=discord&logoColor=white&labelColor=1a1a2e"></a>
40
+ <a href="https://github.com/HKUDS/LightRAG/issues/285"><img src="https://img.shields.io/badge/💬WeChat-Group-07c160?style=for-the-badge&logo=wechat&logoColor=white&labelColor=1a1a2e"></a>
41
+ </p>
42
+ <p>
43
+ <a href="README-zh.md"><img src="https://img.shields.io/badge/🇨🇳中文版-1a1a2e?style=for-the-badge"></a>
44
+ <a href="README.md"><img src="https://img.shields.io/badge/🇺🇸English-1a1a2e?style=for-the-badge"></a>
45
+ </p>
46
+ <p>
47
+ <a href="https://pepy.tech/projects/lightrag-hku"><img src="https://static.pepy.tech/personalized-badge/lightrag-hku?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads"></a>
48
+ </p>
49
+ </div>
50
+ </div>
51
+
52
+ </div>
53
+
54
+ <div align="center" style="margin: 30px 0;">
55
+ <img src="https://user-images.githubusercontent.com/74038190/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif" width="800">
56
+ </div>
57
+
58
+ <div align="center" style="margin: 30px 0;">
59
+ <img src="./README.assets/b2aaf634151b4706892693ffb43d9093.png" width="800" alt="LightRAG Diagram">
60
+ </div>
61
+
62
+ ---
63
+
64
+ <div align="center">
65
+ <table>
66
+ <tr>
67
+ <td style="vertical-align: middle;">
68
+ <img src="./assets/LiteWrite.png"
69
+ width="56"
70
+ height="56"
71
+ alt="LiteWrite"
72
+ style="border-radius: 12px;" />
73
+ </td>
74
+ <td style="vertical-align: middle; padding-left: 12px;">
75
+ <a href="https://litewrite.ai">
76
+ <img src="https://img.shields.io/badge/🚀%20LiteWrite-AI%20Native%20LaTeX%20Editor-ff6b6b?style=for-the-badge&logoColor=white&labelColor=1a1a2e">
77
+ </a>
78
+ </td>
79
+ </tr>
80
+ </table>
81
+ </div>
82
+
83
+ ---
84
+
85
+ ## 🎉 News
86
+ - [2026.03]🎯[New Feature]: Integrated **OpenSearch** as a unified storage backend, providing comprehensive support for all four LightRAG storage.
87
+ - [2026.03]🎯[New Feature]: Introduced a setup wizard. Support for local deployment of embedding, reranking, and storage backends via Docker.
88
+ - [2025.11]🎯[New Feature]: Integrated **RAGAS for Evaluation** and **Langfuse for Tracing**. Updated the API to return retrieved contexts alongside query results to support context precision metrics.
89
+ - [2025.10]🎯[Scalability Enhancement]: Eliminated processing bottlenecks to support **Large-Scale Datasets Efficiently**.
90
+ - [2025.09]🎯[New Feature] Enhances knowledge graph extraction accuracy for **Open-Sourced LLMs** such as Qwen3-30B-A3B.
91
+ - [2025.08]🎯[New Feature] **Reranker** is now supported, significantly boosting performance for mixed queries (set as default query mode).
92
+ - [2025.08]🎯[New Feature] Added **Document Deletion** with automatic KG regeneration to ensure optimal query performance.
93
+ - [2025.06]🎯[New Release] Our team has released [RAG-Anything](https://github.com/HKUDS/RAG-Anything) — an **All-in-One Multimodal RAG** system for seamless processing of text, images, tables, and equations.
94
+ - [2025.06]🎯[New Feature] LightRAG now supports comprehensive multimodal data handling through [RAG-Anything](https://github.com/HKUDS/RAG-Anything) integration, enabling seamless document parsing and RAG capabilities across diverse formats including PDFs, images, Office documents, tables, and formulas. Please refer to the new [multimodal section](https://github.com/HKUDS/LightRAG/?tab=readme-ov-file#multimodal-document-processing-rag-anything-integration) for details.
95
+ - [2025.03]🎯[New Feature] LightRAG now supports citation functionality, enabling proper source attribution and enhanced document traceability.
96
+ - [2025.02]🎯[New Feature] You can now use MongoDB as an all-in-one storage solution for unified data management.
97
+ - [2025.02]🎯[New Release] Our team has released [VideoRAG](https://github.com/HKUDS/VideoRAG)-a RAG system for understanding extremely long-context videos
98
+ - [2025.01]🎯[New Release] Our team has released [MiniRAG](https://github.com/HKUDS/MiniRAG) making RAG simpler with small models.
99
+ - [2025.01]🎯You can now use PostgreSQL as an all-in-one storage solution for data management.
100
+ - [2024.11]🎯[New Resource] A comprehensive guide to LightRAG is now available on [LearnOpenCV](https://learnopencv.com/lightrag). — explore in-depth tutorials and best practices. Many thanks to the blog author for this excellent contribution!
101
+ - [2024.11]🎯[New Feature] Introducing the LightRAG WebUI — an interface that allows you to insert, query, and visualize LightRAG knowledge through an intuitive web-based dashboard.
102
+ - [2024.11]🎯[New Feature] You can now [use Neo4J for Storage](https://github.com/HKUDS/LightRAG?tab=readme-ov-file#using-neo4j-for-storage)-enabling graph database support.
103
+ - [2024.10]🎯[New Feature] We've added a link to a [LightRAG Introduction Video](https://youtu.be/oageL-1I0GE). — a walkthrough of LightRAG's capabilities. Thanks to the author for this excellent contribution!
104
+ - [2024.10]🎯[New Channel] We have created a [Discord channel](https://discord.gg/yF2MmDJyGJ)!💬 Welcome to join our community for sharing, discussions, and collaboration! 🎉🎉
105
+
106
+ <details>
107
+ <summary style="font-size: 1.4em; font-weight: bold; cursor: pointer; display: list-item;">
108
+ Algorithm Flowchart
109
+ </summary>
110
+
111
+ ![LightRAG Indexing Flowchart](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-VectorDB-Json-KV-Store-Indexing-Flowchart-scaled.jpg)
112
+ *Figure 1: LightRAG Indexing Flowchart - Img Caption : [Source](https://learnopencv.com/lightrag/)*
113
+ ![LightRAG Retrieval and Querying Flowchart](https://learnopencv.com/wp-content/uploads/2024/11/LightRAG-Querying-Flowchart-Dual-Level-Retrieval-Generation-Knowledge-Graphs-scaled.jpg)
114
+ *Figure 2: LightRAG Retrieval and Querying Flowchart - Img Caption : [Source](https://learnopencv.com/lightrag/)*
115
+
116
+ </details>
117
+
118
+ ## Installation
119
+
120
+ **💡 Using uv for Package Management**: This project uses [uv](https://docs.astral.sh/uv/) for fast and reliable Python package management. Install uv first: `curl -LsSf https://astral.sh/uv/install.sh | sh` (Unix/macOS) or `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"` (Windows)
121
+
122
+ > **Note**: You can also use pip if you prefer, but uv is recommended for better performance and more reliable dependency management.
123
+ >
124
+ > **📦 Offline Deployment**: For offline or air-gapped environments, see the [Offline Deployment Guide](./docs/OfflineDeployment.md) for instructions on pre-installing all dependencies and cache files.
125
+
126
+ ### Install LightRAG Server
127
+
128
+ The LightRAG Server is designed to provide Web UI and API support. The Web UI facilitates document indexing, knowledge graph exploration, and a simple RAG query interface. LightRAG Server also provide an Ollama compatible interfaces, aiming to emulate LightRAG as an Ollama chat model. This allows AI chat bot, such as Open WebUI, to access LightRAG easily.
129
+
130
+ * Install from PyPI
131
+
132
+ ```bash
133
+ ### Install LightRAG Server as tool using uv (recommended)
134
+ uv tool install "lightrag-hku[api]"
135
+
136
+ ### Or using pip
137
+ # python -m venv .venv
138
+ # source .venv/bin/activate # Windows: .venv\Scripts\activate
139
+ # pip install "lightrag-hku[api]"
140
+
141
+ ### Build front-end artifacts
142
+ cd lightrag_webui
143
+ bun install --frozen-lockfile
144
+ bun run build
145
+ cd ..
146
+
147
+ # Setup env file
148
+ # Obtain the env.example file by downloading it from the GitHub repository root
149
+ # or by copying it from a local source checkout.
150
+ cp env.example .env # Update the .env with your LLM and embedding configurations
151
+ # Launch the server
152
+ lightrag-server
153
+ ```
154
+
155
+ * Installation from Source
156
+
157
+ ```bash
158
+ git clone https://github.com/HKUDS/LightRAG.git
159
+ cd LightRAG
160
+
161
+ # Bootstrap the development environment (recommended)
162
+ make dev
163
+ source .venv/bin/activate # Activate the virtual environment (Linux/macOS)
164
+ # Or on Windows: .venv\Scripts\activate
165
+
166
+ # make dev installs the test toolchain plus the full offline stack
167
+ # (API, storage backends, and provider integrations), then builds the frontend.
168
+ # Run make env-base or copy env.example to .env before starting the server.
169
+
170
+ # Equivalent manual steps with uv
171
+ # Note: uv sync automatically creates a virtual environment in .venv/
172
+ uv sync --extra test --extra offline
173
+ source .venv/bin/activate # Activate the virtual environment (Linux/macOS)
174
+ # Or on Windows: .venv\Scripts\activate
175
+
176
+ ### Or using pip with virtual environment
177
+ # python -m venv .venv
178
+ # source .venv/bin/activate # Windows: .venv\Scripts\activate
179
+ # pip install -e ".[test,offline]"
180
+
181
+ # Build front-end artifacts
182
+ cd lightrag_webui
183
+ bun install --frozen-lockfile
184
+ bun run build
185
+ cd ..
186
+
187
+ # setup env file
188
+ make env-base # Or: cp env.example .env and update it manually
189
+ # Launch API-WebUI server
190
+ lightrag-server
191
+ ```
192
+
193
+ * Launching the LightRAG Server with Docker Compose
194
+
195
+ ```bash
196
+ git clone https://github.com/HKUDS/LightRAG.git
197
+ cd LightRAG
198
+ cp env.example .env # Update the .env with your LLM and embedding configurations
199
+ # modify LLM and Embedding settings in .env
200
+ docker compose up
201
+ ```
202
+
203
+ > Historical versions of LightRAG docker images can be found here: [LightRAG Docker Images]( https://github.com/HKUDS/LightRAG/pkgs/container/lightrag)
204
+
205
+ ### Create .env File With Setup Tool
206
+
207
+ Instead of editing `env.example` by hand, use the interactive setup wizard to generate a configured `.env` and, when needed, `docker-compose.final.yml`:
208
+
209
+ ```bash
210
+ make env-base # Required first step: LLM, embedding, reranker
211
+ make env-storage # Optional: storage backends and database services
212
+ make env-server # Optional: server port, auth, and SSL
213
+ make env-base-rewrite # Optional: force-regenerate wizard-managed compose services
214
+ make env-storage-rewrite # Optional: force-regenerate wizard-managed compose services
215
+ make env-security-check # Optional: audit the current .env for security risks
216
+ ```
217
+
218
+ For full description of every target see [docs/InteractiveSetup.md](./docs/InteractiveSetup.md).
219
+ The setup wizards update configuration only; run `make env-security-check` separately to audit the
220
+ current `.env` for security risks before deployment.
221
+ By default, rerunning the setup preserves unchanged wizard-managed compose service blocks; use a
222
+ `*-rewrite` target only when you need to rebuild those managed blocks from the bundled templates.
223
+
224
+ ### Install LightRAG Core
225
+
226
+ * Install from source (Recommended)
227
+
228
+ ```bash
229
+ cd LightRAG
230
+ # Note: uv sync automatically creates a virtual environment in .venv/
231
+ uv sync
232
+ source .venv/bin/activate # Activate the virtual environment (Linux/macOS)
233
+ # Or on Windows: .venv\Scripts\activate
234
+
235
+ # Or: pip install -e .
236
+ ```
237
+
238
+ * Install from PyPI
239
+
240
+ ```bash
241
+ uv pip install lightrag-hku
242
+ # Or: pip install lightrag-hku
243
+ ```
244
+
245
+ ## Quick Start
246
+
247
+ ### LLM and Technology Stack Requirements for LightRAG
248
+
249
+ LightRAG's demands on the capabilities of Large Language Models (LLMs) are significantly higher than those of traditional RAG, as it requires the LLM to perform entity-relationship extraction tasks from documents. Configuring appropriate Embedding and Reranker models is also crucial for improving query performance.
250
+
251
+ - **LLM Selection**:
252
+ - It is recommended to use an LLM with at least 32 billion parameters.
253
+ - The context length should be at least 32KB, with 64KB being recommended.
254
+ - It is not recommended to choose reasoning models during the document indexing stage.
255
+ - During the query stage, it is recommended to choose models with stronger capabilities than those used in the indexing stage to achieve better query results.
256
+ - **Embedding Model**:
257
+ - A high-performance Embedding model is essential for RAG.
258
+ - We recommend using mainstream multilingual Embedding models, such as: `BAAI/bge-m3` and `text-embedding-3-large`.
259
+ - **Important Note**: The Embedding model must be determined before document indexing, and the same model must be used during the document query phase. For certain storage solutions (e.g., PostgreSQL), the vector dimension must be defined upon initial table creation. Therefore, when changing embedding models, it is necessary to delete the existing vector-related tables and allow LightRAG to recreate them with the new dimensions.
260
+ - **Reranker Model Configuration**:
261
+ - Configuring a Reranker model can significantly enhance LightRAG's retrieval performance.
262
+ - When a Reranker model is enabled, it is recommended to set the "mix mode" as the default query mode.
263
+ - We recommend using mainstream Reranker models, such as: `BAAI/bge-reranker-v2-m3` or models provided by services like Jina.
264
+
265
+ ### Quick Start for LightRAG Server
266
+
267
+ The LightRAG Server is designed to provide Web UI and API support. The LightRAG Server offers a comprehensive knowledge graph visualization feature. It supports various gravity layouts, node queries, subgraph filtering, and more. For more information about LightRAG Server, please refer to [LightRAG Server](./docs/LightRAG-API-Server.md).
268
+
269
+ ![iShot_2025-03-23_12.40.08](./README.assets/iShot_2025-03-23_12.40.08.png)
270
+
271
+
272
+ ### Quick Start for LightRAG core
273
+
274
+ To get started with LightRAG core, refer to the sample codes available in the `examples` folder. Additionally, a [video demo](https://www.youtube.com/watch?v=g21royNJ4fw) demonstration is provided to guide you through the local setup process. If you already possess an OpenAI API key, you can run the demo right away:
275
+
276
+ ```bash
277
+ ### you should run the demo code with project folder
278
+ cd LightRAG
279
+ ### provide your API-KEY for OpenAI
280
+ export OPENAI_API_KEY="sk-...your_opeai_key..."
281
+ ### download the demo document of "A Christmas Carol" by Charles Dickens
282
+ curl https://raw.githubusercontent.com/gusye1234/nano-graphrag/main/tests/mock_data.txt > ./book.txt
283
+ ### run the demo code
284
+ python examples/lightrag_openai_demo.py
285
+ ```
286
+
287
+ For a streaming response implementation example, please see `examples/lightrag_openai_compatible_demo.py`. Prior to execution, ensure you modify the sample code's LLM and embedding configurations accordingly.
288
+
289
+ **Note 1**: When running the demo program, please be aware that different test scripts may use different embedding models. If you switch to a different embedding model, you must clear the data directory (`./dickens`); otherwise, the program may encounter errors. If you wish to retain the LLM cache, you can preserve the `kv_store_llm_response_cache.json` file while clearing the data directory.
290
+
291
+ **Note 2**: Only `lightrag_openai_demo.py` and `lightrag_openai_compatible_demo.py` are officially supported sample codes. Other sample files are community contributions that haven't undergone full testing and optimization.
292
+
293
+ ## Programming with LightRAG Core
294
+
295
+ For the complete Core API reference — including init parameters, `QueryParam`, LLM/embedding provider examples (OpenAI, Ollama, Azure, Gemini, HuggingFace, LlamaIndex), reranker injection, insert operations, entity/relation management, and delete/merge — see **[docs/ProgramingWithCore.md](./docs/ProgramingWithCore.md)**.
296
+
297
+ > ⚠️ **If you would like to integrate LightRAG into your project, we recommend utilizing the REST API provided by the LightRAG Server**. LightRAG Core is typically intended for embedded applications or for researchers who wish to conduct studies and evaluations.
298
+
299
+ ### Advanced Features
300
+
301
+ LightRAG provides additional capabilities including token usage tracking, knowledge graph data export, LLM cache management, Langfuse observability integration, and RAGAS-based evaluation. See **[docs/AdvancedFeatures.md](./docs/AdvancedFeatures.md)**.
302
+
303
+ ### Multimodal Document Processing (RAG-Anything Integration)
304
+
305
+ LightRAG integrates with [RAG-Anything](https://github.com/HKUDS/RAG-Anything) for end-to-end multimodal RAG across PDFs, Office documents, images, tables, and formulas. For setup and usage examples, see **[docs/AdvancedFeatures.md](./docs/AdvancedFeatures.md)**.
306
+
307
+ > LightRAG Server will soon integrate RAG-Anything’s multimodal processing capabilities into its file processing pipeline. Stay tuned.
308
+
309
+ ## Replicating Findings in the Papper
310
+
311
+ LightRAG consistently outperforms NaiveRAG, RQ-RAG, HyDE, and GraphRAG across agriculture, computer science, legal, and mixed domains. For the full evaluation methodology, prompts, and reproduce steps, see **[docs/Reproduce.md](./docs/Reproduce.md)**.
312
+
313
+ **Overall Performance Table**
314
+
315
+ ||**Agriculture**||**CS**||**Legal**||**Mix**||
316
+ |----------------------|---------------|------------|------|------------|---------|------------|-------|------------|
317
+ ||NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|NaiveRAG|**LightRAG**|
318
+ |**Comprehensiveness**|32.4%|**67.6%**|38.4%|**61.6%**|16.4%|**83.6%**|38.8%|**61.2%**|
319
+ |**Diversity**|23.6%|**76.4%**|38.0%|**62.0%**|13.6%|**86.4%**|32.4%|**67.6%**|
320
+ |**Empowerment**|32.4%|**67.6%**|38.8%|**61.2%**|16.4%|**83.6%**|42.8%|**57.2%**|
321
+ |**Overall**|32.4%|**67.6%**|38.8%|**61.2%**|15.2%|**84.8%**|40.0%|**60.0%**|
322
+ ||RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|RQ-RAG|**LightRAG**|
323
+ |**Comprehensiveness**|31.6%|**68.4%**|38.8%|**61.2%**|15.2%|**84.8%**|39.2%|**60.8%**|
324
+ |**Diversity**|29.2%|**70.8%**|39.2%|**60.8%**|11.6%|**88.4%**|30.8%|**69.2%**|
325
+ |**Empowerment**|31.6%|**68.4%**|36.4%|**63.6%**|15.2%|**84.8%**|42.4%|**57.6%**|
326
+ |**Overall**|32.4%|**67.6%**|38.0%|**62.0%**|14.4%|**85.6%**|40.0%|**60.0%**|
327
+ ||HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|HyDE|**LightRAG**|
328
+ |**Comprehensiveness**|26.0%|**74.0%**|41.6%|**58.4%**|26.8%|**73.2%**|40.4%|**59.6%**|
329
+ |**Diversity**|24.0%|**76.0%**|38.8%|**61.2%**|20.0%|**80.0%**|32.4%|**67.6%**|
330
+ |**Empowerment**|25.2%|**74.8%**|40.8%|**59.2%**|26.0%|**74.0%**|46.0%|**54.0%**|
331
+ |**Overall**|24.8%|**75.2%**|41.6%|**58.4%**|26.4%|**73.6%**|42.4%|**57.6%**|
332
+ ||GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|GraphRAG|**LightRAG**|
333
+ |**Comprehensiveness**|45.6%|**54.4%**|48.4%|**51.6%**|48.4%|**51.6%**|**50.4%**|49.6%|
334
+ |**Diversity**|22.8%|**77.2%**|40.8%|**59.2%**|26.4%|**73.6%**|36.0%|**64.0%**|
335
+ |**Empowerment**|41.2%|**58.8%**|45.2%|**54.8%**|43.6%|**56.4%**|**50.8%**|49.2%|
336
+ |**Overall**|45.2%|**54.8%**|48.0%|**52.0%**|47.2%|**52.8%**|**50.4%**|49.6%|
337
+
338
+
339
+ ## 🔗 Related Projects
340
+
341
+ *Ecosystem & Extensions*
342
+
343
+ <div align="center">
344
+ <table>
345
+ <tr>
346
+ <td align="center">
347
+ <a href="https://github.com/HKUDS/RAG-Anything">
348
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
349
+ <span style="font-size: 32px;">📸</span>
350
+ </div>
351
+ <b>RAG-Anything</b><br>
352
+ <sub>Multimodal RAG</sub>
353
+ </a>
354
+ </td>
355
+ <td align="center">
356
+ <a href="https://github.com/HKUDS/VideoRAG">
357
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
358
+ <span style="font-size: 32px;">🎥</span>
359
+ </div>
360
+ <b>VideoRAG</b><br>
361
+ <sub>Extreme Long-Context Video RAG</sub>
362
+ </a>
363
+ </td>
364
+ <td align="center">
365
+ <a href="https://github.com/HKUDS/MiniRAG">
366
+ <div style="width: 100px; height: 100px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2); display: flex; align-items: center; justify-content: center; margin-bottom: 10px;">
367
+ <span style="font-size: 32px;">✨</span>
368
+ </div>
369
+ <b>MiniRAG</b><br>
370
+ <sub>Extremely Simple RAG</sub>
371
+ </a>
372
+ </td>
373
+ </tr>
374
+ </table>
375
+ </div>
376
+
377
+ ---
378
+
379
+ ## ⭐ Star History
380
+
381
+ [![Star History Chart](https://api.star-history.com/svg?repos=HKUDS/LightRAG&type=Date)](https://star-history.com/#HKUDS/LightRAG&Date)
382
+
383
+ ## 🤝 Contribution
384
+
385
+ <div align="center">
386
+ We welcome contributions of all kinds — bug fixes, new features, documentation improvements, and more.<br>
387
+ Please read our <a href=".github/CONTRIBUTING.md"><strong>Contributing Guide</strong></a> before submitting a pull request.
388
+ </div>
389
+
390
+ <br>
391
+
392
+ <div align="center">
393
+ We thank all our contributors for their valuable contributions.
394
+ </div>
395
+
396
+ <div align="center">
397
+ <a href="https://github.com/HKUDS/LightRAG/graphs/contributors">
398
+ <img src="https://contrib.rocks/image?repo=HKUDS/LightRAG" style="border-radius: 15px; box-shadow: 0 0 20px rgba(0, 217, 255, 0.3);" />
399
+ </a>
400
+ </div>
401
+
402
+
403
+ ## 📖 Citation
404
+
405
+ ```python
406
+ @article{guo2024lightrag,
407
+ title={LightRAG: Simple and Fast Retrieval-Augmented Generation},
408
+ author={Zirui Guo and Lianghao Xia and Yanhua Yu and Tu Ao and Chao Huang},
409
+ year={2024},
410
+ eprint={2410.05779},
411
+ archivePrefix={arXiv},
412
+ primaryClass={cs.IR}
413
+ }
414
+ ```
415
+
416
+ ---
417
+
418
+ <div align="center" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; padding: 30px; margin: 30px 0;">
419
+ <div>
420
+ <img src="https://user-images.githubusercontent.com/74038190/212284100-561aa473-3905-4a80-b561-0d28506553ee.gif" width="500">
421
+ </div>
422
+ <div style="margin-top: 20px;">
423
+ <a href="https://github.com/HKUDS/LightRAG" style="text-decoration: none;">
424
+ <img src="https://img.shields.io/badge/⭐%20Star%20us%20on%20GitHub-1a1a2e?style=for-the-badge&logo=github&logoColor=white">
425
+ </a>
426
+ <a href="https://github.com/HKUDS/LightRAG/issues" style="text-decoration: none;">
427
+ <img src="https://img.shields.io/badge/🐛%20Report%20Issues-ff6b6b?style=for-the-badge&logo=github&logoColor=white">
428
+ </a>
429
+ <a href="https://github.com/HKUDS/LightRAG/discussions" style="text-decoration: none;">
430
+ <img src="https://img.shields.io/badge/💬%20Discussions-4ecdc4?style=for-the-badge&logo=github&logoColor=white">
431
+ </a>
432
+ </div>
433
+ </div>
434
+
435
+ <div align="center">
436
+ <div style="width: 100%; max-width: 600px; margin: 20px auto; padding: 20px; background: linear-gradient(135deg, rgba(0, 217, 255, 0.1) 0%, rgba(0, 217, 255, 0.05) 100%); border-radius: 15px; border: 1px solid rgba(0, 217, 255, 0.2);">
437
+ <div style="display: flex; justify-content: center; align-items: center; gap: 15px;">
438
+ <span style="font-size: 24px;">⭐</span>
439
+ <span style="color: #00d9ff; font-size: 18px;">Thank you for visiting LightRAG!</span>
440
+ <span style="font-size: 24px;">⭐</span>
441
+ </div>
442
+ </div>
443
+ </div>
SECURITY.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reporting Security Issues
2
+
3
+ The LightRAG team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
4
+
5
+ To report a security issue, please use the GitHub Security Advisory: [Report a Vulnerability](https://github.com/HKUDS/LightRAG/security/advisories/new)
6
+
7
+ The LightRAG team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
8
+
9
+ Report security bugs in third-party modules to the person or team maintaining the module.
10
+
11
+ ### Supported Versions
12
+
13
+ The following versions currently being supported with security updates.
14
+
15
+ | Version | Supported |
16
+ | ------- | ------------------ |
17
+ | 1.2.x | :x: |
18
+ | 1.3.x | :white_check_mark: |
_redirects DELETED
@@ -1 +0,0 @@
1
- /* /index.html 200
 
 
assets/AIAssistant-CUeVd4uO.css DELETED
@@ -1 +0,0 @@
1
- .ai-assistant-page{height:calc(100vh - var(--header-height) - 24px);max-height:calc(100vh - var(--header-height) - 24px);background:var(--background);flex-direction:column;gap:0;animation:.35s ease-out fadeIn;display:flex}.ai-header{background:var(--surface);border-bottom:1px solid var(--border);border-radius:var(--radius-lg) var(--radius-lg) 0 0;flex-shrink:0;justify-content:space-between;align-items:center;padding:16px 24px;display:flex}.ai-header-left{align-items:center;gap:12px;display:flex}.ai-header-icon{border-radius:var(--radius-lg);background:var(--primary-gradient);color:#fff;width:42px;height:42px;box-shadow:var(--shadow-primary);justify-content:center;align-items:center;display:flex}.ai-header-info h2{color:var(--text-primary);margin:0;font-size:1.05rem;font-weight:700;line-height:1.3}.ai-header-info p{color:var(--text-muted);margin:0;font-size:.78rem}.ai-header-right{align-items:center;gap:10px;display:flex}.ai-health-dot{border-radius:50%;flex-shrink:0;width:9px;height:9px}.ai-health-dot.online{background:var(--success);animation:2s ease-in-out infinite pulse-dot;box-shadow:0 0 0 3px #05966933}.ai-health-dot.offline{background:var(--danger);box-shadow:0 0 0 3px #dc262626}.ai-health-dot.checking{background:var(--warning);animation:1s ease-in-out infinite pulse-dot}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.5}}.ai-health-label{color:var(--text-muted);font-size:.75rem;font-weight:600}.ai-mode-select{border-radius:var(--radius-pill);border:1.5px solid var(--border);color:var(--text-secondary);background:var(--surface-50);cursor:pointer;transition:var(--transition-fast);outline:none;padding:5px 10px;font-family:inherit;font-size:.76rem;font-weight:600}.ai-mode-select:hover,.ai-mode-select:focus{border-color:var(--primary);color:var(--primary)}.ai-messages{background:var(--background-gradient);flex-direction:column;flex:1;gap:20px;padding:24px;display:flex;overflow-y:auto}.ai-welcome{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;padding:40px 20px;animation:.5s ease-out fadeIn;display:flex}.ai-welcome-icon{background:var(--primary-gradient);color:#fff;width:72px;height:72px;box-shadow:var(--shadow-primary);border-radius:20px;justify-content:center;align-items:center;margin-bottom:20px;animation:3s ease-in-out infinite float;display:flex}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-8px)}}.ai-welcome h3{color:var(--text-primary);margin-bottom:6px;font-size:1.25rem;font-weight:700}.ai-welcome p{color:var(--text-secondary);max-width:420px;font-size:.88rem;line-height:1.6}.ai-quick-prompts{flex-wrap:wrap;justify-content:center;gap:8px;max-width:600px;margin-top:24px;display:flex}.ai-quick-btn{border-radius:var(--radius-pill);border:1.5px solid var(--border);background:var(--surface);color:var(--text-secondary);cursor:pointer;transition:var(--transition-fast);white-space:nowrap;padding:8px 16px;font-family:inherit;font-size:.8rem;font-weight:600}.ai-quick-btn:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-light);box-shadow:var(--shadow-sm);transform:translateY(-1px)}.ai-msg{gap:10px;max-width:85%;animation:.3s ease-out fadeIn;display:flex}.ai-msg.user{flex-direction:row-reverse;align-self:flex-end}.ai-msg.assistant{align-self:flex-start}.ai-msg-avatar{border-radius:10px;flex-shrink:0;justify-content:center;align-items:center;width:34px;min-width:34px;height:34px;margin-top:2px;font-size:.85rem;display:flex}.ai-msg.user .ai-msg-avatar{background:var(--accent-light);color:var(--accent)}.ai-msg.assistant .ai-msg-avatar{background:var(--primary-gradient);color:#fff;box-shadow:0 2px 8px #0f766e2e}.ai-msg-bubble{border-radius:var(--radius-lg);word-break:break-word;padding:12px 16px;font-size:.88rem;line-height:1.65;position:relative}.ai-msg.user .ai-msg-bubble{background:var(--primary);color:#fff;box-shadow:var(--shadow-primary);border-bottom-right-radius:4px}.ai-msg.assistant .ai-msg-bubble{background:var(--surface);color:var(--text-primary);border:1px solid var(--border);box-shadow:var(--shadow-sm);border-bottom-left-radius:4px}.ai-msg-time{color:var(--text-muted);text-align:right;margin-top:4px;font-size:.68rem}.ai-msg.assistant .ai-msg-time{text-align:left}.ai-typing-dots{gap:4px;padding:4px 0;display:inline-flex}.ai-typing-dots span{background:var(--primary);opacity:.4;border-radius:50%;width:7px;height:7px;animation:1.4s ease-in-out infinite typingBounce}.ai-typing-dots span:nth-child(2){animation-delay:.15s}.ai-typing-dots span:nth-child(3){animation-delay:.3s}@keyframes typingBounce{0%,80%,to{opacity:.3;transform:scale(.6)}40%{opacity:1;transform:scale(1)}}.ai-input-area{background:var(--surface);border-top:1px solid var(--border);border-radius:0 0 var(--radius-lg) var(--radius-lg);flex-shrink:0;align-items:flex-end;gap:10px;padding:16px 24px 20px;display:flex}.ai-input-wrapper{background:var(--surface-50);border:1.5px solid var(--border);border-radius:var(--radius-lg);transition:var(--transition-fast);flex:1;align-items:flex-end;gap:8px;padding:6px 8px 6px 14px;display:flex}.ai-input-wrapper:focus-within{border-color:var(--primary);box-shadow:0 0 0 3px #0f766e1a}.ai-input-wrapper textarea{resize:none;color:var(--text-primary);background:0 0;border:none;outline:none;flex:1;min-height:24px;max-height:120px;padding:4px 0;font-size:.88rem;line-height:1.5}.ai-input-wrapper textarea::placeholder{color:var(--text-muted)}.ai-attach-btn,.ai-send-btn{cursor:pointer;width:36px;height:36px;transition:var(--transition-fast);border:none;border-radius:10px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.ai-attach-btn{color:var(--text-muted);background:0 0}.ai-attach-btn:hover{background:var(--surface-100);color:var(--text-secondary)}.ai-send-btn{background:var(--primary-gradient);color:#fff;box-shadow:var(--shadow-primary)}.ai-send-btn:hover:not(:disabled){transform:scale(1.06);box-shadow:0 6px 18px #0f766e47}.ai-send-btn:disabled{opacity:.45;cursor:not-allowed;transform:none}.ai-file-toast{background:var(--info-light);color:var(--info);border-radius:var(--radius-md);align-items:center;gap:8px;margin:0 24px;padding:8px 16px;font-size:.8rem;font-weight:600;animation:.25s ease-out fadeIn;display:flex}.ai-file-toast button{color:var(--danger);cursor:pointer;background:0 0;border:none;border-radius:4px;margin-left:auto;padding:2px 6px;font-size:.75rem;font-weight:700}.ai-file-toast button:hover{background:var(--danger-light)}.ai-msg-error{color:var(--danger);background:var(--danger-light);border-radius:var(--radius-md);border:1px solid #dc262626;padding:8px 14px;font-size:.82rem;font-weight:500}@media (width<=899px){.ai-assistant-page{height:calc(100vh - var(--header-height) - 12px);max-height:calc(100vh - var(--header-height) - 12px)}.ai-header{border-radius:0;padding:12px 16px}.ai-messages{gap:16px;padding:16px}.ai-msg{max-width:92%}.ai-input-area{border-radius:0;padding:12px 16px 16px}.ai-quick-prompts{gap:6px}.ai-quick-btn{padding:6px 12px;font-size:.75rem}.ai-welcome-icon{border-radius:14px;width:56px;height:56px}}
 
 
assets/AIAssistant-foLVZGCT.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{t as i}from"./send-DYka62Uw.js";import{t as a}from"./x-Dz7E0PNg.js";var o=r(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),s=r(`paperclip`,[[`path`,{d:`m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551`,key:`1miecu`}]]),c=r(`sparkles`,[[`path`,{d:`M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z`,key:`1s2grr`}],[`path`,{d:`M20 2v4`,key:`1rf3ol`}],[`path`,{d:`M22 4h-4`,key:`gwowj6`}],[`circle`,{cx:`4`,cy:`20`,r:`2`,key:`6kqj1y`}]]),l=e(n(),1),u=``.replace(/\/+$/,``),d=()=>({"Content-Type":`application/json`}),f=async()=>{if(!u)return!1;try{let e=new AbortController,t=setTimeout(()=>e.abort(),8e3),n=await fetch(`${u}/health`,{headers:d(),signal:e.signal});return clearTimeout(t),n.ok}catch{return!1}},p=async(e,t,n,r)=>{try{let r=await fetch(`${u}/query/stream`,{method:`POST`,headers:d(),body:JSON.stringify({query:e.query,mode:e.mode||`hybrid`,stream:!0,response_type:`Multiple Paragraphs`,top_k:60,conversation_history:e.conversation_history||[]})});if(!r.ok||!r.body)throw Error(`Stream failed: ${r.status}`);let i=r.body.getReader(),a=new TextDecoder;for(;;){let{done:e,value:n}=await i.read();if(e)break;t(a.decode(n,{stream:!0}))}n()}catch(e){r(e instanceof Error?e:Error(String(e)))}},m=async e=>{let t=new FormData;t.append(`file`,e);let n=await fetch(`${u}/documents/file`,{method:`POST`,headers:{},body:t});if(!n.ok)throw Error(`File upload failed: ${n.status}`);return n.json()},h=t(),g=[`Thiết bị nào sắp hết hạn đăng kiểm?`,`Quy trình bảo trì thiết bị y tế?`,`Hướng dẫn sử dụng máy thở?`,`Thông tư về quản lý trang thiết bị y tế?`,`Cách xử lý khi thiết bị gặp sự cố?`,`Quy định kiểm định thiết bị?`],_={hybrid:`Hybrid (Khuyến nghị)`,local:`Local`,global:`Global`,naive:`Naive`},v=e=>new Date(e).toLocaleTimeString(`vi-VN`,{hour:`2-digit`,minute:`2-digit`}),y=()=>{let[e,t]=(0,l.useState)([]),[n,r]=(0,l.useState)(``),[u,d]=(0,l.useState)(!1),[y,b]=(0,l.useState)(null),[x,S]=(0,l.useState)(`hybrid`),[C,w]=(0,l.useState)(null),[T,E]=(0,l.useState)(null),D=(0,l.useRef)(null),O=(0,l.useRef)(null),k=(0,l.useRef)(null);(0,l.useEffect)(()=>{let e=!1,t=async()=>{b(null);let t=await f();e||b(t)};t();let n=setInterval(t,6e4);return()=>{e=!0,clearInterval(n)}},[]),(0,l.useEffect)(()=>{D.current?.scrollIntoView({behavior:`smooth`})},[e]),(0,l.useEffect)(()=>{let e=O.current;e&&(e.style.height=`auto`,e.style.height=Math.min(e.scrollHeight,120)+`px`)},[n]);let A=(0,l.useCallback)(async i=>{let a=(i||n).trim();if(!a||u)return;r(``);let o={role:`user`,content:a,timestamp:Date.now()};t(e=>[...e,o]);let s=[...e,o].slice(-10).map(e=>({role:e.role,content:e.content})),c=Date.now();t(e=>[...e,{role:`assistant`,content:``,timestamp:c}]),d(!0),await p({query:a,mode:x,conversation_history:s},e=>{t(t=>{let n=[...t],r=n.length-1;return n[r]?.role===`assistant`&&(n[r]={...n[r],content:n[r].content+e}),n})},()=>d(!1),e=>{d(!1),t(t=>{let n=[...t],r=n.length-1;return n[r]?.role===`assistant`&&(n[r]={...n[r],content:`⚠️ Lỗi: ${e.message}. Vui lòng thử lại.`}),n})})},[n,u,e,x]),j=e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),A())},M=e=>{let t=e.target.files?.[0];t&&w(t),e.target.value=``},N=async()=>{if(C){E(`Đang tải lên...`);try{await m(C),E(`✅ Đã nạp "${C.name}" vào knowledge base!`),w(null),setTimeout(()=>E(null),4e3)}catch(e){E(`❌ Lỗi: ${e instanceof Error?e.message:String(e)}`),setTimeout(()=>E(null),5e3)}}};(0,l.useEffect)(()=>{C&&N()},[C]);let P=y===null?`checking`:y?`online`:`offline`,F=y===null?`Đang kiểm tra...`:y?`Online`:`Offline`;return(0,h.jsxs)(`div`,{className:`ai-assistant-page`,children:[(0,h.jsxs)(`div`,{className:`ai-header`,children:[(0,h.jsxs)(`div`,{className:`ai-header-left`,children:[(0,h.jsx)(`div`,{className:`ai-header-icon`,children:(0,h.jsx)(o,{size:22})}),(0,h.jsxs)(`div`,{className:`ai-header-info`,children:[(0,h.jsx)(`h2`,{children:`AI Trợ lý Trang thiết bị`}),(0,h.jsx)(`p`,{children:`Hỏi đáp thông minh dựa trên dữ liệu nội bộ`})]})]}),(0,h.jsxs)(`div`,{className:`ai-header-right`,children:[(0,h.jsx)(`div`,{className:`ai-health-dot ${P}`}),(0,h.jsx)(`span`,{className:`ai-health-label`,children:F}),(0,h.jsx)(`select`,{className:`ai-mode-select`,value:x,onChange:e=>S(e.target.value),title:`Chọn chế độ truy vấn RAG`,children:Object.entries(_).map(([e,t])=>(0,h.jsx)(`option`,{value:e,children:t},e))})]})]}),T&&(0,h.jsxs)(`div`,{className:`ai-file-toast`,children:[(0,h.jsx)(`span`,{children:T}),(0,h.jsx)(`button`,{onClick:()=>E(null),title:`Đóng`,children:(0,h.jsx)(a,{size:14})})]}),(0,h.jsxs)(`div`,{className:`ai-messages`,children:[e.length===0?(0,h.jsxs)(`div`,{className:`ai-welcome`,children:[(0,h.jsx)(`div`,{className:`ai-welcome-icon`,children:(0,h.jsx)(c,{size:32})}),(0,h.jsx)(`h3`,{children:`Xin chào! 👋`}),(0,h.jsx)(`p`,{children:`Tôi là trợ lý AI chuyên về trang thiết bị y tế. Hãy hỏi tôi về quy trình, hướng dẫn sử dụng, thông tư, nghị định hoặc bất kỳ thông tin nào liên quan.`}),(0,h.jsx)(`div`,{className:`ai-quick-prompts`,children:g.map(e=>(0,h.jsx)(`button`,{className:`ai-quick-btn`,onClick:()=>A(e),children:e},e))})]}):e.map((e,t)=>(0,h.jsxs)(`div`,{className:`ai-msg ${e.role}`,children:[(0,h.jsx)(`div`,{className:`ai-msg-avatar`,children:e.role===`assistant`?(0,h.jsx)(o,{size:16}):`👤`}),(0,h.jsxs)(`div`,{children:[(0,h.jsx)(`div`,{className:`ai-msg-bubble`,children:e.role===`assistant`&&!e.content&&u?(0,h.jsxs)(`div`,{className:`ai-typing-dots`,children:[(0,h.jsx)(`span`,{}),(0,h.jsx)(`span`,{}),(0,h.jsx)(`span`,{})]}):e.content.startsWith(`⚠️`)?(0,h.jsx)(`span`,{className:`ai-msg-error`,children:e.content}):(0,h.jsx)(`span`,{style:{whiteSpace:`pre-wrap`},children:e.content})}),(0,h.jsx)(`div`,{className:`ai-msg-time`,children:v(e.timestamp)})]})]},t)),(0,h.jsx)(`div`,{ref:D})]}),(0,h.jsxs)(`div`,{className:`ai-input-area`,children:[(0,h.jsx)(`input`,{ref:k,type:`file`,accept:`.pdf,.docx,.doc,.txt,.csv`,style:{display:`none`},onChange:M}),(0,h.jsxs)(`div`,{className:`ai-input-wrapper`,children:[(0,h.jsx)(`button`,{className:`ai-attach-btn`,onClick:()=>k.current?.click(),title:`Nạp tài liệu (PDF, DOCX, TXT)`,children:(0,h.jsx)(s,{size:18})}),(0,h.jsx)(`textarea`,{ref:O,rows:1,placeholder:`Nhập câu hỏi về trang thiết bị...`,value:n,onChange:e=>r(e.target.value),onKeyDown:j,disabled:u})]}),(0,h.jsx)(`button`,{className:`ai-send-btn`,onClick:()=>A(),disabled:!n.trim()||u,title:`Gửi (Enter)`,children:(0,h.jsx)(i,{size:18})})]})]})};export{y as default};
 
 
assets/Dashboard-Bu4sLdM_.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{B as i,L as a,V as o,c as s,d as c,f as l,h as u,l as d,m as f,p,s as m,u as h,v as g,y as _}from"./ui-B-VMc-s2.js";import{n as v}from"./stringUtils-DXxYBSKD.js";import{t as y}from"./calendar-x-2-Bgp8TMWd.js";import{t as b}from"./download-Dml9X3_Z.js";import{t as x}from"./shield-alert-5nY9RH10.js";import{E as S,O as C,T as w,a as T,c as ee,g as te,h as ne,l as E,p as D,r as O,t as k}from"./index-C1supBxi.js";import{r as A,t as j}from"./dist-C0DRywKl.js";import{t as M}from"./deviceStatus-CXqVBxuC.js";import{n as N,t as P}from"./jspdf.plugin.autotable-BhtUcEI9.js";var re=r(`stethoscope`,[[`path`,{d:`M11 2v2`,key:`1539x4`}],[`path`,{d:`M5 2v2`,key:`1yf1q8`}],[`path`,{d:`M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1`,key:`rb5t3r`}],[`path`,{d:`M8 15a6 6 0 0 0 12 0v-3`,key:`x18d4x`}],[`circle`,{cx:`20`,cy:`10`,r:`2`,key:`ts1r5v`}]]),ie=e(n(),1),F=e=>String(e||``).replace(`[KHẨN] `,``).trim(),I=(e,t,n=new Date)=>{let r=new Set(t.filter(e=>O(e.status)).map(e=>F(e.deviceId)).filter(Boolean));return e.reduce((e,t)=>{let i=M(t,n),a=F(t.id||t[`Seri Máy`]),o=r.has(a);return i.expired&&(e.expiredComplianceCount+=1),i.complianceWarning&&(e.complianceWarningCount+=1),o&&(e.activeRepairDeviceCount+=1),i.good&&!o&&(e.stableDeviceCount+=1),e},{stableDeviceCount:0,expiredComplianceCount:0,complianceWarningCount:0,activeRepairDeviceCount:0})},L=(e,t)=>{let n=Array(12).fill(0);return e.forEach(e=>{if(!e.rowId||E(e.status))return;let r=e.rowId.match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/);if(!r)return;let i=Number(r[2]);Number(r[3])===t&&i>=1&&i<=12&&(n[i-1]+=1)}),n},R=t(),z=()=>{let{devices:e,isLoading:t,mutate:n}=D(),{repairs:r,isLoading:E}=ne(),M=t||E,[F,z]=(0,ie.useState)(null),B=C(),{isAdmin:V}=S(),H=r.filter(e=>O(e.status)),U=H.length,W=H.filter(e=>T(e.status)).length,G=H.filter(e=>ee(e.status)).length,{stableDeviceCount:K,expiredComplianceCount:q,complianceWarningCount:J}=I(e,r),Y=()=>{let t={};e.forEach(e=>{let n=e.department?e.department.trim():`Chưa phân bổ`;n===``&&(n=`Chưa phân bổ`),t[n]=(t[n]||0)+1});let n=Object.entries(t).sort((e,t)=>t[1]-e[1]);return{labels:n.map(e=>e[0]),data:n.map(e=>e[1])}},ae=e.map(e=>{let t=999,n=`safe`,r=``,a=``,o=e.documents||[];if(o.length>0){let e=null,s=45,c=``,l=new Date;l.setHours(0,0,0,0);for(let t of o){let n=i(t.expiryDate);if(n){let r=Math.ceil((n.getTime()-l.getTime())/(1e3*60*60*24));if((t.status===`Đã gửi`||t.status===`Đã phê duyệt`)&&r>=0)continue;if(!e||n.getTime()<e.getTime()){e=n;let r=String(t.prepTime||``).match(/\d+/);s=r?parseInt(r[0],10):45,c=t.docType}}}if(e){let i=new Date;i.setHours(0,0,0,0);let o=e.getTime(),l=o-s*24*60*60*1e3,u=Math.ceil((l-i.getTime())/(1e3*60*60*24)),d=Math.ceil((o-i.getTime())/(1e3*60*60*24));d<0?(n=`critical`,r=`Quá hạn ${c} ${Math.abs(d)} ngày`):u<=0?(n=`danger`,r=`Tới hạn chuẩn bị hồ sơ ${c} (còn ${d} ngày)`):u<=5&&(n=`warning`,r=`Còn ${u} ngày bắt đầu làm hồ sơ ${c}`),t=u<=5?u:d,a=c}}else{let a=String(e[`Thời hạn cấp lại/ Hạn đăng kiểm`]||e[`Ngày bảo dưỡng tiếp theo`]||``),o=String(e[`Thời gian chuẩn bị Hồ sơ`]||e[`Thời gian chuẩn bị Hồ sơ`]||``),s=i(a);if(s){let e=o.match(/\d+/),i=e?parseInt(e[0],10):45,a=new Date;a.setHours(0,0,0,0);let c=s.getTime(),l=c-i*24*60*60*1e3,u=Math.ceil((l-a.getTime())/(1e3*60*60*24)),d=Math.ceil((c-a.getTime())/(1e3*60*60*24));d<0?(n=`critical`,r=`Quá hạn đăng kiểm ${Math.abs(d)} ngày`):u<=0?(n=`danger`,r=`Tới hạn chuẩn bị hồ sơ (còn ${d} ngày đăng kiểm)`):u<=5&&(n=`warning`,r=`Còn ${u} ngày bắt đầu làm hồ sơ`),t=u<=5?u:d}}(e[`Trạng thái Hồ sơ`]||``)===`Đã gửi`&&n!==`safe`&&n!==`critical`&&(n=`success`,r=`Đã gửi hồ sơ`);let s=i(String(e[`Thời hạn cấp lại/ Hạn đăng kiểm`]||e[`Hạn đăng kiểm`]||``));return{...e,deadlineDate:s?s.toLocaleDateString(`vi-VN`):`Không rõ`,warningLevel:n,alertText:r,daysRemaining:t,urgentDocType:a}}),oe=async(t,r)=>{if(!V){alert(`Chỉ tài khoản Admin được cập nhật trạng thái hồ sơ.`);return}z(t);let i=await a(t,`Đã gửi`,r);i&&i.success?n(e.map(e=>e.id===t?{...e,"Trạng thái Hồ sơ":`Đã gửi`,documents:e.documents?.map(e=>!r||e.docType===r?{...e,status:`Đã gửi`}:e)}:e)):alert(`Có lỗi xảy ra: `+(i?.message||``)),z(null)},X=ae.filter(e=>e.warningLevel!==`safe`).sort((e,t)=>e.daysRemaining-t.daysRemaining),{labels:Z,data:Q}=Y(),$=[`#0d9488`,`#3b82f6`,`#f59e0b`,`#e11d48`,`#64748b`,`#7c3aed`,`#0891b2`,`#84cc16`,`#ea580c`,`#be123c`,`#2563eb`,`#16a34a`,`#9333ea`,`#ca8a04`,`#475569`],se={labels:Z.length>0?Z:[`Chưa có dữ liệu`],datasets:[{data:Q.length>0?Q:[1],backgroundColor:(Z.length>0?Z:[`Chưa có dữ liệu`]).map((e,t)=>$[t%$.length]),borderWidth:0}]},ce=(()=>{let e=new Date().getFullYear(),t=L(r,e);return{labels:[`T1`,`T2`,`T3`,`T4`,`T5`,`T6`,`T7`,`T8`,`T9`,`T10`,`T11`,`T12`],datasets:[{label:`Số ca báo hỏng/sửa chữa hợp lệ năm ${e}`,data:t,backgroundColor:`#0d9488`,borderRadius:6}]}})();return(0,R.jsxs)(`div`,{className:`dashboard-page`,children:[(0,R.jsxs)(`div`,{className:`dashboard-header dashboard-hero`,children:[(0,R.jsxs)(`div`,{className:`dashboard-hero-main`,children:[(0,R.jsx)(`span`,{className:`dashboard-hero-icon`,"aria-hidden":`true`,children:(0,R.jsx)(w,{size:24})}),(0,R.jsxs)(`div`,{children:[(0,R.jsx)(`span`,{className:`dashboard-eyebrow`,children:`Bảng điều phối thiết bị`}),(0,R.jsx)(`h1`,{className:`dashboard-title`,children:`Tổng quan hệ thống`}),(0,R.jsx)(`p`,{className:`dashboard-subtitle`,children:`Theo dõi thiết bị, yêu cầu sửa chữa và hồ sơ kiểm định trong ngày.`})]})]}),(0,R.jsxs)(`div`,{className:`dashboard-hero-actions`,children:[(0,R.jsxs)(`span`,{className:`dashboard-update-chip`,children:[`Cập nhật `,new Date().toLocaleTimeString(`vi-VN`),`, `,new Date().toLocaleDateString(`vi-VN`)]}),(0,R.jsx)(_,{variant:`secondary`,icon:(0,R.jsx)(b,{size:18}),onClick:()=>{let t=new N({unit:`mm`,format:`a4`});t.setFontSize(14),t.setFont(`helvetica`,`bold`),t.text(`BAO CAO TONG QUAN HE THONG QLTTB`,105,20,{align:`center`}),t.setFontSize(10),t.setFont(`helvetica`,`normal`),t.text(`Trung tam Y te khu vuc Thanh Ba`,105,28,{align:`center`}),t.text(`Ngay xuat: ${new Date().toLocaleString(`vi-VN`)}`,105,35,{align:`center`}),P(t,{startY:42,head:[[`Chi so`,`So lieu`]],body:[[`Tong so TB quan ly`,`${e.length} may`],[`Dang hoat dong on dinh`,`${K} may`],[`Bao hong / cho xu ly`,`${W} yeu cau`],[`Dang sua chua`,`${G} yeu cau`],[`Canh bao dang kiem`,`${J} thiet bi`],[`Qua han dang kiem`,`${q} thiet bi`]],styles:{fontSize:11,cellPadding:4},headStyles:{fillColor:[13,148,136],textColor:255,fontStyle:`bold`}});let n=Y();P(t,{startY:t.lastAutoTable.finalY+12,head:[[`Khoa/Phong`,`So luong thiet bi`]],body:n.labels.map((e,t)=>[v(e),n.data[t]]),styles:{fontSize:10},headStyles:{fillColor:[13,148,136],textColor:255}}),t.save(`TongQuan_QLTTB_${new Date().toLocaleDateString(`vi-VN`).replace(/\//g,`-`)}.pdf`)},children:`Xuất báo cáo PDF`})]})]}),(0,R.jsxs)(`div`,{className:`stats-grid dashboard-kpi-grid`,children:[(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-blue`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(re,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Tài sản`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:e.length}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Thiết bị quản lý`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Toàn bộ thiết bị trong hệ thống`})]})]}),(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-teal`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(w,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Ổn định`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:K}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Hoạt động ổn định`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Không sửa chữa mở, không quá hạn/cảnh báo đăng kiểm`})]})]}),(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-orange`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(o,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Tiếp nhận`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:W}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Báo hỏng / chờ xử lý`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Đang chờ duyệt hoặc phân công`})]})]}),(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-orange`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(te,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Sửa chữa`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:G}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Đang sửa chữa`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Đã tiếp nhận và đang xử lý`})]})]}),(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-rose`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(x,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Hồ sơ`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:J}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Cảnh báo đăng kiểm`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Sắp đến mốc chuẩn bị hồ sơ`})]})]}),(0,R.jsxs)(m,{className:`stat-card dashboard-kpi-card gradient-rose`,children:[(0,R.jsxs)(`div`,{className:`stat-card-header`,children:[(0,R.jsx)(`div`,{className:`stat-icon`,children:(0,R.jsx)(y,{size:26})}),(0,R.jsx)(`span`,{className:`stat-status-pill`,children:`Quá hạn`})]}),(0,R.jsxs)(`div`,{className:`stat-info`,children:[(0,R.jsx)(`span`,{className:`stat-value`,children:M?`...`:q}),(0,R.jsx)(`span`,{className:`stat-label`,children:`Hết hạn đăng kiểm`}),(0,R.jsx)(`span`,{className:`stat-meta`,children:`Cần ưu tiên xử lý ngay`})]})]})]}),(0,R.jsxs)(`div`,{className:`charts-grid dashboard-analytics-grid`,children:[(0,R.jsxs)(m,{className:`dashboard-panel dashboard-chart-panel`,children:[(0,R.jsx)(d,{title:(0,R.jsxs)(`div`,{className:`dashboard-card-heading`,children:[(0,R.jsx)(`span`,{children:`Phân bổ thiết bị theo khoa/phòng`}),(0,R.jsx)(`small`,{children:`Cơ cấu thiết bị đang quản lý theo đơn vị sử dụng`})]})}),(0,R.jsxs)(s,{children:[(0,R.jsx)(`div`,{className:`chart-container`,children:M?(0,R.jsx)(`div`,{className:`dashboard-loading-state`,children:`Đang tải biểu đồ...`}):(0,R.jsx)(A,{data:se,options:{maintainAspectRatio:!1,plugins:{legend:{display:!1}}}})}),!M&&Z.length>0&&(0,R.jsx)(`div`,{className:`department-detail-list`,children:Z.map((e,t)=>(0,R.jsxs)(`div`,{className:`department-detail-item`,children:[(0,R.jsx)(`span`,{className:`department-detail-dot`,style:{backgroundColor:$[t%$.length]}}),(0,R.jsx)(`span`,{className:`department-detail-name`,children:e}),(0,R.jsx)(`span`,{className:`department-detail-count`,children:Q[t]})]},e))})]})]}),(0,R.jsxs)(m,{className:`dashboard-panel dashboard-chart-panel`,children:[(0,R.jsx)(d,{title:(0,R.jsxs)(`div`,{className:`dashboard-card-heading`,children:[(0,R.jsx)(`span`,{children:`Thống kê báo hỏng & sửa chữa năm nay`}),(0,R.jsx)(`small`,{children:`Chỉ tính ca hợp lệ, không gồm yêu cầu bị từ chối`})]})}),(0,R.jsx)(s,{children:(0,R.jsx)(`div`,{className:`chart-container`,children:M?(0,R.jsx)(`div`,{className:`dashboard-loading-state`,children:`Đang tải biểu đồ...`}):(0,R.jsx)(j,{data:ce,options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:`top`}}}})})})]})]}),(0,R.jsxs)(`div`,{className:`lists-grid dashboard-work-grid`,children:[(0,R.jsxs)(m,{className:`dashboard-panel dashboard-list-panel`,children:[(0,R.jsx)(d,{title:(0,R.jsxs)(`div`,{className:`dashboard-card-heading`,children:[(0,R.jsxs)(`span`,{className:`dashboard-title-row`,children:[`Yêu cầu báo hỏng `,U>0&&(0,R.jsxs)(g,{variant:`danger`,children:[U,` đang mở`]})]}),(0,R.jsx)(`small`,{children:`Các yêu cầu cần theo dõi hoặc xử lý tiếp`})]}),action:(0,R.jsx)(_,{variant:`secondary`,size:`sm`,onClick:()=>B(V?`/tracking`:`/requests?type=repair`),children:`Xem tất cả`})}),(0,R.jsxs)(h,{children:[(0,R.jsx)(p,{children:(0,R.jsxs)(u,{children:[(0,R.jsx)(f,{children:`Ngày báo`}),(0,R.jsx)(f,{children:`Mã thiết bị`}),(0,R.jsx)(f,{children:`Khoa/phòng`}),(0,R.jsx)(f,{children:`Tình trạng lỗi`}),(0,R.jsx)(f,{children:`Trạng thái`})]})}),(0,R.jsx)(c,{children:M?(0,R.jsx)(u,{children:(0,R.jsx)(l,{colSpan:5,children:(0,R.jsx)(`div`,{className:`dashboard-table-state`,children:`Đang tải...`})})}):U===0?(0,R.jsx)(u,{children:(0,R.jsx)(l,{colSpan:5,children:(0,R.jsx)(`div`,{className:`dashboard-table-state is-success`,children:`Không có yêu cầu sửa chữa đang mở.`})})}):H.slice(0,5).map(t=>(0,R.jsxs)(u,{children:[(0,R.jsx)(l,{style:{whiteSpace:`nowrap`,fontSize:`0.9rem`,color:`var(--text-secondary)`},children:t.rowId.split(` `)[0]}),(0,R.jsx)(l,{children:(0,R.jsx)(`strong`,{children:t.deviceId})}),(0,R.jsx)(l,{children:e.find(e=>e.id===t.deviceId)?.department||`Chưa rõ`}),(0,R.jsx)(l,{children:(0,R.jsx)(`div`,{style:{maxWidth:`200px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`},title:t.description,children:t.description})}),(0,R.jsx)(l,{children:(0,R.jsx)(g,{variant:k(t.status),children:t.status})})]},`${t.rowId}-${t.deviceId}`))})]})]}),(0,R.jsxs)(m,{className:`dashboard-panel dashboard-list-panel`,children:[(0,R.jsx)(d,{title:(0,R.jsxs)(`div`,{className:`dashboard-card-heading`,children:[(0,R.jsxs)(`span`,{className:`dashboard-title-row`,children:[`Cảnh báo hồ sơ / đăng kiểm `,X.length>0&&(0,R.jsx)(g,{variant:`danger`,children:X.length})]}),(0,R.jsx)(`small`,{children:`Nhóm theo mức độ ưu tiên xử lý hồ sơ`})]}),action:(0,R.jsx)(_,{variant:`secondary`,size:`sm`,onClick:()=>B(`/devices`),children:`Tất cả thiết bị`})}),(0,R.jsx)(s,{children:M?(0,R.jsx)(`div`,{className:`dashboard-table-state`,children:`Đang tải...`}):X.length===0?(0,R.jsx)(`div`,{className:`dashboard-table-state is-success`,children:`Mọi thiết bị đều đang trong hạn an toàn.`}):(()=>{let e={};X.forEach(t=>{let n=t.warningLevel;e[n]||(e[n]=[]),e[n].push(t)});let t=[`critical`,`danger`,`warning`,`success`],n={critical:{label:`Quá hạn`,badgeVariant:`danger`},danger:{label:`Tới hạn chuẩn bị hồ sơ`,badgeVariant:`danger`},warning:{label:`Sắp tới hạn`,badgeVariant:`warning`},success:{label:`Đã g���i hồ sơ`,badgeVariant:`success`}};return(0,R.jsx)(`div`,{className:`alert-groups`,children:t.filter(t=>e[t]?.length).map(t=>{let r=n[t],i=e[t];return(0,R.jsxs)(`div`,{className:`alert-group alert-group-${t}`,children:[(0,R.jsxs)(`div`,{className:`alert-group-header`,children:[(0,R.jsx)(`span`,{className:`alert-level-dot`,"aria-hidden":`true`}),(0,R.jsx)(`strong`,{style:{fontSize:`0.95rem`},children:r.label}),(0,R.jsx)(g,{variant:r.badgeVariant,children:i.length})]}),(0,R.jsx)(`div`,{className:`alert-group-list`,children:i.map(e=>(0,R.jsxs)(`div`,{className:`alert-group-item`,onClick:()=>B(`/devices/${encodeURIComponent(e.id)}`),children:[(0,R.jsxs)(`div`,{className:`alert-device-main`,children:[(0,R.jsx)(`strong`,{children:e.name}),(0,R.jsxs)(`small`,{children:[e.id,` • `,e.department]})]}),(0,R.jsxs)(`div`,{className:`alert-device-deadline`,children:[(0,R.jsx)(`div`,{children:e.alertText}),(0,R.jsxs)(`small`,{children:[`Hạn: `,e.deadlineDate]})]}),V&&t!==`success`&&(0,R.jsx)(_,{size:`sm`,variant:`secondary`,onClick:t=>{t.stopPropagation(),oe(e.id,e.urgentDocType)},disabled:F===e.id,className:`alert-action-button`,children:F===e.id?`...`:`Đã gửi`})]},e.id))})]},t)})})})()})]})]})]})};export{z as default};
 
 
assets/Dashboard-DplGvfIe.css DELETED
@@ -1 +0,0 @@
1
- .dashboard-page{--dash-border:#d8e3ea;--dash-soft:#f8fafc;--dash-ink:#0f172a;--dash-muted:#64748b;flex-direction:column;gap:18px;display:flex}.dashboard-header{justify-content:space-between;align-items:center;gap:16px;display:flex}.dashboard-hero{border:1px solid var(--dash-border);background:linear-gradient(90deg,#0f766e14 0 4px,#0000 4px),linear-gradient(#fff 0%,#f8fbfc 100%);border-radius:8px;padding:18px;box-shadow:0 14px 34px #0f172a0f}.dashboard-hero-main{grid-template-columns:48px minmax(0,1fr);align-items:center;gap:14px;min-width:0;display:grid}.dashboard-hero-icon{width:48px;height:48px;color:var(--primary);background:#f0fdfa;border:1px solid #99f6e4;border-radius:8px;justify-content:center;align-items:center;display:inline-flex;box-shadow:inset 0 -2px #0f766e1f}.dashboard-eyebrow{color:var(--primary);letter-spacing:.08em;text-transform:uppercase;margin-bottom:4px;font-size:.76rem;font-weight:850;display:block}.dashboard-title{color:var(--dash-ink);letter-spacing:0;margin:0;font-size:1.55rem;font-weight:860;line-height:1.2}.dashboard-subtitle{color:var(--dash-muted);margin:5px 0 0;font-size:.92rem;line-height:1.5}.dashboard-hero-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:10px;display:flex}.dashboard-update-chip{min-height:38px;color:var(--dash-muted);white-space:nowrap;background:#fff;border:1px solid #cfe1e8;border-radius:8px;align-items:center;padding:0 12px;font-size:.84rem;font-weight:760;display:inline-flex}.dashboard-kpi-grid{grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:12px;display:grid}.dashboard-page .dashboard-kpi-card{min-height:148px;color:var(--dash-ink);background:#fff;border-radius:8px;flex-direction:column;justify-content:space-between;gap:14px;padding:16px;display:flex;position:relative;box-shadow:0 10px 26px #0f172a0e;border:1px solid var(--dash-border)!important;border-top:4px solid var(--primary)!important}.dashboard-page .dashboard-kpi-card:hover{transform:translateY(-1px);box-shadow:0 16px 34px #0f172a14;border-color:#bfd0da!important}.dashboard-page .stat-card-header{justify-content:space-between;align-items:center;gap:10px;display:flex}.dashboard-page .stat-icon{width:42px;height:42px;color:var(--primary);background:#eef8f7;border-radius:8px;justify-content:center;align-items:center;display:inline-flex}.dashboard-page .stat-status-pill{background:var(--dash-soft);max-width:98px;color:var(--dash-muted);text-overflow:ellipsis;white-space:nowrap;border:1px solid #e1ebf1;border-radius:999px;padding:5px 8px;font-size:.72rem;font-weight:820;overflow:hidden}.dashboard-page .stat-info{flex-direction:column;gap:3px;min-width:0;display:flex}.dashboard-page .stat-value{color:var(--dash-ink);font-variant-numeric:tabular-nums;font-size:1.92rem;font-weight:880;line-height:1.04}.dashboard-page .stat-label{color:var(--text-primary);font-size:.86rem;font-weight:820;line-height:1.25}.dashboard-page .stat-meta{color:var(--dash-muted);font-size:.76rem;font-weight:650;line-height:1.35;display:block}.dashboard-page .gradient-blue{border-top-color:var(--accent)!important}.dashboard-page .gradient-teal{border-top-color:var(--success)!important}.dashboard-page .gradient-orange{border-top-color:var(--warning)!important}.dashboard-page .gradient-rose{border-top-color:var(--danger)!important}.dashboard-page .gradient-blue .stat-icon{color:var(--accent);background:var(--accent-light)}.dashboard-page .gradient-orange .stat-icon{color:var(--warning);background:var(--warning-light)}.dashboard-page .gradient-rose .stat-icon{color:var(--danger);background:var(--danger-light)}.dashboard-page .gradient-teal .stat-icon{color:var(--success);background:var(--success-light)}.dashboard-analytics-grid{grid-template-columns:minmax(340px,.92fr) minmax(0,1.08fr);gap:16px;display:grid}.dashboard-work-grid{grid-template-columns:minmax(0,1.08fr) minmax(360px,.92fr);gap:16px;display:grid}.dashboard-panel.card{border-color:var(--dash-border);border-radius:8px;box-shadow:0 12px 30px #0f172a0e}.dashboard-panel .card-header{background:#fbfdfe;align-items:center;min-height:68px}.dashboard-card-heading{flex-direction:column;gap:3px;min-width:0;display:flex}.dashboard-card-heading span{color:var(--dash-ink);font-size:.98rem;font-weight:830;line-height:1.25}.dashboard-card-heading small{color:var(--dash-muted);font-size:.78rem;font-weight:650;line-height:1.35}.dashboard-title-row{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.chart-container{justify-content:center;align-items:center;height:300px;display:flex}.dashboard-loading-state,.dashboard-table-state{min-height:88px;color:var(--dash-muted);text-align:center;justify-content:center;align-items:center;font-size:.9rem;font-weight:720;display:flex}.dashboard-table-state.is-success{color:var(--success)}.department-detail-list{border-top:1px solid var(--dash-border);grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:8px 10px;margin-top:14px;padding-top:14px;display:grid}.department-detail-item{background:#f8fafc;border:1px solid #e3edf3;border-radius:8px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;min-width:0;padding:8px 9px;display:grid}.department-detail-dot{border-radius:3px;width:10px;height:10px}.department-detail-name{min-width:0;color:var(--dash-muted);text-overflow:ellipsis;white-space:nowrap;font-size:.82rem;font-weight:680;overflow:hidden}.department-detail-count{color:var(--dash-ink);font-variant-numeric:tabular-nums;font-size:.82rem;font-weight:850}.dashboard-list-panel .table-container{box-shadow:none;border:0;border-radius:0}.dashboard-list-panel .data-table th{color:#526277;background:#f7fafb;font-size:.72rem}.dashboard-list-panel .data-table td{vertical-align:middle}.alert-groups{flex-direction:column;gap:12px;display:flex}.alert-group{background:#fff;border:1px solid #dce7ee;border-radius:8px;padding:12px}.alert-group-critical{background:#fff5f5;border-color:#fecaca}.alert-group-danger{background:#fff7ed;border-color:#fed7aa}.alert-group-warning{background:#fffbeb;border-color:#fde68a}.alert-group-success{background:#f0fdf4;border-color:#bbf7d0}.alert-group-header{color:var(--dash-ink);align-items:center;gap:8px;margin-bottom:8px;display:flex}.alert-level-dot{background:var(--dash-muted);border-radius:999px;width:10px;height:10px;box-shadow:0 0 0 3px #64748b1f}.alert-group-critical .alert-level-dot{background:var(--danger);box-shadow:0 0 0 3px #dc26261f}.alert-group-danger .alert-level-dot{background:var(--warning);box-shadow:0 0 0 3px #f59e0b24}.alert-group-warning .alert-level-dot{background:#ca8a04;box-shadow:0 0 0 3px #ca8a0424}.alert-group-success .alert-level-dot{background:var(--success);box-shadow:0 0 0 3px #05966924}.alert-group-list{flex-direction:column;gap:8px;display:flex}.alert-group-item{color:var(--dash-ink);cursor:pointer;transition:border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast);background:#ffffffbd;border:1px solid #94a3b83d;border-radius:8px;grid-template-columns:minmax(0,1fr) minmax(160px,auto) auto;align-items:center;gap:10px;padding:10px;display:grid}.alert-group-item:hover,.alert-group-item:focus-visible{border-color:#b8cad5;outline:none;transform:translateY(-1px);box-shadow:0 8px 18px #0f172a14}.alert-device-main{flex-direction:column;gap:2px;min-width:0;display:flex}.alert-device-main strong{color:var(--dash-ink);text-overflow:ellipsis;white-space:nowrap;font-size:.88rem;font-weight:820;overflow:hidden}.alert-device-main small,.alert-device-deadline small{color:var(--dash-muted);font-size:.76rem;font-weight:650;line-height:1.35}.alert-device-deadline{color:var(--text-primary);text-align:right;flex-direction:column;flex-shrink:0;gap:2px;font-size:.8rem;font-weight:720;display:flex}.alert-action-button.btn{white-space:nowrap;flex-shrink:0;font-size:.78rem}@media (width<=1200px){.dashboard-analytics-grid,.dashboard-work-grid{grid-template-columns:1fr}}@media (width<=768px){.dashboard-header,.dashboard-hero-actions{align-items:stretch}.dashboard-hero{flex-direction:column}.dashboard-hero-actions{justify-content:flex-start;width:100%}.dashboard-update-chip{white-space:normal}.dashboard-kpi-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.alert-group-item{grid-template-columns:1fr}.alert-device-deadline{text-align:left}}@media (width<=520px){.dashboard-hero-main{grid-template-columns:40px minmax(0,1fr)}.dashboard-hero-icon{width:40px;height:40px}.dashboard-title{font-size:1.28rem}.dashboard-kpi-grid,.department-detail-list{grid-template-columns:1fr}}
 
 
assets/DeviceList-uEjUqpus.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{E as i,V as a,W as o,_ as ee,b as te,d as ne,f as s,h as c,i as re,m as l,o as ie,p as ae,s as u,u as oe,y as d}from"./ui-B-VMc-s2.js";import{t as se}from"./stringUtils-DXxYBSKD.js";import{t as ce}from"./calendar-x-2-Bgp8TMWd.js";import{t as le}from"./download-Dml9X3_Z.js";import{n as ue,t as de}from"./Devices-Cc89Wtut.js";import{t as f}from"./loader-circle-jjFgLZ0s.js";import{t as fe}from"./plus-DkeqYTKZ.js";import{t as p}from"./printer-tT3rpZyK.js";import{t as pe}from"./shield-alert-5nY9RH10.js";import{t as m}from"./x-Dz7E0PNg.js";import{A as me,E as he,O as ge,g as _e,p as ve,y as ye}from"./index-C1supBxi.js";import{i as h,n as be,r as xe,t as g}from"./deviceStatus-CXqVBxuC.js";import{t as Se}from"./exportCsv-YXOCfA7g.js";import{t as Ce}from"./esm-62HA5UXF.js";var we=r(`monitor`,[[`rect`,{width:`20`,height:`14`,x:`2`,y:`3`,rx:`2`,key:`48i651`}],[`line`,{x1:`8`,x2:`16`,y1:`21`,y2:`21`,key:`1svkeh`}],[`line`,{x1:`12`,x2:`12`,y1:`17`,y2:`21`,key:`vw1qmm`}]]),Te=r(`pen`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}]]),_=e(n(),1),v=t(),y={serial:``,name:``,department:``,dateAdded:new Date().toLocaleDateString(`vi-VN`),notes:``},b=e=>String(e||``).split(`;`).map(e=>e.trim()).filter(Boolean),x=()=>{let{devices:e,isLoading:t,refetch:n}=ve(),[r,x]=(0,_.useState)(``),[S,C]=(0,_.useState)(`all`),[w,T]=(0,_.useState)(`all`),[E,D]=(0,_.useState)(!1),[O,k]=(0,_.useState)(1),[A,j]=(0,_.useState)([]),[Ee,M]=(0,_.useState)(!1),[N,P]=(0,_.useState)(`add`),[F,I]=(0,_.useState)(y),[L,R]=(0,_.useState)(!1),[z,B]=(0,_.useState)(``),De=ge(),[V]=me(),{isAdmin:H}=he(),U=ie();(0,_.useEffect)(()=>{x(V.get(`search`)||``)},[V]),(0,_.useEffect)(()=>{k(1)},[r,S,w,E]),(0,_.useEffect)(()=>{A.length>0&&setTimeout(()=>{window.print(),j([])},500)},[A]);let W=Array.from(new Set(e.map(e=>e.department))).filter(Boolean).sort(),G=e.reduce((e,t)=>{let n=g(t);return e.all+=1,Object.entries(n).forEach(([t,n])=>{n&&(e[t]+=1)}),e},{all:0,good:0,reported:0,repairing:0,expired:0,complianceWarning:0,unassigned:0}),K=G.good,q=G.reported,J=G.repairing,Y=G.complianceWarning,X=G.expired,Oe=G.unassigned,ke=e.filter(e=>be(e)).length,Z=e.filter(e=>{let t=se(e,[`id`,`name`,`department`,`Ghi chú`],r),n=S===`all`||e.department===S,i=xe(e,w),a=!E||be(e);return t&&n&&i&&a}),Q=Math.ceil(Z.length/50),Ae=Z.slice((O-1)*50,O*50),je=e=>j([e]),Me=()=>{Z.length>0?j(Z.slice(0,20)):U.warning(`Không có thiết bị nào trong danh sách hiển thị để in.`)},Ne=()=>{if(Z.length===0){U.warning(`Không có dữ liệu để xuất CSV.`);return}Se(Z.map(e=>({"Mã thiết bị":e.id,"Tên thiết bị":e.name,"Khoa/phòng":e.department,"Trạng thái":h(e,{activeFilter:w}).sheetStatus,"Ngày nhập / đăng kiểm":e.dateAdded})),`DanhSachThietBi_ThanhBa.csv`)},Pe=()=>{H&&(I(y),P(`add`),B(``),M(!0))},Fe=e=>{H&&(I({serial:String(e.id||``),name:String(e.name||``),department:String(e.department||``),dateAdded:e.dateAdded===`N/A`?``:String(e.dateAdded||``),notes:String(e[`Ghi chú`]||``)}),P(`edit`),B(``),M(!0))},$=e=>{I(t=>({...t,[e.target.name]:e.target.value}))};return(0,v.jsxs)(`div`,{className:`device-list-page`,children:[(0,v.jsxs)(`div`,{className:`page-header`,children:[(0,v.jsx)(`h1`,{className:`page-title`,children:`Danh sách trang thiết bị`}),(0,v.jsxs)(`div`,{className:`action-buttons`,children:[(0,v.jsx)(d,{variant:`secondary`,icon:(0,v.jsx)(le,{size:18}),onClick:Ne,children:`Xuất CSV`}),(0,v.jsx)(d,{variant:`secondary`,icon:(0,v.jsx)(p,{size:18}),onClick:Me,children:`In QR hàng loạt`}),H&&(0,v.jsx)(d,{variant:`primary`,icon:(0,v.jsx)(fe,{size:18}),onClick:Pe,children:`Thêm thiết bị mới`})]})]}),(0,v.jsxs)(`div`,{className:`stats-grid`,children:[(0,v.jsxs)(u,{className:`stat-card primary-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(we,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:e.length}),(0,v.jsx)(`p`,{children:`Tổng thiết bị`})]})]}),(0,v.jsxs)(u,{className:`stat-card success-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(o,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:K}),(0,v.jsx)(`p`,{children:`Đang hoạt động`})]})]}),(0,v.jsxs)(u,{className:`stat-card danger-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(a,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:q}),(0,v.jsx)(`p`,{children:`Báo hỏng / chờ xử lý`})]})]}),(0,v.jsxs)(u,{className:`stat-card warning-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(_e,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:J}),(0,v.jsx)(`p`,{children:`Đang sửa chữa`})]})]}),(0,v.jsxs)(u,{className:`stat-card compliance-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(pe,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:Y}),(0,v.jsx)(`p`,{children:`Cảnh báo đăng kiểm`})]})]}),(0,v.jsxs)(u,{className:`stat-card expired-gradient`,children:[(0,v.jsx)(`div`,{className:`stat-icon-wrapper`,children:(0,v.jsx)(ce,{size:28})}),(0,v.jsxs)(`div`,{className:`stat-content`,children:[(0,v.jsx)(`h3`,{children:X}),(0,v.jsx)(`p`,{children:`Hết hạn đăng kiểm`})]})]})]}),(0,v.jsxs)(u,{children:[(0,v.jsxs)(`div`,{className:`toolbar`,style:{padding:`20px`,paddingBottom:`12px`},children:[(0,v.jsxs)(`div`,{className:`filter-group`,children:[(0,v.jsxs)(`select`,{className:`filter-select`,value:S,onChange:e=>C(e.target.value),children:[(0,v.jsx)(`option`,{value:`all`,children:`Tất cả khoa/phòng`}),W.map(e=>(0,v.jsx)(`option`,{value:e,children:e},e))]}),(0,v.jsxs)(`select`,{className:`filter-select`,value:w,onChange:e=>T(e.target.value),children:[(0,v.jsx)(`option`,{value:`all`,children:`Tất cả trạng thái`}),(0,v.jsx)(`option`,{value:`good`,children:`Hoạt động tốt`}),(0,v.jsx)(`option`,{value:`reported`,children:`Báo hỏng`}),(0,v.jsx)(`option`,{value:`repairing`,children:`Đang sửa chữa`}),(0,v.jsx)(`option`,{value:`complianceWarning`,children:`Cảnh báo ĐK`}),(0,v.jsx)(`option`,{value:`expired`,children:`Hết hạn ĐK`}),(0,v.jsx)(`option`,{value:`unassigned`,children:`Chưa phân bổ`})]})]}),(0,v.jsxs)(`div`,{className:`search-box`,style:{position:`relative`,display:`flex`,alignItems:`center`,gap:`8px`},children:[(0,v.jsxs)(`div`,{style:{flex:1,position:`relative`},children:[(0,v.jsx)(ee,{placeholder:`Tìm kiếm mã thiết bị, tên máy...`,icon:(0,v.jsx)(ye,{size:18}),value:r,onChange:e=>x(e.target.value),style:{paddingRight:r?`32px`:`12px`}}),r&&(0,v.jsx)(`button`,{onClick:()=>x(``),style:{position:`absolute`,right:`10px`,top:`50%`,transform:`translateY(-50%)`,background:`none`,border:`none`,cursor:`pointer`,color:`var(--text-secondary)`,display:`flex`,alignItems:`center`,padding:`4px`},title:`Xóa tìm kiếm`,children:(0,v.jsx)(m,{size:16})})]}),(r||S!==`all`||w!==`all`||E)&&(0,v.jsx)(d,{variant:`secondary`,size:`sm`,onClick:()=>{x(``),C(`all`),T(`all`),D(!1)},style:{whiteSpace:`nowrap`},icon:(0,v.jsx)(m,{size:14}),children:`Xóa lọc`})]})]}),(0,v.jsxs)(`div`,{className:`quick-filters-chips`,children:[(0,v.jsxs)(`button`,{className:`chip ${w===`all`&&!E?`active`:``}`,onClick:()=>{T(`all`),D(!1)},children:[`Tất cả (`,e.length,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-success ${w===`good`?`active`:``}`,onClick:()=>T(`good`),children:[`Hoạt động tốt (`,K,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-danger ${w===`reported`?`active`:``}`,onClick:()=>T(`reported`),children:[`Báo hỏng (`,q,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-warning ${w===`repairing`?`active`:``}`,onClick:()=>T(`repairing`),children:[`Đang sửa (`,J,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-warning ${w===`complianceWarning`?`active`:``}`,onClick:()=>T(`complianceWarning`),children:[`Cảnh báo ĐK (`,Y,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-danger ${w===`expired`?`active`:``}`,onClick:()=>T(`expired`),children:[`Hết hạn ĐK (`,X,`)`]}),(0,v.jsxs)(`button`,{className:`chip chip-neutral ${w===`unassigned`?`active`:``}`,onClick:()=>T(`unassigned`),children:[`Chưa phân bổ (`,Oe,`)`]}),(0,v.jsxs)(`button`,{className:`chip ${E?`active`:``}`,onClick:()=>D(e=>!e),title:`Thêm mới trong vòng 30 ngày qua`,children:[`Mới nhập (`,ke,`)`]})]}),(0,v.jsxs)(oe,{className:`device-inventory-table`,children:[(0,v.jsx)(ae,{children:(0,v.jsxs)(c,{children:[(0,v.jsx)(l,{className:`col-device-code`,children:`Mã / Seri`}),(0,v.jsx)(l,{className:`col-device-name`,children:`Tên thiết bị`}),(0,v.jsx)(l,{className:`col-device-dept`,children:`Khoa/phòng`}),(0,v.jsx)(l,{className:`col-device-status`,children:`Trạng thái`}),(0,v.jsx)(l,{className:`col-device-date`,children:`Ngày nhập`}),(0,v.jsx)(l,{className:`col-device-actions`,children:`Thao tác`})]})}),(0,v.jsx)(ne,{children:t?(0,v.jsx)(c,{children:(0,v.jsxs)(s,{colSpan:6,style:{textAlign:`center`,padding:`3rem`},children:[(0,v.jsx)(f,{size:24,style:{animation:`spin 1s linear infinite`,margin:`0 auto`}}),` Đang tải dữ liệu...`]})}):Ae.length===0?(0,v.jsx)(c,{children:(0,v.jsx)(s,{colSpan:6,style:{textAlign:`center`,padding:`3rem`,color:`var(--text-secondary)`},children:`Không tìm thấy thiết bị nào phù hợp.`})}):Ae.map((e,t)=>{let n=h(e,{activeFilter:w}),r=b(e.id),i=r.slice(0,2),a=Math.max(0,r.length-i.length),o=g(e).unassigned;return(0,v.jsxs)(c,{className:`device-table-row`,children:[(0,v.jsx)(s,{className:`col-device-code`,children:(0,v.jsxs)(`div`,{className:`device-code-stack`,title:String(e.id||``),children:[(i.length>0?i:[e.id]).map((e,t)=>(0,v.jsx)(`span`,{className:`device-id-cell`,children:e},`${String(e)}-${t}`)),a>0&&(0,v.jsxs)(`span`,{className:`device-id-more`,children:[`+`,a]})]})}),(0,v.jsx)(s,{className:`col-device-name`,children:(0,v.jsx)(`span`,{className:`device-name-cell`,children:e.name})}),(0,v.jsx)(s,{className:`col-device-dept`,children:(0,v.jsx)(`span`,{className:`department-badge ${o?`is-unassigned`:``}`,title:String(e.department||``),children:e.department})}),(0,v.jsx)(s,{children:(0,v.jsxs)(`span`,{className:`device-status-pill ${n.className}`,children:[(0,v.jsx)(`span`,{className:`device-status-dot`}),n.label]})}),(0,v.jsx)(s,{className:`col-device-date`,children:(0,v.jsx)(`span`,{className:`device-date-cell`,children:e.dateAdded})}),(0,v.jsx)(s,{className:`col-device-actions`,children:(0,v.jsxs)(`div`,{className:`action-buttons-cell`,children:[(0,v.jsx)(d,{variant:`secondary`,size:`sm`,icon:(0,v.jsx)(ue,{size:16}),title:`Xem chi tiết`,onClick:()=>De(`/devices/${encodeURIComponent(e.id)}`),className:`btn-icon-only`}),H&&(0,v.jsx)(d,{variant:`secondary`,size:`sm`,icon:(0,v.jsx)(Te,{size:16}),title:`Sửa`,onClick:()=>Fe(e),className:`btn-icon-only`}),(0,v.jsx)(d,{variant:`secondary`,size:`sm`,icon:(0,v.jsx)(p,{size:16}),title:`In QR`,onClick:()=>je(e),className:`btn-icon-only`})]})})]},`${e.id}-${t}`)})})]}),(0,v.jsxs)(`div`,{className:`pagination`,style:{padding:`16px 20px`,display:`flex`,justifyContent:`space-between`,alignItems:`center`},children:[(0,v.jsxs)(`span`,{style:{color:`var(--text-secondary)`,fontSize:`0.9rem`},children:[`Hiển thị `,Z.length>0?(O-1)*50+1:0,`–`,Math.min(O*50,Z.length),` / `,Z.length,` thiết bị`]}),(0,v.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`},children:[(0,v.jsx)(d,{variant:`secondary`,size:`sm`,disabled:O===1||Q===0,onClick:()=>k(e=>Math.max(1,e-1)),children:`Trang trước`}),(0,v.jsxs)(`span`,{style:{fontSize:`0.9rem`,margin:`0 8px`},children:[`Trang `,O,` / `,Q||1]}),(0,v.jsx)(d,{variant:`secondary`,size:`sm`,disabled:O>=Q||Q===0,onClick:()=>k(e=>Math.min(Q,e+1)),children:`Trang sau`})]})]})]}),A.length>0&&(0,v.jsx)(`div`,{id:`print-area`,children:A.map((e,t)=>{let n=`${window.location.origin}/devices/${encodeURIComponent(e.id)}`,r=b(e.id),i=(r.length>0?r:[String(e.id||``)]).slice(0,2),a=Math.max(0,r.length-i.length);return(0,v.jsxs)(`div`,{className:`print-page print-qr-card`,children:[(0,v.jsx)(`div`,{className:`print-qr-org`,children:`TTYT khu vực Thanh Ba`}),(0,v.jsx)(`div`,{className:`print-qr-subtitle`,children:`Hệ thống QLTTB`}),(0,v.jsx)(Ce,{value:n,size:96,level:`M`}),(0,v.jsxs)(`div`,{className:`print-qr-code-list`,children:[i.map((e,t)=>(0,v.jsx)(`span`,{children:e},`${e}-${t}`)),a>0&&(0,v.jsxs)(`span`,{className:`print-qr-hidden-count`,children:[`+`,a,` mã`]})]}),(0,v.jsx)(`div`,{className:`print-qr-name`,children:e.name}),(0,v.jsx)(`div`,{className:`print-qr-department`,children:e.department})]},`print-${e.id}-${t}`)})}),(0,v.jsx)(re,{isOpen:Ee,onClose:()=>M(!1),title:N===`add`?`➕ Thêm thiết bị mới`:`✏️ Sửa thông tin thiết bị`,size:`md`,children:(0,v.jsxs)(`form`,{onSubmit:async e=>{if(e.preventDefault(),!H){B(`❌ Bạn không có quyền thay đổi danh mục thiết bị.`);return}if(!F.name.trim()||!F.department.trim()){B(`❌ Vui lòng điền đầy đủ tên thiết bị và khoa/phòng.`);return}R(!0),B(``);let t=N===`add`?await te({name:F.name,serial:F.serial,department:F.department,dateAdded:F.dateAdded,notes:F.notes}):await i({serial:F.serial,name:F.name,department:F.department,dateAdded:F.dateAdded,notes:F.notes});R(!1),t.success?(U.success(t.message||`Thành công!`),await n(),M(!1)):B(`❌ `+(t.message||`Có lỗi xảy ra.`))},style:{display:`flex`,flexDirection:`column`,gap:`16px`},children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`,color:`var(--text-primary)`},children:`Tên thiết bị *`}),(0,v.jsx)(`input`,{name:`name`,value:F.name,onChange:$,placeholder:`VD: Máy siêu âm màu Doppler 4D`,required:!0,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`,color:`var(--text-primary)`},children:`Seri / Mã thiết bị`}),(0,v.jsx)(`input`,{name:`serial`,value:F.serial,onChange:$,placeholder:`VD: TB-001 (để trống hệ thống tự tạo)`,readOnly:N===`edit`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,background:N===`edit`?`var(--surface-50)`:`white`,boxSizing:`border-box`}})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`,color:`var(--text-primary)`},children:`Khoa/phòng sử dụng *`}),(0,v.jsx)(`input`,{name:`department`,value:F.department,onChange:$,placeholder:`VD: Khoa Chẩn đoán hình ảnh`,list:`dept-list`,required:!0,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}}),(0,v.jsx)(`datalist`,{id:`dept-list`,children:W.map(e=>(0,v.jsx)(`option`,{value:e},e))})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`,color:`var(--text-primary)`},children:`Ngày nhập / đăng kiểm`}),(0,v.jsx)(`input`,{name:`dateAdded`,value:F.dateAdded,onChange:$,placeholder:`VD: 15/03/2025`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`,color:`var(--text-primary)`},children:`Ghi chú thêm`}),(0,v.jsx)(`textarea`,{name:`notes`,value:F.notes,onChange:$,placeholder:`Nguồn vốn, nhà cung cấp, ghi chú kỹ thuật...`,rows:3,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,resize:`vertical`,boxSizing:`border-box`}})]}),z&&(0,v.jsx)(`div`,{style:{padding:`10px 14px`,borderRadius:`8px`,background:z.startsWith(`✅`)?`var(--success-light)`:`var(--danger-light)`,color:z.startsWith(`✅`)?`var(--success)`:`var(--danger)`,fontSize:`0.9rem`},children:z}),(0,v.jsxs)(`div`,{style:{display:`flex`,gap:`12px`,justifyContent:`flex-end`,marginTop:`4px`},children:[(0,v.jsx)(d,{type:`button`,variant:`secondary`,onClick:()=>M(!1),children:`Hủy`}),(0,v.jsx)(d,{type:`submit`,variant:`primary`,icon:L?(0,v.jsx)(f,{size:16,style:{animation:`spin 1s linear infinite`}}):(0,v.jsx)(de,{size:16}),disabled:L,children:L?`Đang lưu...`:N===`add`?`Thêm thiết bị`:`Lưu thay đổi`})]})]})})]})};export{x as default};
 
 
assets/DeviceProfile-CSWlLc0Y.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{T as i,V as a,c as ee,d as o,f as s,g as te,h as c,i as ne,m as l,p as u,s as re,t as ie,u as d,v as f,x as ae,y as p}from"./ui-B-VMc-s2.js";import{n as oe,t as se}from"./EvidenceLinks-A2VHKnJo.js";import{n as ce,t as m}from"./Devices-Cc89Wtut.js";import{t as le}from"./file-text-CXpdkbbP.js";import{t as ue}from"./plus-DkeqYTKZ.js";import{t as de}from"./refresh-cw-3TMtZCQy.js";import{t as fe}from"./x-Dz7E0PNg.js";import{E as pe,O as me,h as he,k as ge,m as _e,p as ve,v as ye}from"./index-C1supBxi.js";import{i as be}from"./deviceStatus-CXqVBxuC.js";import{t as xe}from"./esm-62HA5UXF.js";var Se=r(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),h=e(n(),1),g=t(),_=()=>{let{id:e}=ge(),t=me(),{isAuthenticated:n,username:r,isAdmin:_}=pe(),{devices:v,isLoading:Ce,refetch:we}=ve(),{transfers:Te,isLoading:Ee,refetch:De}=_e(),{repairs:Oe,isLoading:ke}=he(),Ae=Ce||Ee||ke,y=decodeURIComponent(e||``),b=v.find(e=>e.id===y)||null,x=b?be(b):null,je=Array.from(new Set(v.map(e=>e.department).filter(Boolean))).sort(),S=Te.filter(e=>e.deviceId===y).reverse(),C=Oe.filter(e=>e.deviceId===y),[Me,w]=(0,h.useState)(!1),[T,E]=(0,h.useState)(``),[D,O]=(0,h.useState)(``),[k,A]=(0,h.useState)(!1),[j,Ne]=(0,h.useState)(`add`),[Pe,M]=(0,h.useState)(!1),[N,P]=(0,h.useState)(null),[F,I]=(0,h.useState)(``),[L,R]=(0,h.useState)(``),[z,B]=(0,h.useState)(``),[V,H]=(0,h.useState)(``),[U,W]=(0,h.useState)(``),[G,K]=(0,h.useState)(`Chưa gửi`),[q,J]=(0,h.useState)(``),[Y,X]=(0,h.useState)(``),[Z,Q]=(0,h.useState)(``),Fe=()=>{b&&sessionStorage.setItem(`repairDeviceId`,b.id),t(`/requests?type=repair`)},Ie=async()=>{if(!n){t(`/login`,{state:{from:{pathname:`/devices/${e}`}}});return}if(!T.trim()){alert(`Vui lòng nhập khoa/phòng đích.`);return}if(!b)return;let a=await i({deviceId:b.id,toDepartment:T,reason:D,actorUsername:r});alert((a.success?`✅ `:`❌ `)+(a.message||`Có lỗi xảy ra.`)),a.success&&(await De(),w(!1),E(``),O(``))},Le=(0,g.jsxs)(`div`,{className:`info-grid`,children:[(0,g.jsx)(`div`,{className:`info-section-title`,children:`Thông tin cơ bản`}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Tên thiết bị`}),(0,g.jsx)(`span`,{className:`info-value`,children:b?.name||`—`})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Mã thiết bị`}),(0,g.jsx)(`span`,{className:`info-value`,children:b?.id||`—`})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Seri máy`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Seri Máy`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Model`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.Model||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Đơn vị tính`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Đơn vị tính`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Số lượng`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Số lượng`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Nhóm`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.Nhóm||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Phân loại`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Phân loại`]||`—`)})]}),(0,g.jsx)(`div`,{className:`info-section-title`,children:`Vị trí & trạng thái`}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Khoa/phòng sử dụng`}),(0,g.jsx)(`span`,{className:`info-value`,children:b?.department||`—`})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Hiện trạng thực tế`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Hiện trạng thực tế`]||`—`)})]}),(0,g.jsx)(`div`,{className:`info-section-title`,children:`Thông tin kỹ thuật`}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Hãng sản xuất`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Hãng SX`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Nước sản xuất`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Nước SX`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Năm sản xuất`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Năm SX`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Năm sử dụng`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Năm SD`]||`—`)})]}),(0,g.jsx)(`div`,{className:`info-section-title`,children:`Tài chính & Nguồn gốc`}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Giá trị`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.Giá||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Nguồn vốn`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.Nguồn||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Công ty cung ứng`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Công ty cung ứng`]||`—`)})]}),(0,g.jsxs)(`div`,{className:`info-item`,children:[(0,g.jsx)(`span`,{className:`info-label`,children:`Ghi chú`}),(0,g.jsx)(`span`,{className:`info-value`,children:String(b?.[`Ghi chú`]||`—`)})]})]}),Re=(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{children:(0,g.jsxs)(c,{children:[(0,g.jsx)(l,{children:`Khoa/phòng`}),(0,g.jsx)(l,{children:`Từ ngày`}),(0,g.jsx)(l,{children:`Đến ngày`}),(0,g.jsx)(l,{children:`Người bàn giao`})]})}),(0,g.jsx)(o,{children:S.length===0?(0,g.jsx)(c,{children:(0,g.jsx)(s,{colSpan:4,style:{textAlign:`center`,color:`var(--text-secondary)`,padding:`2rem`},children:`Chưa có lịch sử luân chuyển.`})}):S.map(e=>(0,g.jsxs)(c,{children:[(0,g.jsxs)(s,{children:[e.fromDepartment,` → `,(0,g.jsx)(`strong`,{children:e.toDepartment})]}),(0,g.jsx)(s,{children:e.requestedAt||e.createdAt}),(0,g.jsx)(s,{children:e.receivedAt||e.status}),(0,g.jsx)(s,{children:e.requestedByName||e.requestedBy})]},e.transferId))})]}),ze=(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{children:(0,g.jsxs)(c,{children:[(0,g.jsx)(l,{children:`Ngày`}),(0,g.jsx)(l,{children:`Mô tả lỗi`}),(0,g.jsx)(l,{children:`Trạng thái`}),(0,g.jsx)(l,{children:`Người báo`})]})}),(0,g.jsx)(o,{children:C.length===0?(0,g.jsx)(c,{children:(0,g.jsx)(s,{colSpan:4,style:{textAlign:`center`,color:`var(--text-secondary)`,padding:`2rem`},children:`Chưa có lịch sử sửa chữa cho thiết bị này.`})}):C.map((e,t)=>(0,g.jsxs)(c,{children:[(0,g.jsx)(s,{children:e.rowId||`—`}),(0,g.jsxs)(s,{children:[(0,g.jsx)(`span`,{children:oe(e.description)||`—`}),(0,g.jsx)(se,{text:e.description})]}),(0,g.jsx)(s,{children:(0,g.jsx)(f,{variant:e.status===`Đã xử lý`?`success`:e.status===`Từ chối`?`danger`:`warning`,children:e.status})}),(0,g.jsx)(s,{children:e.userName||`—`})]},e.rowId||t))})]}),Be=e=>{if(!e)return``;let t=e.split(`-`);return t.length===3?`${t[2]}/${t[1]}/${t[0]}`:e},Ve=e=>{if(!e)return``;let t=e.split(`/`);if(t.length===3)return`${t[2]}-${t[1].padStart(2,`0`)}-${t[0].padStart(2,`0`)}`;try{let t=new Date(e);if(!isNaN(t.getTime()))return t.toISOString().split(`T`)[0]}catch{}return e},He=e=>new Promise((t,n)=>{let r=new FileReader;r.readAsDataURL(e),r.onload=()=>{let e=r.result.split(`,`)[1];t(e)},r.onerror=e=>n(e)}),$=(e,t)=>{Ne(e),P(null),e===`edit`&&t?(I(t.docType||``),R(t.licenseNo||``),B(Ve(t.issuedDate||``)),H(Ve(t.expiryDate||``)),W(t.prepTime||``),K(t.status||`Chưa gửi`),J(t.responsible||``),X(t.collaborator||``),Q(t.deptManager||``)):(I(``),R(``),B(``),H(``),W(``),K(`Chưa gửi`),J(``),X(``),Q(``)),A(!0)},Ue=async e=>{if(e.preventDefault(),b){if(!F.trim()){alert(`Vui lòng chọn hoặc nhập Loại tài liệu.`);return}M(!0);try{let e=``,t=``,n=``;if(N){if(N.size>10*1024*1024){alert(`Kích thước file quá lớn (tối đa 10MB).`),M(!1);return}e=await He(N),t=N.name,n=N.type}let r=await ae({serial:b.id,docType:F.trim(),licenseNo:L.trim(),issuedDate:Be(z),expiryDate:Be(V),prepTime:U.trim(),status:G,responsible:q.trim(),collaborator:Y.trim(),deptManager:Z.trim(),fileContent:e,fileName:t,mimeType:n});alert((r.success?`✅ `:`❌ `)+(r.message||`Có lỗi xảy ra.`)),r.success&&(await we(),A(!1),P(null))}catch(e){console.error(e),alert(`❌ Đã xảy ra lỗi trong quá trình lưu tài liệu.`)}finally{M(!1)}}},We=(()=>{let e=b?.documents||[];return(0,g.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`16px`},children:[_&&(0,g.jsx)(`div`,{style:{display:`flex`,justifyContent:`flex-end`},children:(0,g.jsx)(p,{variant:`primary`,size:`sm`,icon:(0,g.jsx)(ue,{size:16}),onClick:()=>$(`add`),children:`Thêm tài liệu mới`})}),e.length===0?(0,g.jsxs)(`div`,{style:{padding:`2rem`,textAlign:`center`,color:`var(--text-secondary)`},children:[(0,g.jsx)(le,{size:48,style:{opacity:.3,marginBottom:`12px`}}),(0,g.jsx)(`p`,{children:`Thiết bị này chưa có tài liệu kiểm định / đăng kiểm nào.`})]}):(0,g.jsxs)(d,{children:[(0,g.jsx)(u,{children:(0,g.jsxs)(c,{children:[(0,g.jsx)(l,{children:`Loại tài liệu`}),(0,g.jsx)(l,{children:`Số văn bản`}),(0,g.jsx)(l,{children:`Ngày cấp`}),(0,g.jsx)(l,{children:`Hạn hiệu lực`}),(0,g.jsx)(l,{children:`Thời gian chuẩn bị`}),(0,g.jsx)(l,{children:`Trạng thái`}),(0,g.jsx)(l,{children:`Ngư���i chịu TN`}),(0,g.jsx)(l,{children:`File đính kèm`}),_&&(0,g.jsx)(l,{style:{textAlign:`right`},children:`Thao tác`})]})}),(0,g.jsx)(o,{children:e.map((e,t)=>{let n=e.daysUntilExpiry,r=`neutral`,i=``;return n!==null&&(n<0?(r=`danger`,i=`Quá hạn ${Math.abs(n)} ngày`):n<=7?(r=`danger`,i=`Còn ${n} ngày`):n<=30?(r=`warning`,i=`Còn ${n} ngày`):(r=`success`,i=`Còn ${n} ngày`)),(0,g.jsxs)(c,{children:[(0,g.jsx)(s,{children:(0,g.jsx)(`strong`,{children:e.docType||`—`})}),(0,g.jsx)(s,{children:e.licenseNo||`—`}),(0,g.jsx)(s,{children:e.issuedDate||`—`}),(0,g.jsxs)(s,{children:[e.expiryDate||`—`,i&&(0,g.jsx)(`div`,{children:(0,g.jsx)(f,{variant:r,children:i})})]}),(0,g.jsx)(s,{children:e.prepTime?`${e.prepTime} ngày`:`—`}),(0,g.jsx)(s,{children:(0,g.jsx)(f,{variant:e.status===`Đã gửi`||e.status===`Đã phê duyệt`?`success`:e.status===`Đang xử lý`?`warning`:`neutral`,children:e.status||`Chưa gửi`})}),(0,g.jsx)(s,{children:e.responsible||`—`}),(0,g.jsx)(s,{children:e.fileUrl?(0,g.jsxs)(`a`,{href:e.fileUrl,target:`_blank`,rel:`noopener noreferrer`,className:`file-link`,style:{display:`inline-flex`,alignItems:`center`,gap:`4px`,color:`var(--primary)`,fontWeight:600,textDecoration:`none`},children:[(0,g.jsx)(ce,{size:14}),`Xem file`]}):(0,g.jsx)(`span`,{style:{color:`var(--text-secondary)`,fontSize:`0.85rem`},children:`Không có file`})}),_&&(0,g.jsx)(s,{style:{textAlign:`right`},children:(0,g.jsx)(`div`,{style:{display:`flex`,gap:`8px`,justifyContent:`flex-end`},children:(0,g.jsx)(p,{variant:`secondary`,size:`sm`,style:{padding:`4px 8px`,minHeight:`auto`,height:`28px`,fontSize:`0.8rem`},icon:(0,g.jsx)(ye,{size:12}),onClick:()=>$(`edit`,e),children:`Sửa`})})})]},t)})})]})]})})(),Ge=[{id:`general`,label:`Thông tin chung`,content:Le},{id:`movement`,label:`Lịch sử luân chuyển`,content:Re},{id:`maintenance`,label:`Sửa chữa & Bảo dưỡng`,content:ze},{id:`docs`,label:`Tài liệu kiểm định (${b?.documents?.length||0})`,content:We}];return(0,g.jsxs)(`div`,{className:`device-profile-page`,children:[(0,g.jsx)(`div`,{className:`page-header`,style:{marginBottom:`16px`},children:(0,g.jsx)(p,{variant:`secondary`,size:`sm`,icon:(0,g.jsx)(Se,{size:16}),onClick:()=>t(`/devices`),children:`Quay lại`})}),(0,g.jsx)(re,{children:(0,g.jsx)(ee,{children:Ae?(0,g.jsx)(`div`,{style:{textAlign:`center`,padding:`40px`},children:`Đang tải thông tin thiết bị...`}):b?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsxs)(`div`,{className:`profile-header`,children:[(0,g.jsxs)(`div`,{className:`device-main-info`,children:[(0,g.jsx)(`div`,{className:`qr-code-box`,children:(0,g.jsx)(xe,{value:`${window.location.origin}/devices/${encodeURIComponent(b.id)}`,size:160,level:`M`,includeMargin:!0})}),(0,g.jsxs)(`div`,{className:`device-details`,children:[(0,g.jsx)(`h1`,{children:b.name}),(0,g.jsx)(`div`,{className:`device-id`,children:b.id}),(0,g.jsx)(`div`,{children:x&&(0,g.jsx)(f,{variant:x.badgeVariant,children:x.sheetStatus})})]})]}),(0,g.jsxs)(`div`,{className:`action-buttons`,children:[(0,g.jsx)(p,{variant:`danger`,icon:(0,g.jsx)(a,{size:18}),onClick:Fe,children:`Báo hỏng`}),(0,g.jsx)(p,{variant:`secondary`,icon:(0,g.jsx)(de,{size:18}),onClick:()=>w(!0),children:`Điều chuyển khoa`})]})]}),(0,g.jsx)(`div`,{style:{marginTop:`32px`},children:(0,g.jsx)(te,{tabs:Ge,defaultTab:`general`})})]}):(0,g.jsxs)(`div`,{style:{textAlign:`center`,padding:`40px`,color:`var(--danger)`},children:[`Không tìm thấy thiết bị với mã: `,(0,g.jsx)(`strong`,{children:e})]})})}),Me&&(0,g.jsx)(`div`,{style:{position:`fixed`,inset:0,background:`rgba(0,0,0,0.5)`,zIndex:1e3,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`16px`},children:(0,g.jsxs)(`div`,{style:{background:`white`,borderRadius:`12px`,width:`100%`,maxWidth:`440px`,boxShadow:`0 20px 60px rgba(0,0,0,0.3)`},children:[(0,g.jsxs)(`div`,{style:{display:`flex`,justifyContent:`space-between`,alignItems:`center`,padding:`20px 24px`,borderBottom:`1px solid var(--border)`},children:[(0,g.jsx)(`h2`,{style:{margin:0,fontSize:`1.1rem`},children:`🔄 Yêu cầu điều chuyển thiết bị`}),(0,g.jsx)(`button`,{onClick:()=>w(!1),style:{background:`none`,border:`none`,cursor:`pointer`},children:(0,g.jsx)(fe,{size:22})})]}),(0,g.jsxs)(`div`,{style:{padding:`24px`,display:`flex`,flexDirection:`column`,gap:`16px`},children:[(0,g.jsxs)(`p`,{style:{margin:0,color:`var(--text-secondary)`,fontSize:`0.9rem`},children:[`Thiết bị: `,(0,g.jsx)(`strong`,{children:b?.name}),` (`,b?.id,`)`]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`},children:`Khoa/phòng đích *`}),(0,g.jsx)(`input`,{value:T,onChange:e=>E(e.target.value),placeholder:`VD: Khoa Phẫu thuật`,list:`profile-transfer-depts`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}}),(0,g.jsx)(`datalist`,{id:`profile-transfer-depts`,children:je.filter(e=>e!==b?.department).map(e=>(0,g.jsx)(`option`,{value:e},e))})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.9rem`},children:`Ghi chú`}),(0,g.jsx)(`textarea`,{value:D,onChange:e=>O(e.target.value),rows:3,placeholder:`Lý do điều chuyển...`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`,resize:`vertical`}})]}),(0,g.jsxs)(`div`,{style:{display:`flex`,gap:`12px`,justifyContent:`flex-end`},children:[(0,g.jsx)(p,{variant:`secondary`,onClick:()=>w(!1),children:`Hủy`}),(0,g.jsx)(p,{variant:`primary`,icon:(0,g.jsx)(m,{size:16}),onClick:Ie,children:`Ghi nhận điều chuyển`})]})]})]})}),k&&(0,g.jsx)(ne,{isOpen:k,onClose:()=>A(!1),title:j===`add`?`📄 Thêm tài liệu kiểm định mới`:`📝 Sửa thông tin tài liệu`,size:`lg`,children:(0,g.jsxs)(`form`,{onSubmit:Ue,children:[(0,g.jsxs)(`div`,{style:{display:`grid`,gridTemplateColumns:`1fr 1fr`,gap:`16px`,marginBottom:`20px`},children:[(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Loại tài liệu *`}),(0,g.jsx)(`input`,{type:`text`,value:F,onChange:e=>I(e.target.value),disabled:j===`edit`,placeholder:`VD: Kiểm định, Hiệu chuẩn...`,required:!0,list:`doc-types-list`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}}),(0,g.jsxs)(`datalist`,{id:`doc-types-list`,children:[(0,g.jsx)(`option`,{value:`Kiểm định`}),(0,g.jsx)(`option`,{value:`Hiệu chuẩn`}),(0,g.jsx)(`option`,{value:`Kiểm tra định kỳ`}),(0,g.jsx)(`option`,{value:`Bảo dưỡng định kỳ`})]})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Số văn bản / số đăng kiểm`}),(0,g.jsx)(`input`,{type:`text`,value:L,onChange:e=>R(e.target.value),placeholder:`VD: KD-12345`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Ngày cấp / ngày đăng kiểm`}),(0,g.jsx)(`input`,{type:`date`,value:z,onChange:e=>B(e.target.value),style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Hạn đăng kiểm / hạn hiệu lực`}),(0,g.jsx)(`input`,{type:`date`,value:V,onChange:e=>H(e.target.value),style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Thời gian chuẩn bị hồ sơ (ngày)`}),(0,g.jsx)(`input`,{type:`number`,value:U,onChange:e=>W(e.target.value),placeholder:`VD: 30`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Trạng thái hồ sơ`}),(0,g.jsxs)(`select`,{value:G,onChange:e=>K(e.target.value),style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`,background:`var(--surface)`,color:`var(--text-primary)`},children:[(0,g.jsx)(`option`,{value:`Chưa gửi`,children:`Chưa gửi`}),(0,g.jsx)(`option`,{value:`Đang xử lý`,children:`Đang xử lý`}),(0,g.jsx)(`option`,{value:`Đã gửi`,children:`Đã gửi`}),(0,g.jsx)(`option`,{value:`Đã phê duyệt`,children:`Đã phê duyệt`})]})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Người chịu trách nhiệm`}),(0,g.jsx)(`input`,{type:`text`,value:q,onChange:e=>J(e.target.value),placeholder:`VD: Nguyễn Văn A`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Phối hợp thực hiện`}),(0,g.jsx)(`input`,{type:`text`,value:Y,onChange:e=>X(e.target.value),placeholder:`VD: Trần Thị B`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsxs)(`div`,{style:{gridColumn:`1 / -1`},children:[(0,g.jsx)(`label`,{style:{display:`block`,fontWeight:`600`,marginBottom:`6px`,fontSize:`0.85rem`,color:`var(--text-secondary)`},children:`Giao quản lý tại khoa`}),(0,g.jsx)(`input`,{type:`text`,value:Z,onChange:e=>Q(e.target.value),placeholder:`VD: Khoa Cấp cứu`,style:{width:`100%`,padding:`10px 14px`,border:`1.5px solid var(--border)`,borderRadius:`8px`,fontSize:`0.95rem`,boxSizing:`border-box`}})]}),(0,g.jsx)(`div`,{style:{gridColumn:`1 / -1`,marginTop:`8px`},children:(0,g.jsx)(ie,{selectedFile:N,onFileSelect:P,label:j===`edit`?`Thay thế file tài liệu (để trống nếu giữ nguyên file cũ)`:`File tài liệu đính kèm`})})]}),(0,g.jsxs)(`div`,{style:{display:`flex`,gap:`12px`,justifyContent:`flex-end`,borderTop:`1px solid var(--border)`,paddingTop:`16px`},children:[(0,g.jsx)(p,{variant:`secondary`,type:`button`,onClick:()=>A(!1),children:`Hủy`}),(0,g.jsx)(p,{variant:`primary`,type:`submit`,icon:(0,g.jsx)(m,{size:16}),children:`Lưu tài liệu`})]})]})}),Pe&&(0,g.jsxs)(`div`,{style:{position:`fixed`,inset:0,background:`rgba(15, 23, 42, 0.4)`,backdropFilter:`blur(8px)`,WebkitBackdropFilter:`blur(8px)`,zIndex:9999,display:`flex`,flexDirection:`column`,alignItems:`center`,justifyContent:`center`,gap:`16px`,color:`white`,animation:`fadeIn 0.2s ease-out`},children:[(0,g.jsx)(`div`,{style:{width:`50px`,height:`50px`,borderRadius:`50%`,border:`4px solid rgba(255, 255, 255, 0.3)`,borderTopColor:`white`,animation:`spin 1s linear infinite`}}),(0,g.jsx)(`div`,{style:{fontWeight:`700`,fontSize:`1.1rem`,textShadow:`0 2px 4px rgba(0,0,0,0.2)`},children:`Đang lưu thông tin và tải tài liệu lên Google Drive...`}),(0,g.jsx)(`div`,{style:{fontSize:`0.85rem`,opacity:.8,textShadow:`0 1px 2px rgba(0,0,0,0.2)`},children:`Vui lòng không đóng hoặc tải lại trang web.`})]})]})};export{_ as default};
 
 
assets/Devices-Cc89Wtut.js DELETED
@@ -1 +0,0 @@
1
- import{t as e}from"./createLucideIcon-Ds6Kxw25.js";var t=e(`eye`,[[`path`,{d:`M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0`,key:`1nclc0`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),n=e(`save`,[[`path`,{d:`M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z`,key:`1c8476`}],[`path`,{d:`M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7`,key:`1ydtos`}],[`path`,{d:`M7 3v4a1 1 0 0 0 1 1h7`,key:`t51u73`}]]);export{t as n,n as t};
 
 
assets/Devices-DMAvR0Dh.css DELETED
@@ -1 +0,0 @@
1
- .page-header{justify-content:space-between;align-items:flex-start;gap:20px;margin-bottom:16px;display:flex}.page-title{color:var(--text-primary);letter-spacing:0;font-size:1.45rem;font-weight:820}.action-buttons{align-items:center;gap:8px;display:flex}.toolbar{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:18px;display:flex}.filter-group{flex:1;gap:10px;display:flex}.filter-select{border:1px solid var(--border);border-radius:var(--radius-md);background-color:var(--surface);min-height:38px;color:var(--text-primary);min-width:200px;transition:all var(--transition-fast);appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235b6678' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");background-position:right 11px center;background-repeat:no-repeat;outline:none;padding:8px 34px 8px 12px}.filter-select:focus{border-color:var(--primary);box-shadow:0 0 0 3px #0f766e1c}.search-box{flex:1;max-width:400px}.pagination{border-top:1px solid var(--border);border-bottom-left-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md);background-color:#fafafa;justify-content:space-between;align-items:center;gap:16px;margin-top:16px;padding:16px 24px;display:flex}.device-list-page .stats-grid{grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;margin-bottom:18px;display:grid}.device-list-page .stat-card{border:1px solid var(--border);min-height:96px;color:var(--text-primary);box-shadow:none;transition:transform var(--transition-normal), box-shadow var(--transition-normal);border-left-width:4px;border-radius:8px;flex-direction:row;align-items:center;gap:14px;padding:16px 18px;display:flex}.device-list-page .stat-card:hover{transform:translateY(-1px);box-shadow:0 10px 24px #0f172a12}.primary-gradient{background:#fff;border-left:4px solid #2563eb}.success-gradient{background:#fff;border-left:4px solid #10b981}.warning-gradient{background:#fff;border-left:4px solid #f59e0b}.danger-gradient{background:#fff;border-left:4px solid #ef4444}.compliance-gradient{background:#fff;border-left:4px solid #7c3aed}.expired-gradient{background:#fff;border-left:4px solid #b91c1c}.device-list-page .stat-icon-wrapper{width:42px;height:42px;color:var(--primary);background:#eef8f7;border-radius:8px;justify-content:center;align-items:center;display:flex}.device-list-page .stat-content h3{letter-spacing:0;font-variant-numeric:tabular-nums;margin:0;font-size:1.55rem;font-weight:860;line-height:1.1}.device-list-page .stat-content p{color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em;margin:2px 0 0;font-size:.78rem;font-weight:700}.device-inventory-table.table-container{box-shadow:none;border-color:var(--border);border-radius:8px}.device-inventory-table .data-table{table-layout:fixed}.device-inventory-table .data-table th,.device-inventory-table .data-table td{vertical-align:middle;padding:12px 16px}.device-inventory-table .data-table th{color:#334155;letter-spacing:.08em;border-bottom:1px solid var(--border);background:#f6f9fb;font-size:.72rem}.device-inventory-table .data-table tbody tr:nth-child(2n){background:#fbfdfe}.device-table-row{transition:all var(--transition-fast)}.device-table-row:hover{box-shadow:inset 4px 0 0 var(--primary);background-color:#f2fbf8!important}.device-code-stack{align-items:center;gap:6px;min-width:0;max-width:100%;display:flex}.device-id-cell{max-width:108px;color:var(--primary-dark);border:1px solid var(--border);white-space:nowrap;text-overflow:ellipsis;background:#fff;border-radius:5px;padding:4px 7px;font-family:Cascadia Mono,SFMono-Regular,Consolas,monospace;font-size:.78rem;line-height:1.35;display:inline-block;overflow:hidden}.device-id-more{color:#64748b;border:1px solid var(--border);background:#eef2f6;border-radius:999px;flex:none;padding:3px 7px;font-size:.72rem;font-weight:800}.device-name-cell{color:var(--text-primary);font-size:.92rem;font-weight:700;line-height:1.35;display:block}.department-badge{color:#334155;text-overflow:ellipsis;background:#f5f8fa;border-radius:6px;align-items:center;max-width:100%;min-height:30px;padding:5px 9px;font-size:.78rem;font-weight:700;line-height:1.25;display:inline-flex;overflow:hidden}.department-badge.is-unassigned{color:#64748b;background:#eef2f6}.device-status-pill{white-space:nowrap;border-radius:999px;align-items:center;gap:7px;min-height:30px;padding:5px 9px;font-size:.78rem;font-weight:760;display:inline-flex}.device-status-pill.is-ok{color:#047857;background:#ecfdf5}.device-status-pill.is-warning{color:#a16207;background:#fffbeb}.device-status-pill.is-danger{color:#be123c;background:#fff1f2}.device-status-pill.is-neutral{color:#475569;background:#f1f5f9}.device-status-dot{background:currentColor;border-radius:999px;width:7px;height:7px}.device-date-cell{color:var(--text-primary);font-size:.84rem;font-weight:700}.action-buttons-cell{justify-content:flex-end;gap:6px;display:flex}.btn-icon-only{border-radius:var(--radius-sm);border-color:var(--border);background:#f8fbfd;width:32px;height:32px;padding:0}.btn-icon-only:hover:not(:disabled){color:var(--primary-dark);background:#e8f4f2}.col-device-code,.col-device-name{width:26%}.col-device-dept,.col-device-status{width:12%}.col-device-date{width:10%}.col-device-actions{text-align:right;width:14%}@media (width<=920px){.page-header{flex-direction:column;align-items:flex-start;gap:14px}.page-title{max-width:100%;line-height:1.18}.action-buttons{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%;display:grid}.action-buttons .btn{width:100%;min-height:44px;padding-inline:12px}.action-buttons .btn-primary{grid-column:1/-1}.toolbar{gap:12px}.filter-group,.search-box{flex:100%;max-width:none}.filter-group{grid-template-columns:1fr;gap:10px;display:grid}.filter-select{width:100%;min-width:0}.device-inventory-table .data-table{min-width:900px}.col-device-code{width:260px}.col-device-name{width:220px}.col-device-dept,.col-device-status{width:140px}.col-device-date{width:110px}.col-device-actions{width:130px}}.profile-header{justify-content:space-between;align-items:flex-start;margin-bottom:24px;display:flex}.device-main-info{gap:24px;display:flex}.qr-code-box{border:1px solid var(--border);border-radius:var(--radius-md);background-color:#fff;justify-content:center;align-items:center;width:120px;height:120px;padding:8px;display:flex}.qr-code-box img{object-fit:contain;width:100%;height:100%}.device-details h1{color:var(--text-primary);margin-bottom:8px;font-size:1.5rem}.device-id{color:var(--text-secondary);background:var(--background);border-radius:4px;margin-bottom:12px;padding:4px 8px;font-family:monospace;font-size:1.1rem;display:inline-block}.info-grid{grid-template-columns:repeat(3,1fr);gap:14px 20px;display:grid}.info-section-title{color:var(--primary,#0056b3);text-transform:uppercase;letter-spacing:.06em;border-bottom:2px solid var(--primary,#0056b3);grid-column:1/-1;margin-top:8px;padding-bottom:6px;font-size:.8rem;font-weight:700}.info-section-title:first-child{margin-top:0}.info-item{flex-direction:column;display:flex}.info-label{color:var(--text-secondary);margin-bottom:3px;font-size:.8rem}.info-value{color:var(--text-primary);word-break:break-word;font-weight:500}@media (width<=768px){.info-grid{grid-template-columns:repeat(2,1fr)}}@media (width<=480px){.info-grid{grid-template-columns:1fr}}@media print{@page{size:A4;margin:8mm}body *{visibility:hidden}#print-area,#print-area *{visibility:visible}#print-area{grid-template-columns:repeat(3,58mm);grid-auto-rows:74mm;justify-content:start;align-items:start;gap:5mm 6mm;width:100%;margin:0;padding:0;display:grid;position:absolute;top:0;left:0}.print-page,.print-qr-card{page-break-inside:avoid;break-inside:avoid;box-sizing:border-box;text-align:center;color:#0f172a;border:1px dashed #94a3b8;border-radius:3mm;flex-direction:column;align-items:center;width:58mm;min-height:74mm;padding:5mm 4mm;font-family:Arial,sans-serif;display:flex;overflow:hidden}.print-qr-org{text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap;max-width:100%;font-size:9px;font-weight:700;line-height:1.1;overflow:hidden}.print-qr-subtitle{color:#334155;margin:1mm 0 3mm;font-size:8px;line-height:1}.print-qr-card svg{flex:none;width:25mm;height:25mm}.print-qr-code-list{overflow-wrap:anywhere;word-break:break-word;flex-wrap:wrap;justify-content:center;gap:1mm;max-width:100%;max-height:14mm;margin-top:3mm;display:flex;overflow:hidden}.print-qr-code-list span{color:#0f172a;background:#f8fafc;border:1px solid #cbd5e1;border-radius:2mm;max-width:100%;padding:1mm 1.5mm;font-family:Cascadia Mono,Consolas,monospace;font-size:8px;font-weight:700;line-height:1.15}.print-qr-hidden-count{color:#475569!important;font-family:Arial,sans-serif!important}.print-qr-name{color:#0f172a;overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical;max-width:100%;margin:2mm 0 1mm;font-size:9px;font-weight:700;line-height:1.2;display:-webkit-box;overflow:hidden}.print-qr-department{color:#334155;overflow-wrap:anywhere;-webkit-line-clamp:1;-webkit-box-orient:vertical;max-width:100%;margin:0;font-size:8px;font-weight:600;line-height:1.2;display:-webkit-box;overflow:hidden}}.quick-filters-chips{flex-wrap:wrap;gap:7px;padding:0 18px 16px;display:flex}.quick-filters-chips .chip{border-radius:var(--radius-sm);border:1px solid var(--border);min-height:32px;color:var(--text-secondary);cursor:pointer;transition:all var(--transition-fast);background:#fff;padding:6px 11px;font-size:.8rem;font-weight:760}.quick-filters-chips .chip:hover{background:#f7fafb;border-color:#bccbd6}.quick-filters-chips .chip.active{background:var(--primary);border-color:var(--primary);color:#fff}.quick-filters-chips .chip.chip-success.active{background:var(--success);border-color:var(--success);color:#fff}.quick-filters-chips .chip.chip-warning.active{background:var(--warning);border-color:var(--warning);color:#fff}.quick-filters-chips .chip.chip-danger.active{background:var(--danger);border-color:var(--danger);color:#fff}.quick-filters-chips .chip.chip-neutral.active{background:var(--text-primary);border-color:var(--text-primary);color:#fff}
 
 
assets/EvidenceLinks-A2VHKnJo.js DELETED
@@ -1,2 +0,0 @@
1
- import"./chunk-CaILmz35.js";import{n as e,r as t,t as n}from"./createLucideIcon-Ds6Kxw25.js";var r=n(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),i=n(`image`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}]]);t();var a=/^\s*\[([^\]]*Ảnh[^\]]*)\]\s*:\s*(https?:\/\/\S+)\s*$/i,o=e=>String(e??``).split(/\r?\n/).map(e=>e.match(a)).filter(e=>!!e).map(e=>({label:e[1].trim(),url:e[2].trim().replace(/[.,;]+$/g,``)})),s=e=>String(e??``).split(/\r?\n/).filter(e=>!a.test(e)).join(`
2
- `).trim(),c=e(),l=({text:e,className:t=``})=>{let n=o(e);return n.length===0?null:(0,c.jsx)(`div`,{className:`evidence-links ${t}`.trim(),"aria-label":`Ảnh minh chứng`,children:n.map((e,t)=>(0,c.jsxs)(`a`,{className:`evidence-link`,href:e.url,target:`_blank`,rel:`noreferrer`,children:[(0,c.jsx)(i,{size:14}),(0,c.jsx)(`span`,{children:e.label}),(0,c.jsx)(r,{size:12})]},`${e.url}-${t}`))})};export{s as n,l as t};
 
 
 
assets/EvidenceLinks-BVpp2_oz.css DELETED
@@ -1 +0,0 @@
1
- .evidence-links{flex-wrap:wrap;gap:6px;margin-top:8px;display:flex}.evidence-link{border:1px solid color-mix(in srgb, var(--primary) 28%, var(--border));background:color-mix(in srgb, var(--primary) 7%, var(--surface));width:fit-content;max-width:100%;color:var(--primary-dark);border-radius:8px;align-items:center;gap:5px;padding:5px 8px;font-size:.78rem;font-weight:760;line-height:1.2;text-decoration:none;display:inline-flex}.evidence-link:hover,.evidence-link:focus-visible{border-color:var(--primary);background:color-mix(in srgb, var(--primary) 12%, var(--surface));color:var(--primary);outline:none}
 
 
assets/GspLog-BEwbqtYT.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{A as i,S as a,V as o,W as s,c,l,s as u,y as d,z as f}from"./ui-B-VMc-s2.js";import{t as p}from"./plus-DkeqYTKZ.js";import{t as m}from"./refresh-cw-3TMtZCQy.js";import{E as h,_ as g}from"./index-C1supBxi.js";import{n as _}from"./dist-C0DRywKl.js";var v=r(`calendar`,[[`path`,{d:`M8 2v4`,key:`1cmpym`}],[`path`,{d:`M16 2v4`,key:`4m81vk`}],[`rect`,{width:`18`,height:`18`,x:`3`,y:`4`,rx:`2`,key:`1hopcy`}],[`path`,{d:`M3 10h18`,key:`8toen8`}]]),y=r(`droplets`,[[`path`,{d:`M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z`,key:`1ptgy4`}],[`path`,{d:`M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97`,key:`1sl1rz`}]]),b=e(n(),1),x=t(),S={tempKho:{min:15,max:30},tempTuLanh:{min:2,max:8},humidity:{min:40,max:75}},C=()=>{let[e,t]=(0,b.useState)([]),[n,r]=(0,b.useState)(!0),[C,w]=(0,b.useState)(!1),[T,E]=(0,b.useState)(`chart`),[D,O]=(0,b.useState)(30),{name:k}=h(),[A,j]=(0,b.useState)({shift:`Sáng`,tempKho:``,tempTuLanh:``,humidity:``,note:``}),M=(0,b.useCallback)(async()=>{r(!0);try{t(await i())}catch{alert(`Lỗi tải dữ liệu nhật ký GSP.`)}finally{r(!1)}},[]);(0,b.useEffect)(()=>{M()},[M]);let N=async e=>{if(e.preventDefault(),!k)return alert(`Vui lòng đăng nhập!`);w(!0);try{let e=await a({...A,tempKho:parseFloat(A.tempKho),tempTuLanh:parseFloat(A.tempTuLanh),humidity:parseFloat(A.humidity),recorder:k});e.success?(alert(`✅ Đã ghi nhận thành công!`),j({shift:`Sáng`,tempKho:``,tempTuLanh:``,humidity:``,note:``}),M()):alert(`❌ Lỗi: `+e.message)}catch{alert(`Lỗi kết nối mạng!`)}w(!1)},P=new Date;P.setDate(P.getDate()-D);let F=e.filter(e=>{let t=f(e.date);return t?t>=P:!1}).sort((e,t)=>(f(e.date)?.getTime()??0)-(f(t.date)?.getTime()??0)),I={labels:F.map(e=>{let t=f(e.date);return t?`${t.getDate()}/${t.getMonth()+1} ${e.shift}`:`${e.date} ${e.shift}`}),datasets:[{label:`Nhiệt độ kho (°C)`,data:F.map(e=>e.tempKho),borderColor:`#f59e0b`,backgroundColor:`rgba(245, 158, 11, 0.08)`,tension:.4,fill:!0,pointRadius:4,pointHoverRadius:7},{label:`Nhiệt độ tủ lạnh (°C)`,data:F.map(e=>e.tempTuLanh),borderColor:`#3b82f6`,backgroundColor:`rgba(59, 130, 246, 0.05)`,tension:.4,fill:!1,pointRadius:4,pointHoverRadius:7},{label:`Độ ẩm kho (%)`,data:F.map(e=>e.humidity),borderColor:`#0d9488`,backgroundColor:`rgba(13, 148, 136, 0.05)`,tension:.4,fill:!1,pointRadius:4,pointHoverRadius:7,yAxisID:`y1`}]},L={responsive:!0,maintainAspectRatio:!1,interaction:{mode:`index`,intersect:!1},plugins:{legend:{position:`top`},tooltip:{callbacks:{label:e=>{let t=Number(e.parsed.y),n=e.datasetIndex===2?`%`:`°C`,r=``;return r=e.datasetIndex===0?t<S.tempKho.min||t>S.tempKho.max?` ⚠️ NGOÀI GIỚI HẠN`:` ✅`:e.datasetIndex===1?t<S.tempTuLanh.min||t>S.tempTuLanh.max?` ⚠️ NGOÀI GIỚI HẠN`:` ✅`:t<S.humidity.min||t>S.humidity.max?` ⚠️ NGOÀI GIỚI HẠN`:` ✅`,`${e.dataset.label}: ${t}${n}${r}`}}}},scales:{y:{title:{display:!0,text:`Nhiệt độ (°C)`},grid:{color:`rgba(0,0,0,0.05)`}},y1:{position:`right`,title:{display:!0,text:`Độ ẩm (%)`},grid:{drawOnChartArea:!1},min:0,max:100}}},R=e.filter(e=>e.tempKho<S.tempKho.min||e.tempKho>S.tempKho.max||e.tempTuLanh<S.tempTuLanh.min||e.tempTuLanh>S.tempTuLanh.max||e.humidity<S.humidity.min||e.humidity>S.humidity.max);return(0,x.jsxs)(`div`,{className:`dashboard-page`,children:[(0,x.jsxs)(`div`,{className:`dashboard-header`,children:[(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`h1`,{className:`dashboard-title`,children:`Nhật ký nhiệt độ / độ ẩm kho (GSP)`}),(0,x.jsx)(`p`,{className:`dashboard-subtitle`,children:`Tiêu chuẩn: Kho thường 15-30°C | Tủ lạnh 2-8°C | Độ ẩm 40-75%`})]}),(0,x.jsx)(d,{variant:`secondary`,icon:(0,x.jsx)(m,{size:16}),onClick:M,children:`Làm mới`})]}),(0,x.jsxs)(`div`,{className:`stats-grid gsp-stats-grid`,children:[(0,x.jsxs)(u,{className:`stat-card gradient-teal`,children:[(0,x.jsx)(`div`,{className:`stat-icon`,children:(0,x.jsx)(g,{size:26})}),(0,x.jsxs)(`div`,{className:`stat-info`,children:[(0,x.jsx)(`span`,{className:`stat-value`,children:F.length>0?F[F.length-1].tempKho+`°C`:`--`}),(0,x.jsx)(`span`,{className:`stat-label`,children:`Nhiệt độ kho (mới nhất)`})]})]}),(0,x.jsxs)(u,{className:`stat-card gradient-blue`,children:[(0,x.jsx)(`div`,{className:`stat-icon`,children:(0,x.jsx)(g,{size:26})}),(0,x.jsxs)(`div`,{className:`stat-info`,children:[(0,x.jsx)(`span`,{className:`stat-value`,children:F.length>0?F[F.length-1].tempTuLanh+`°C`:`--`}),(0,x.jsx)(`span`,{className:`stat-label`,children:`Nhiệt độ tủ lạnh (mới nhất)`})]})]}),(0,x.jsxs)(u,{className:`stat-card ${R.length>0?`gradient-rose`:`gradient-teal`}`,children:[(0,x.jsx)(`div`,{className:`stat-icon`,children:R.length>0?(0,x.jsx)(o,{size:26}):(0,x.jsx)(s,{size:26})}),(0,x.jsxs)(`div`,{className:`stat-info`,children:[(0,x.jsx)(`span`,{className:`stat-value`,children:R.length>0?R.length+` lần`:`Đạt`}),(0,x.jsx)(`span`,{className:`stat-label`,children:R.length>0?`Vi phạm GSP`:`Tất cả trong giới hạn`})]})]})]}),(0,x.jsxs)(`div`,{className:`gsp-layout`,children:[(0,x.jsxs)(u,{children:[(0,x.jsx)(l,{title:(0,x.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`},children:[(0,x.jsx)(p,{size:18}),` Ghi nhận ca mới`]})}),(0,x.jsxs)(c,{children:[(0,x.jsxs)(`form`,{onSubmit:N,className:`gsp-form`,children:[(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`label`,{className:`gsp-form-label`,children:`Ca làm việc`}),(0,x.jsxs)(`select`,{value:A.shift,onChange:e=>j({...A,shift:e.target.value}),className:`gsp-form-input`,children:[(0,x.jsx)(`option`,{value:`Sáng`,children:`☀️ Ca sáng (7h - 11h30)`}),(0,x.jsx)(`option`,{value:`Chiều`,children:`🌇 Ca chiều (13h - 16h30)`})]})]}),(0,x.jsxs)(`div`,{children:[(0,x.jsxs)(`label`,{className:`gsp-form-label`,children:[(0,x.jsx)(g,{size:14,style:{marginRight:4}}),`Nhiệt độ kho thường (°C) `,(0,x.jsx)(`span`,{style:{color:`var(--warning)`,fontSize:`0.7rem`},children:`15 - 30°C`})]}),(0,x.jsx)(`input`,{type:`number`,step:`0.1`,required:!0,value:A.tempKho,onChange:e=>j({...A,tempKho:e.target.value}),placeholder:`VD: 25.5`,className:`gsp-form-input`}),A.tempKho&&(parseFloat(A.tempKho)<15||parseFloat(A.tempKho)>30)&&(0,x.jsx)(`div`,{className:`gsp-violation-hint`,children:`⚠️ Ngoài giới hạn GSP! Cần báo cáo ngay.`})]}),(0,x.jsxs)(`div`,{children:[(0,x.jsxs)(`label`,{className:`gsp-form-label`,children:[(0,x.jsx)(g,{size:14,style:{marginRight:4}}),`Nhiệt độ tủ lạnh (°C) `,(0,x.jsx)(`span`,{style:{color:`var(--warning)`,fontSize:`0.7rem`},children:`2 - 8°C`})]}),(0,x.jsx)(`input`,{type:`number`,step:`0.1`,required:!0,value:A.tempTuLanh,onChange:e=>j({...A,tempTuLanh:e.target.value}),placeholder:`VD: 5.0`,className:`gsp-form-input`}),A.tempTuLanh&&(parseFloat(A.tempTuLanh)<2||parseFloat(A.tempTuLanh)>8)&&(0,x.jsx)(`div`,{className:`gsp-violation-hint`,children:`⚠️ Ngoài giới hạn GSP! Tủ lạnh gặp sự cố.`})]}),(0,x.jsxs)(`div`,{children:[(0,x.jsxs)(`label`,{className:`gsp-form-label`,children:[(0,x.jsx)(y,{size:14,style:{marginRight:4}}),`Độ ẩm kho (%) `,(0,x.jsx)(`span`,{style:{color:`var(--warning)`,fontSize:`0.7rem`},children:`40 - 75%`})]}),(0,x.jsx)(`input`,{type:`number`,step:`1`,required:!0,value:A.humidity,onChange:e=>j({...A,humidity:e.target.value}),placeholder:`VD: 60`,className:`gsp-form-input`}),A.humidity&&(parseFloat(A.humidity)<40||parseFloat(A.humidity)>75)&&(0,x.jsx)(`div`,{className:`gsp-violation-hint`,children:`⚠️ Ngoài giới hạn GSP!`})]}),(0,x.jsxs)(`div`,{children:[(0,x.jsx)(`label`,{className:`gsp-form-label`,children:`Ghi chú (nếu có)`}),(0,x.jsx)(`textarea`,{value:A.note,onChange:e=>j({...A,note:e.target.value}),placeholder:`Biện pháp xử lý nếu vượt giới hạn...`,rows:2,className:`gsp-form-input`})]}),(0,x.jsx)(d,{type:`submit`,variant:`primary`,disabled:C,children:C?`Đang lưu...`:`💾 Lưu nhật ký`})]}),(0,x.jsxs)(`div`,{className:`gsp-standards-box`,children:[(0,x.jsx)(`strong`,{children:`📋 Tiêu chuẩn GSP:`}),(0,x.jsxs)(`ul`,{children:[(0,x.jsxs)(`li`,{children:[`Kho thường: `,(0,x.jsx)(`strong`,{children:`15°C – 30°C`})]}),(0,x.jsxs)(`li`,{children:[`Tủ lạnh bảo quản: `,(0,x.jsx)(`strong`,{children:`2°C – 8°C`})]}),(0,x.jsxs)(`li`,{children:[`Độ ẩm: `,(0,x.jsx)(`strong`,{children:`40% – 75%`})]}),(0,x.jsxs)(`li`,{children:[`Ghi chép: `,(0,x.jsx)(`strong`,{children:`2 lần/ngày`}),` (sáng & chiều)`]})]})]})]})]}),(0,x.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`1.5rem`},children:[(0,x.jsxs)(u,{children:[(0,x.jsx)(l,{title:(0,x.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`},children:[(0,x.jsx)(v,{size:18}),` Biểu đồ theo dõi dao động`]}),action:(0,x.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`},children:[(0,x.jsxs)(`select`,{value:D,onChange:e=>O(parseInt(e.target.value)),className:`gsp-period-select`,children:[(0,x.jsx)(`option`,{value:7,children:`7 ngày`}),(0,x.jsx)(`option`,{value:14,children:`14 ngày`}),(0,x.jsx)(`option`,{value:30,children:`30 ngày`}),(0,x.jsx)(`option`,{value:60,children:`60 ngày`})]}),(0,x.jsxs)(`div`,{className:`gsp-view-toggle`,children:[(0,x.jsx)(`button`,{onClick:()=>E(`chart`),className:T===`chart`?`active`:``,children:`Biểu đồ`}),(0,x.jsx)(`button`,{onClick:()=>E(`table`),className:T===`table`?`active`:``,children:`Bảng`})]})]})}),(0,x.jsx)(c,{children:n?(0,x.jsx)(`div`,{style:{textAlign:`center`,padding:`3rem`,color:`var(--text-secondary)`},children:`Đang tải dữ liệu...`}):F.length===0?(0,x.jsxs)(`div`,{style:{textAlign:`center`,padding:`3rem`,color:`var(--text-secondary)`},children:[`Chưa có dữ liệu trong `,D,` ngày qua. Hãy bắt đầu ghi chép!`]}):T===`chart`?(0,x.jsx)(`div`,{className:`gsp-chart-container`,children:(0,x.jsx)(_,{data:I,options:L})}):(0,x.jsx)(`div`,{className:`gsp-table-container`,children:(0,x.jsxs)(`table`,{className:`gsp-table`,children:[(0,x.jsx)(`thead`,{children:(0,x.jsx)(`tr`,{style:{background:`var(--bg-secondary)`},children:[`Ngày`,`Ca`,`Kho (°C)`,`Tủ lạnh (°C)`,`Độ ẩm (%)`,`Ghi chú`,`Người ghi`].map(e=>(0,x.jsx)(`th`,{children:e},e))})}),(0,x.jsx)(`tbody`,{children:[...F].reverse().map((e,t)=>{let n=e.tempKho>=15&&e.tempKho<=30,r=e.tempTuLanh>=2&&e.tempTuLanh<=8,i=e.humidity>=40&&e.humidity<=75;return(0,x.jsxs)(`tr`,{className:!n||!r||!i?`has-violation`:``,children:[(0,x.jsx)(`td`,{children:f(e.date)?.toLocaleDateString(`vi-VN`)||e.date}),(0,x.jsx)(`td`,{children:e.shift}),(0,x.jsxs)(`td`,{className:n?``:`violation-value`,children:[e.tempKho,`°C `,!n&&`⚠️`]}),(0,x.jsxs)(`td`,{className:r?``:`violation-value`,children:[e.tempTuLanh,`°C `,!r&&`⚠️`]}),(0,x.jsxs)(`td`,{className:i?``:`violation-value`,children:[e.humidity,`% `,!i&&`⚠️`]}),(0,x.jsx)(`td`,{className:`gsp-note-cell`,children:e.note||`--`}),(0,x.jsx)(`td`,{className:`gsp-note-cell`,children:e.recorder||`--`})]},t)})})]})})})]}),R.length>0&&(0,x.jsxs)(u,{children:[(0,x.jsx)(l,{title:(0,x.jsxs)(`div`,{style:{display:`flex`,gap:`8px`,alignItems:`center`,color:`var(--danger)`},children:[(0,x.jsx)(o,{size:18}),` Cảnh báo vi phạm GSP (`,R.length,` lần)`]})}),(0,x.jsx)(c,{children:(0,x.jsxs)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`8px`},children:[R.slice(0,5).map((e,t)=>(0,x.jsxs)(`div`,{className:`gsp-violation-card`,children:[(0,x.jsxs)(`strong`,{children:[f(e.date)?.toLocaleDateString(`vi-VN`)||e.date,` - `,e.shift,`:`]}),e.tempKho<15||e.tempKho>30?(0,x.jsxs)(`span`,{style:{marginLeft:8,color:`var(--danger)`},children:[`Kho `,e.tempKho,`°C`]}):null,e.tempTuLanh<2||e.tempTuLanh>8?(0,x.jsxs)(`span`,{style:{marginLeft:8,color:`var(--danger)`},children:[`Tủ lạnh `,e.tempTuLanh,`°C`]}):null,e.humidity<40||e.humidity>75?(0,x.jsxs)(`span`,{style:{marginLeft:8,color:`var(--danger)`},children:[`Ẩm `,e.humidity,`%`]}):null,e.note&&(0,x.jsxs)(`span`,{style:{marginLeft:8,color:`var(--text-secondary)`},children:[`— `,e.note]})]},t)),R.length>5&&(0,x.jsxs)(`div`,{style:{textAlign:`center`,fontSize:`0.8rem`,color:`var(--text-secondary)`},children:[`... và `,R.length-5,` lần khác`]})]})})]})]})]})]})};export{C as default};
 
 
assets/GspLog-Bk61_0BY.css DELETED
@@ -1 +0,0 @@
1
- .dashboard-page{flex-direction:column;gap:20px;display:flex}.dashboard-header{justify-content:space-between;align-items:center;gap:16px;display:flex}.dashboard-title{color:var(--text-primary);margin-bottom:3px;font-size:1.45rem;font-weight:820}.dashboard-subtitle{color:var(--text-secondary);font-size:.9rem}.dashboard-page .stats-grid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;display:grid}.gsp-stats-grid{margin-bottom:18px}.dashboard-page .stat-card{border-radius:var(--radius-md);background:var(--surface);min-height:106px;box-shadow:var(--shadow-sm);flex-direction:row;align-items:center;gap:14px;padding:18px;display:flex;border:1px solid var(--border)!important;border-left-width:4px!important}.dashboard-page .gradient-teal{border-left-color:var(--success)!important}.dashboard-page .gradient-blue{border-left-color:var(--accent)!important}.dashboard-page .gradient-rose{border-left-color:var(--danger)!important}.dashboard-page .stat-icon{border-radius:var(--radius-md);background:var(--success-light);width:44px;height:44px;color:var(--success);justify-content:center;align-items:center;display:flex}.dashboard-page .gradient-blue .stat-icon{background:var(--accent-light);color:var(--accent)}.dashboard-page .gradient-rose .stat-icon{background:var(--danger-light);color:var(--danger)}.dashboard-page .stat-info{flex-direction:column;min-width:0;display:flex}.dashboard-page .stat-value{color:var(--text-primary);font-variant-numeric:tabular-nums;font-size:1.65rem;font-weight:860;line-height:1.2}.dashboard-page .stat-label{color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em;font-size:.78rem;font-weight:800}.gsp-layout{grid-template-columns:350px 1fr;align-items:start;gap:16px;display:grid}.gsp-form{flex-direction:column;gap:1rem;display:flex}.gsp-form-label{color:var(--text-secondary);margin-bottom:6px;font-size:.8rem;font-weight:760;display:block}.gsp-form-label .label-icon{vertical-align:middle;margin-right:4px}.gsp-form-label .label-limit{color:var(--warning);font-size:.7rem;font-weight:500}.gsp-form-input,.gsp-form-select,.gsp-form-textarea{border:1px solid var(--border);background:var(--surface);width:100%;color:var(--text-primary);box-sizing:border-box;transition:border-color var(--transition-fast), box-shadow var(--transition-fast);border-radius:8px;padding:8px 12px;font-family:inherit;font-size:.9rem}.gsp-form-input:focus,.gsp-form-select:focus,.gsp-form-textarea:focus{border-color:var(--primary);outline:none;box-shadow:0 0 0 3px #0d94881a}.gsp-form-textarea{resize:vertical}.gsp-violation-hint{color:var(--danger);margin-top:4px;font-size:.75rem;font-weight:500}.gsp-standards-box{background:var(--surface-50);border:1px solid var(--border);color:var(--text-secondary);border-radius:8px;margin-top:1.5rem;padding:12px;font-size:.8rem}.gsp-standards-box ul{margin:8px 0 0;padding-left:16px;line-height:1.8;list-style:outside}.gsp-chart-header-actions{align-items:center;gap:8px;display:flex}.gsp-period-select{border:1px solid var(--border);background:var(--surface);color:var(--text-primary);cursor:pointer;transition:border-color var(--transition-fast);border-radius:8px;padding:4px 8px;font-size:.8rem}.gsp-period-select:focus{border-color:var(--primary);outline:none}.gsp-view-toggle button{border:1px solid var(--border);cursor:pointer;transition:all var(--transition-fast);border-radius:8px;padding:4px 12px;font-family:inherit;font-size:.8rem}.gsp-view-toggle button.active{background:var(--primary);color:#fff;border-color:var(--primary)}.gsp-view-toggle button:not(.active){background:var(--surface);color:var(--text-primary)}.gsp-view-toggle button:not(.active):hover{background:var(--background)}.gsp-chart-container{height:380px;position:relative}.gsp-table-container{-webkit-overflow-scrolling:touch;max-height:420px;overflow-x:auto}.gsp-table{border-collapse:separate;border-spacing:0;width:100%}.gsp-table th{color:var(--text-secondary);text-align:left;border-bottom:1px solid var(--border);z-index:1;background:#f6f9fb;padding:10px 14px;font-size:.75rem;font-weight:700;position:sticky;top:0}.gsp-table td{border-bottom:1px solid var(--border);padding:10px 14px;font-size:.85rem}.gsp-table td.td-small,.gsp-note-cell{color:var(--text-secondary);font-size:.8rem}.gsp-table tr.has-violation{background:#ef44440a}.gsp-table td.violation-value{color:var(--danger);font-weight:700}.gsp-table tbody tr{transition:background var(--transition-fast)}.gsp-table tbody tr:hover{background:#f2fbf8}.gsp-violation-card{background:var(--danger-light);border-radius:var(--radius-md);border:1px solid #fecaca;padding:12px 16px;font-size:.85rem}.gsp-violation-card .violation-detail{color:var(--danger);margin-left:8px}.gsp-violation-card .violation-note{color:var(--text-secondary);margin-left:8px}.gsp-violations-list{flex-direction:column;gap:8px;display:flex}.gsp-violations-more{text-align:center;color:var(--text-secondary);font-size:.8rem}.gsp-empty-message{text-align:center;color:var(--text-secondary);padding:3rem}.gsp-chart-column{flex-direction:column;gap:1.5rem;display:flex}@media (width<=900px){.gsp-layout{grid-template-columns:1fr}}@media (width<=480px){.gsp-chart-header-actions{flex-wrap:wrap}.gsp-chart-container{height:280px}}
 
 
assets/LiteWrite.png ADDED

Git LFS Details

  • SHA256: 642492872accc70fa1c705e22c9a51e0039596b4b55eed050d9714a49f3a7a6b
  • Pointer size: 131 Bytes
  • Size of remote file: 510 kB
assets/Login-Cq7VUOkg.css DELETED
@@ -1 +0,0 @@
1
- @keyframes gradientShift{0%{background-position:0%}50%{background-position:100%}to{background-position:0%}}@keyframes logoPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.9;transform:scale(1.05)}}@keyframes cardFloat{0%{opacity:0;transform:translateY(30px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}.login-page{background:linear-gradient(-45deg,#0d9488,#3b82f6,#0f766e,#0891b2) 0 0/400% 400%;justify-content:center;align-items:center;min-height:100vh;animation:15s infinite gradientShift;display:flex;position:relative;overflow:hidden}.login-page:before{content:"";pointer-events:none;background-image:radial-gradient(circle at 25% 25%,#ffffff14 0%,#0000 50%),radial-gradient(circle at 75% 75%,#ffffff0d 0%,#0000 50%);position:absolute;inset:0}.login-card{-webkit-backdrop-filter:blur(20px);border-radius:var(--radius-lg);z-index:1;background:#ffffffeb;border:1px solid #ffffff4d;flex-direction:column;gap:24px;width:100%;max-width:420px;padding:40px 32px;animation:.6s ease-out cardFloat;display:flex;position:relative;box-shadow:0 20px 60px -10px #0003,inset 0 0 1px #ffffff4d}.login-header{text-align:center;margin-bottom:8px}.login-logo{color:var(--primary);margin-bottom:16px;animation:3s ease-in-out infinite logoPulse;display:inline-block}.login-title{color:var(--primary-dark);margin-bottom:8px;font-size:1.5rem;font-weight:700}.login-subtitle{color:var(--text-secondary);font-size:.95rem}.login-form{flex-direction:column;gap:16px;display:flex}.login-form .input-wrapper input{transition:border-color var(--transition-fast), box-shadow var(--transition-fast)}.login-form .input-wrapper input:focus{border-color:var(--primary);box-shadow:0 0 0 4px #0d94881a}.login-options{justify-content:space-between;align-items:center;font-size:.9rem;display:flex}.remember-me{color:var(--text-secondary);cursor:pointer;align-items:center;gap:8px;display:flex}.forgot-password{color:var(--primary);transition:color var(--transition-fast);font-weight:500;text-decoration:none}.forgot-password:hover{color:var(--primary-dark);text-decoration:underline}.login-btn{transition:all var(--transition-fast);margin-top:8px;padding:12px;font-size:1rem;box-shadow:0 4px 14px #0d94884d}.login-btn:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 8px 20px #0d948866}.login-btn:active:not(:disabled){transform:translateY(0)}@media (width<=480px){.login-card{margin:16px;padding:32px 24px}}
 
 
assets/Login-Dv6ikErd.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{N as i,_ as a,y as o}from"./ui-B-VMc-s2.js";import{t as s}from"./circle-alert-Dgga5202.js";import{t as c}from"./loader-circle-jjFgLZ0s.js";import{D as l,E as u,O as d,b as f,x as p}from"./index-C1supBxi.js";var m=r(`heart-pulse`,[[`path`,{d:`M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5`,key:`mvr1a0`}],[`path`,{d:`M3.22 13H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27`,key:`auskq0`}]]),h=e(n(),1),g=t(),_=()=>{let e=d(),t=l(),{login:n}=u(),[r,_]=(0,h.useState)(``),[v,y]=(0,h.useState)(``),[b,x]=(0,h.useState)(``),[S,C]=(0,h.useState)(!1),w=t.state?.from,T=w?.pathname?`${w.pathname}${w.search||``}`:`/dashboard`;return(0,g.jsx)(`div`,{className:`login-page`,children:(0,g.jsxs)(`div`,{className:`login-card`,children:[(0,g.jsxs)(`div`,{className:`login-header`,children:[(0,g.jsx)(m,{size:48,className:`login-logo`}),(0,g.jsx)(`h1`,{className:`login-title`,children:`Trung tâm Y tế khu vực Thanh Ba`}),(0,g.jsx)(`p`,{className:`login-subtitle`,children:`Hệ thống Quản lý Trang thiết bị Y tế`})]}),(0,g.jsxs)(`form`,{className:`login-form`,onSubmit:async t=>{if(t.preventDefault(),!r||!v){x(`Vui lòng nhập đầy đủ tên đăng nhập và mã PIN.`);return}C(!0),x(``);try{let t=await i({username:r,pin:v});t.success&&t.user?(n(t.user),e(T,{replace:!0})):x(t.message||`Tên đăng nhập hoặc mã PIN không chính xác.`)}catch(e){console.error(e),x(`Không thể kết nối đến máy chủ xác thực.`)}finally{C(!1)}},children:[b&&(0,g.jsxs)(`div`,{style:{color:`var(--danger)`,fontSize:`0.85rem`,display:`flex`,alignItems:`center`,gap:`8px`,background:`var(--danger-light)`,padding:`10px`,borderRadius:`4px`},children:[(0,g.jsx)(s,{size:16}),` `,(0,g.jsx)(`span`,{children:b})]}),(0,g.jsx)(a,{type:`text`,placeholder:`Tên đăng nhập hoặc Email`,icon:(0,g.jsx)(f,{size:18}),value:r,onChange:e=>_(e.target.value),required:!0}),(0,g.jsx)(a,{type:`password`,placeholder:`Mã PIN`,icon:(0,g.jsx)(p,{size:18}),value:v,onChange:e=>y(e.target.value),required:!0}),(0,g.jsx)(o,{type:`submit`,variant:`primary`,className:`login-btn`,disabled:S,children:S?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(c,{size:18,className:`animate-spin`}),` Đang đăng nhập...`]}):`Đăng nhập`})]})]})})};export{_ as default};
 
 
assets/Operations-D1k_u3uX.js DELETED
@@ -1 +0,0 @@
1
- import{n as e}from"./chunk-CaILmz35.js";import{n as t,r as n,t as r}from"./createLucideIcon-Ds6Kxw25.js";import{V as i,c as a,d as o,f as s,h as c,m as l,p as u,s as d,u as f,v as p,y as m,z as h}from"./ui-B-VMc-s2.js";import{n as g}from"./stringUtils-DXxYBSKD.js";import{C as _,O as ee,g as te,h as ne,m as re,p as ie,r as ae}from"./index-C1supBxi.js";import{t as oe}from"./esm-62HA5UXF.js";var v=r(`calendar-days`,[[`path`,{d:`M8 2v4`,key:`1cmpym`}],[`path`,{d:`M16 2v4`,key:`4m81vk`}],[`rect`,{width:`18`,height:`18`,x:`3`,y:`4`,rx:`2`,key:`1hopcy`}],[`path`,{d:`M3 10h18`,key:`8toen8`}],[`path`,{d:`M8 14h.01`,key:`6423bh`}],[`path`,{d:`M12 14h.01`,key:`1etili`}],[`path`,{d:`M16 14h.01`,key:`1gbofw`}],[`path`,{d:`M8 18h.01`,key:`lrp35t`}],[`path`,{d:`M12 18h.01`,key:`mhygvu`}],[`path`,{d:`M16 18h.01`,key:`kzsmim`}]]),y=r(`history`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M12 7v5l4 2`,key:`1fdv2h`}]]),b=r(`qr-code`,[[`rect`,{width:`5`,height:`5`,x:`3`,y:`3`,rx:`1`,key:`1tu5fj`}],[`rect`,{width:`5`,height:`5`,x:`16`,y:`3`,rx:`1`,key:`1v8r4q`}],[`rect`,{width:`5`,height:`5`,x:`3`,y:`16`,rx:`1`,key:`1x03jg`}],[`path`,{d:`M21 16h-3a2 2 0 0 0-2 2v3`,key:`177gqh`}],[`path`,{d:`M21 21v.01`,key:`ents32`}],[`path`,{d:`M12 7v3a2 2 0 0 1-2 2H7`,key:`8crl2c`}],[`path`,{d:`M3 12h.01`,key:`nlz23k`}],[`path`,{d:`M12 3h.01`,key:`n36tog`}],[`path`,{d:`M12 16v.01`,key:`133mhm`}],[`path`,{d:`M16 12h1`,key:`1slzba`}],[`path`,{d:`M21 12v.01`,key:`1lwtk9`}],[`path`,{d:`M12 21v-1`,key:`1880an`}]]),x=r(`receipt-text`,[[`path`,{d:`M13 16H8`,key:`wsln4y`}],[`path`,{d:`M14 8H8`,key:`1l3xfs`}],[`path`,{d:`M16 12H8`,key:`1fr5h0`}],[`path`,{d:`M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z`,key:`ycz6yz`}]]),S=e(n(),1),se=`—`,C=(e,t=se)=>{let n=String(e??``).trim(),r=g(n.toLowerCase());return!n||r===`n/a`||r===`na`||r===`khong co`||r===`-`?t:n},w=e=>new Date(e.getFullYear(),e.getMonth(),e.getDate()),T=(e,t)=>{let n=w(e).getTime(),r=w(t).getTime();return Math.round((r-n)/864e5)},E=(e,t)=>{let n=h(e);return n?T(t,n):null},D=e=>g(e.toLowerCase()),O=e=>{let t=D(e);return t.includes(`da gui`)||t.includes(`da phe duyet`)},k=e=>{let t=e.match(/\d+/);return t?Number(t[0]):30},A=(e,t,n,r,i)=>[e,t,n,r,i].map(e=>g(String(e||`none`)).replace(/[^\w-]+/g,`_`)).join(`__`),j=(e,t,n)=>O(e)?{statusKind:`sent`,statusText:e,priority:`low`,needsAction:!1}:!t||n===null?{statusKind:`missing`,statusText:`Thiếu hồ sơ`,priority:`medium`,needsAction:!0}:n<0?{statusKind:`expired`,statusText:`Hồ sơ hết hạn ${Math.abs(n)} ngày`,priority:`critical`,needsAction:!0}:n<=30?{statusKind:`warning`,statusText:`Còn ${n} ngày`,priority:`high`,needsAction:!0}:{statusKind:`valid`,statusText:`Còn ${n} ngày`,priority:`low`,needsAction:!1},M=(e,t,n,r,i)=>{let a=C(t.docType,`Hồ sơ kiểm định`),o=C(t.licenseNo),s=C(t.issuedDate,``),c=C(t.expiryDate,``),l=C(t.prepTime,`30`),u=typeof t.daysUntilExpiry==`number`?t.daysUntilExpiry:E(c,i),d=j(C(t.status,`Chưa gửi`),c,u);return{taskKey:A(e.id,a,o,c,`${r}-${n}`),deviceId:e.id,deviceName:e.name,department:C(e.department,`Chưa phân bổ`),docType:a,licenseNo:o,issuedDate:C(s),expiryDate:C(c),prepTime:l,responsible:C(t.responsible),daysUntilExpiry:u,prepDays:k(l),...d}},N=(e,t,n)=>{let r=C(e[`Ngày cấp/ Ngày Đăng kiểm`],``),i=C(e[`Thời hạn cấp lại/ Hạn đăng kiểm`]||e[`Hạn đăng kiểm`]||e[`Ngày bảo dưỡng tiếp theo`],``),a=C(e[`Số văn bản / Số Đăng kiểm`]||e[`Số đăng kiểm`],``),o=C(e[`Trạng thái Hồ sơ`],``);if(!(r||i||a||o))return null;let s=C(e[`Thời gian chuẩn bị Hồ sơ`]||e[`Thời gian chuẩn bị Hồ sơ`],`30`),c=o||`Chưa gửi`,l=E(i,n),u=j(c,i,l);return{taskKey:A(e.id,`legacy`,``,i,t),deviceId:e.id,deviceName:e.name,department:C(e.department,`Chưa phân bổ`),docType:r||i?`Đăng kiểm / bảo dưỡng`:`Hồ sơ theo dõi`,licenseNo:C(a),issuedDate:C(r),expiryDate:C(i),prepTime:s,responsible:C(e[`Người chịu trách nhiệm`]),daysUntilExpiry:l,prepDays:k(s),...u}},ce=(e,t=new Date)=>e.flatMap((e,n)=>{let r=e.documents||[];if(r.length===0){let r=N(e,n,t);return r?[r]:[]}return r.map((r,i)=>M(e,r,i,n,t))}).sort((e,t)=>{let n={critical:0,high:1,medium:2,low:3},r=n[e.priority]-n[t.priority];if(r!==0)return r;let i=e.daysUntilExpiry??2**53-1,a=t.daysUntilExpiry??2**53-1;return i===a?e.deviceName.localeCompare(t.deviceName,`vi`):i-a}),P={todo:`Chưa làm`,preparing:`Đang chuẩn bị`,submitted:`Đã gửi`,approved:`Đã phê duyệt`,returned:`Bị trả lại`},le=e=>P[e],ue=(e,t={})=>e.filter(e=>e.needsAction).map(e=>{let n=t[e.taskKey],r=n?.status||(e.statusKind===`missing`?`todo`:`preparing`);return{...e,workflowStatus:r,workflowLabel:P[r],note:n?.note||``,updatedAt:n?.updatedAt||``}}),F=e=>h(e)?.getTime()||0,de=({devices:e,repairs:t,transfers:n,inspectionTasks:r,costEntries:i})=>{let a=new Map(e.map(e=>[e.id,e.name])),o=[];return t.forEach((e,t)=>{o.push({id:`repair-${e.rowId}-${e.deviceId}-${t}`,type:`repair`,date:C(e.rowId),deviceId:e.deviceId,title:`Cập nhật sửa chữa ${e.deviceId}`,description:`${C(e.description)} - ${C(e.status)}`,actor:C(e.userName)})}),n.forEach((e,t)=>{o.push({id:`transfer-${e.transferId}-${t}`,type:`transfer`,date:C(e.updatedAt||e.receivedAt||e.requestedAt||e.createdAt),deviceId:e.deviceId,title:`Luân chuyển ${e.deviceName||e.deviceId}`,description:`${C(e.fromDepartment)} -> ${C(e.toDepartment)} (${C(e.status)})`,actor:C(e.requestedByName||e.requestedBy)})}),r.forEach((e,t)=>{o.push({id:`inspection-${e.taskKey}-${t}`,type:`inspection-task`,date:C(e.updatedAt||e.issuedDate||e.expiryDate),deviceId:e.deviceId,title:`Hồ sơ ${e.docType}`,description:`${e.statusText} - ${e.workflowLabel}`,actor:C(e.responsible)})}),i.forEach((e,t)=>{o.push({id:`cost-${e.id}-${t}`,type:`cost`,date:C(e.date),deviceId:e.deviceId,title:`Chi phí ${a.get(e.deviceId)||e.deviceId}`,description:`${e.category}: ${I(e.amount)} - ${C(e.note)}`,actor:C(e.vendor)})}),o.sort((e,t)=>F(t.date)-F(e.date))},I=e=>new Intl.NumberFormat(`vi-VN`,{style:`currency`,currency:`VND`,maximumFractionDigits:0}).format(Number.isFinite(e)?e:0).replace(/\u00a0/g,` `),fe=e=>{let t=new Date(e.getFullYear(),e.getMonth(),1),n=t.getDay()===0?6:t.getDay()-1,r=[],i=new Date(t);for(i.setDate(t.getDate()-n);r.length<42;){let t=new Date(i);r.push({date:t,inMonth:t.getMonth()===e.getMonth(),key:t.toISOString().slice(0,10)}),i.setDate(i.getDate()+1)}return r},L=t(),R=`qlttb.inspectionWorkflow`,z=`qlttb.maintenanceCosts`,pe=[`todo`,`preparing`,`submitted`,`approved`,`returned`],me=[{id:`calendar`,label:`Lịch kiểm định`,icon:v},{id:`tasks`,label:`Nhắc việc`,icon:_},{id:`audit`,label:`Lịch sử`,icon:y},{id:`costs`,label:`Chi phí`,icon:x},{id:`qr-import`,label:`QR & Import`,icon:b}],B=(e,t)=>{try{let n=localStorage.getItem(e);return n?JSON.parse(n):t}catch{return t}},V=(e,t)=>{localStorage.setItem(e,JSON.stringify(t))},H=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`,he=e=>H(e),ge=e=>{let t=e.split(`-`);return t.length===3?`${t[2]}/${t[1]}/${t[0]}`:e},_e={expired:`Hết hạn`,warning:`Cần chuẩn bị`,missing:`Thiếu hồ sơ`,sent:`Đã gửi/duyệt`,valid:`Còn hạn`},ve={critical:`Khẩn cấp`,high:`Cao`,medium:`Trung bình`,low:`Thấp`},ye={critical:`danger`,high:`warning`,medium:`primary`,low:`neutral`},be={device:`Thiết bị`,repair:`Sửa chữa`,transfer:`Luân chuyển`,"inspection-task":`Kiểm định`,cost:`Chi phí`},xe=(e,t)=>{let n=new Set;return e.split(/\r?\n/).map((e,t)=>({line:e,rowNumber:t+1})).filter(e=>e.line.trim()).map(({line:e,rowNumber:r})=>{let[i=``,a=``,o=``,s=``]=e.split(/\t|,|;/).map(e=>e.trim()),c=[];return i||c.push(`Thiếu mã thiết bị`),a||c.push(`Thiếu tên thiết bị`),o||c.push(`Thiếu khoa/phòng`),s||c.push(`Thiếu hạn đăng kiểm`),i&&t.has(i)&&c.push(`Trùng mã đã có`),i&&n.has(i)&&c.push(`Trùng mã trong file`),s&&!h(s)&&c.push(`Ngày hạn không hợp lệ`),i&&n.add(i),{rowNumber:r,deviceId:i,name:a,department:o,expiryDate:s,issues:c}})},U=()=>{let e=ee(),{devices:t,isLoading:n}=ie(),{repairs:r,isLoading:g}=ne(),{transfers:y,isLoading:se}=re(),C=n||g||se,[w,T]=(0,S.useState)(`calendar`),[E,D]=(0,S.useState)(()=>new Date),[O,k]=(0,S.useState)(()=>B(R,{})),[A,j]=(0,S.useState)(()=>B(z,[])),[M,N]=(0,S.useState)({deviceId:``,date:he(new Date),amount:``,category:`Sửa chữa`,vendor:``,note:``}),[P,F]=(0,S.useState)(``),[U,Se]=(0,S.useState)(``),W=(0,S.useMemo)(()=>new Date,[]),G=(0,S.useMemo)(()=>ce(t,W),[t,W]),K=(0,S.useMemo)(()=>ue(G,O),[G,O]),Ce=(0,S.useMemo)(()=>r.filter(e=>ae(e.status)).length,[r]),q=(0,S.useMemo)(()=>de({devices:t,repairs:r,transfers:y,inspectionTasks:K,costEntries:A}),[A,t,K,r,y]),J=(0,S.useMemo)(()=>new Set(t.map(e=>e.id)),[t]),Y=(0,S.useMemo)(()=>xe(U,J),[J,U]),X=t.find(e=>e.id===P)||t[0]||null,Z=X?`/devices/${encodeURIComponent(X.id)}`:`/devices`,Q=X?`${window.location.origin}${`/`.replace(/\/$/,``)}${Z}`:``,$=(0,S.useMemo)(()=>({expired:G.filter(e=>e.statusKind===`expired`).length,warning:G.filter(e=>e.statusKind===`warning`).length,totalCost:A.reduce((e,t)=>e+t.amount,0)}),[A,G]),we=(0,S.useMemo)(()=>fe(E),[E]),Te=(0,S.useMemo)(()=>G.reduce((e,t)=>{let n=h(t.expiryDate);if(!n)return e;let r=H(n);return e[r]=[...e[r]||[],t],e},{}),[G]),Ee=(0,S.useMemo)(()=>G.filter(e=>{let t=h(e.expiryDate);return t&&t.getFullYear()===E.getFullYear()&&t.getMonth()===E.getMonth()}),[E,G]),De=(e,t)=>{k(n=>{let r=n[e]||{status:`todo`},i={...n,[e]:{...r,...t,updatedAt:new Date().toLocaleDateString(`vi-VN`)}};return V(R,i),i})},Oe=()=>{let e=M.deviceId||X?.id||t[0]?.id||``,n=Number(M.amount);if(!e||!Number.isFinite(n)||n<=0){alert(`Vui lòng chọn thiết bị và nhập chi phí hợp lệ.`);return}let r=[{id:`${Date.now()}`,deviceId:e,date:ge(M.date),amount:n,category:M.category,vendor:M.vendor.trim()||`Chưa nhập`,note:M.note.trim()||`—`},...A];j(r),V(z,r),N(e=>({...e,amount:``,vendor:``,note:``}))},ke=e=>{let t=A.filter(t=>t.id!==e);j(t),V(z,t)},Ae=e=>{D(t=>new Date(t.getFullYear(),t.getMonth()+e,1))};return(0,L.jsxs)(`div`,{className:`operations-page`,children:[(0,L.jsxs)(`div`,{className:`operations-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsxs)(`h1`,{children:[(0,L.jsx)(_,{size:30}),` Điều hành công việc`]}),(0,L.jsx)(`p`,{children:`Lịch kiểm định, nhắc việc, audit log, chi phí, QR và kiểm tra import nằm chung một nơi.`})]}),(0,L.jsx)(`div`,{className:`operations-loading`,children:C?`Đang tải dữ liệu...`:`${t.length} thiết bị`})]}),(0,L.jsxs)(`div`,{className:`ops-stat-grid`,children:[(0,L.jsx)(d,{className:`ops-stat-card is-danger`,children:(0,L.jsxs)(a,{children:[(0,L.jsx)(i,{size:22}),(0,L.jsx)(`strong`,{children:$.expired}),(0,L.jsx)(`span`,{children:`Hết hạn kiểm định`})]})}),(0,L.jsx)(d,{className:`ops-stat-card is-warning`,children:(0,L.jsxs)(a,{children:[(0,L.jsx)(v,{size:22}),(0,L.jsx)(`strong`,{children:$.warning}),(0,L.jsx)(`span`,{children:`Cần chuẩn bị hồ sơ`})]})}),(0,L.jsx)(d,{className:`ops-stat-card is-success`,children:(0,L.jsxs)(a,{children:[(0,L.jsx)(te,{size:22}),(0,L.jsx)(`strong`,{children:Ce}),(0,L.jsx)(`span`,{children:`Yêu cầu sửa chữa mở`})]})})]}),(0,L.jsx)(`div`,{className:`operations-tabs`,children:me.map(e=>{let t=e.icon;return(0,L.jsxs)(`button`,{type:`button`,className:w===e.id?`active`:``,onClick:()=>T(e.id),children:[(0,L.jsx)(t,{size:16}),e.label]},e.id)})}),w===`calendar`&&(0,L.jsxs)(`div`,{className:`ops-split`,children:[(0,L.jsxs)(`section`,{className:`ops-calendar-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsxs)(`h2`,{children:[`Lịch kiểm định `,String(E.getMonth()+1).padStart(2,`0`),`/`,E.getFullYear()]}),(0,L.jsx)(`p`,{children:`Các mốc hạn hiệu lực được đặt vào đúng ngày hết hạn để dễ ưu tiên theo tuần.`})]}),(0,L.jsxs)(`div`,{className:`ops-calendar-actions`,children:[(0,L.jsx)(m,{variant:`secondary`,size:`sm`,onClick:()=>Ae(-1),children:`Tháng trước`}),(0,L.jsx)(m,{variant:`secondary`,size:`sm`,onClick:()=>D(new Date),children:`Hôm nay`}),(0,L.jsx)(m,{variant:`secondary`,size:`sm`,onClick:()=>Ae(1),children:`Tháng sau`})]})]}),(0,L.jsx)(`div`,{className:`ops-calendar-weekdays`,children:[`T2`,`T3`,`T4`,`T5`,`T6`,`T7`,`CN`].map(e=>(0,L.jsx)(`span`,{children:e},e))}),(0,L.jsx)(`div`,{className:`ops-calendar-grid`,children:we.map(e=>{let t=Te[H(e.date)]||[],n=t.filter(e=>e.statusKind===`expired`||e.statusKind===`warning`).length;return(0,L.jsxs)(`div`,{className:`ops-calendar-day ${e.inMonth?``:`is-muted`} ${n?`has-urgent`:``}`,children:[(0,L.jsx)(`div`,{className:`ops-calendar-date`,children:e.date.getDate()}),t.slice(0,3).map(e=>(0,L.jsx)(`span`,{className:`ops-calendar-chip is-${e.statusKind}`,children:e.deviceId},e.taskKey)),t.length>3&&(0,L.jsxs)(`small`,{children:[`+`,t.length-3,` hồ sơ`]})]},e.key)})})]}),(0,L.jsxs)(`section`,{className:`ops-side-panel`,children:[(0,L.jsx)(`h3`,{children:`Việc trong tháng`}),(0,L.jsx)(`div`,{className:`ops-month-list`,children:Ee.length===0?(0,L.jsx)(`div`,{className:`ops-empty`,children:`Không có hồ sơ đến hạn trong tháng này.`}):Ee.slice(0,18).map(e=>(0,L.jsxs)(`div`,{className:`ops-month-item`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`strong`,{children:e.deviceName}),(0,L.jsxs)(`span`,{children:[e.department,` · `,e.docType]})]}),(0,L.jsx)(p,{variant:ye[e.priority],children:_e[e.statusKind]})]},e.taskKey))})]})]}),w===`tasks`&&(0,L.jsxs)(`section`,{className:`ops-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{children:`Nhắc việc kiểm định & quy trình hồ sơ`}),(0,L.jsx)(`p`,{children:`Mỗi dòng là một việc cần xử lý. Trạng thái và ghi chú được lưu trên máy này để theo dõi nhanh.`})]}),(0,L.jsxs)(p,{variant:`warning`,children:[K.length,` việc cần theo dõi`]})]}),(0,L.jsxs)(f,{className:`ops-table`,children:[(0,L.jsx)(u,{children:(0,L.jsxs)(c,{children:[(0,L.jsx)(l,{children:`Ưu tiên`}),(0,L.jsx)(l,{children:`Thiết bị`}),(0,L.jsx)(l,{children:`Hồ sơ`}),(0,L.jsx)(l,{children:`Hạn`}),(0,L.jsx)(l,{children:`Phụ trách`}),(0,L.jsx)(l,{children:`Quy trình`}),(0,L.jsx)(l,{children:`Ghi chú`})]})}),(0,L.jsx)(o,{children:K.length===0?(0,L.jsx)(c,{children:(0,L.jsx)(s,{colSpan:7,style:{textAlign:`center`,padding:`2rem`},children:`Không có việc kiểm định cần xử lý.`})}):K.map(e=>(0,L.jsxs)(c,{children:[(0,L.jsx)(s,{children:(0,L.jsx)(p,{variant:ye[e.priority],children:ve[e.priority]})}),(0,L.jsxs)(s,{children:[(0,L.jsx)(`strong`,{children:e.deviceName}),(0,L.jsx)(`br`,{}),(0,L.jsxs)(`small`,{children:[e.deviceId,` · `,e.department]})]}),(0,L.jsxs)(s,{children:[(0,L.jsx)(`strong`,{children:e.docType}),(0,L.jsx)(`br`,{}),(0,L.jsx)(`small`,{children:e.licenseNo})]}),(0,L.jsxs)(s,{children:[(0,L.jsx)(`strong`,{children:e.expiryDate}),(0,L.jsx)(`br`,{}),(0,L.jsx)(`small`,{children:e.statusText})]}),(0,L.jsx)(s,{children:e.responsible}),(0,L.jsx)(s,{children:(0,L.jsx)(`select`,{className:`ops-select`,value:e.workflowStatus,onChange:t=>De(e.taskKey,{status:t.target.value}),children:pe.map(e=>(0,L.jsx)(`option`,{value:e,children:le(e)},e))})}),(0,L.jsx)(s,{children:(0,L.jsx)(`input`,{className:`ops-inline-input`,value:O[e.taskKey]?.note||``,onChange:t=>De(e.taskKey,{note:t.target.value}),placeholder:`Ghi chú xử lý`})})]},e.taskKey))})]})]}),w===`audit`&&(0,L.jsxs)(`section`,{className:`ops-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{children:`Lịch sử thay đổi & hoạt động`}),(0,L.jsx)(`p`,{children:`Tổng hợp sửa chữa, luân chuyển, hồ sơ kiểm định và chi phí để tra cứu trách nhiệm nhanh.`})]}),(0,L.jsxs)(p,{variant:`primary`,children:[q.length,` sự kiện`]})]}),(0,L.jsx)(`div`,{className:`ops-timeline`,children:q.slice(0,120).map(e=>(0,L.jsxs)(`article`,{className:`ops-timeline-item is-${e.type}`,children:[(0,L.jsx)(`div`,{className:`ops-timeline-dot`}),(0,L.jsxs)(`div`,{className:`ops-timeline-content`,children:[(0,L.jsxs)(`div`,{className:`ops-timeline-meta`,children:[(0,L.jsx)(p,{variant:e.type===`cost`?`success`:e.type===`repair`?`warning`:`primary`,children:be[e.type]}),(0,L.jsx)(`span`,{children:e.date}),(0,L.jsx)(`span`,{children:e.actor})]}),(0,L.jsx)(`h3`,{children:e.title}),(0,L.jsx)(`p`,{children:e.description}),(0,L.jsx)(`small`,{children:e.deviceId})]})]},e.id))})]}),w===`costs`&&(0,L.jsxs)(`section`,{className:`ops-panel ops-cost-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{children:`Chi phí sửa chữa/bảo trì`}),(0,L.jsx)(`p`,{children:`Ghi nhanh chi phí để đối chiếu theo thiết bị và nhà cung cấp.`})]}),(0,L.jsx)(p,{variant:`success`,children:I($.totalCost)})]}),(0,L.jsxs)(`div`,{className:`ops-cost-form`,children:[(0,L.jsxs)(`select`,{value:M.deviceId,onChange:e=>N(t=>({...t,deviceId:e.target.value})),children:[(0,L.jsx)(`option`,{value:``,children:`Chọn thiết bị`}),t.map((e,t)=>(0,L.jsxs)(`option`,{value:e.id,children:[e.id,` - `,e.name]},`${e.id}-${t}`))]}),(0,L.jsx)(`input`,{type:`date`,value:M.date,onChange:e=>N(t=>({...t,date:e.target.value}))}),(0,L.jsx)(`input`,{value:M.amount,onChange:e=>N(t=>({...t,amount:e.target.value})),placeholder:`Chi phí`,inputMode:`numeric`}),(0,L.jsxs)(`select`,{value:M.category,onChange:e=>N(t=>({...t,category:e.target.value})),children:[(0,L.jsx)(`option`,{children:`Sửa chữa`}),(0,L.jsx)(`option`,{children:`Bảo trì`}),(0,L.jsx)(`option`,{children:`Kiểm định`}),(0,L.jsx)(`option`,{children:`Vật tư thay thế`})]}),(0,L.jsx)(`input`,{value:M.vendor,onChange:e=>N(t=>({...t,vendor:e.target.value})),placeholder:`Đơn vị thực hiện`}),(0,L.jsx)(`input`,{value:M.note,onChange:e=>N(t=>({...t,note:e.target.value})),placeholder:`Ghi chú`}),(0,L.jsx)(m,{icon:(0,L.jsx)(x,{size:16}),onClick:Oe,children:`Thêm chi phí`})]}),(0,L.jsx)(`div`,{className:`ops-cost-list`,children:A.length===0?(0,L.jsx)(`div`,{className:`ops-empty`,children:`Chưa có chi phí nào được ghi nhận.`}):A.map(e=>(0,L.jsxs)(`div`,{className:`ops-cost-item`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`strong`,{children:I(e.amount)}),(0,L.jsxs)(`span`,{children:[e.deviceId,` · `,e.category,` · `,e.vendor]}),(0,L.jsxs)(`small`,{children:[e.date,` · `,e.note]})]}),(0,L.jsx)(m,{size:`sm`,variant:`secondary`,onClick:()=>ke(e.id),children:`Xóa`})]},e.id))})]}),w===`qr-import`&&(()=>{let n=Y.filter(e=>e.issues.length>0).length;return(0,L.jsxs)(`div`,{className:`ops-split`,children:[(0,L.jsxs)(`section`,{className:`ops-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{children:`QR nhanh cho thiết bị`}),(0,L.jsx)(`p`,{children:`Chọn thiết bị để mở hồ sơ, báo hỏng hoặc in mã QR dán tại khoa.`})]}),(0,L.jsx)(b,{size:30,className:`ops-muted-icon`})]}),(0,L.jsxs)(`div`,{className:`ops-qr-layout`,children:[(0,L.jsxs)(`label`,{className:`ops-qr-picker`,children:[(0,L.jsx)(`span`,{children:`Thiết bị`}),(0,L.jsx)(`select`,{value:X?.id||``,onChange:e=>F(e.target.value),children:t.map((e,t)=>(0,L.jsxs)(`option`,{value:e.id,children:[e.id,` - `,e.name]},`${e.id}-${t}`))})]}),X&&(0,L.jsxs)(`div`,{className:`ops-qr-workspace`,children:[(0,L.jsx)(`div`,{className:`ops-qr-card`,children:(0,L.jsx)(oe,{value:Q,size:150})}),(0,L.jsxs)(`div`,{className:`ops-qr-info`,children:[(0,L.jsxs)(`div`,{className:`ops-qr-meta`,children:[(0,L.jsx)(`strong`,{children:X.name}),(0,L.jsxs)(`span`,{children:[X.id,` · `,X.department]})]}),(0,L.jsx)(`code`,{children:Q}),(0,L.jsxs)(`div`,{className:`ops-qr-actions`,children:[(0,L.jsx)(m,{size:`sm`,onClick:()=>e(Z),children:`Mở hồ sơ`}),(0,L.jsx)(m,{size:`sm`,variant:`secondary`,onClick:()=>e(`/requests?type=repair&device=${encodeURIComponent(X.id)}`),children:`Báo hỏng`}),(0,L.jsx)(m,{size:`sm`,variant:`secondary`,onClick:()=>e(`/requests?type=transfer`),children:`Điều chuyển`})]})]})]})]})]}),(0,L.jsxs)(`section`,{className:`ops-panel`,children:[(0,L.jsxs)(`div`,{className:`ops-section-header`,children:[(0,L.jsxs)(`div`,{children:[(0,L.jsx)(`h2`,{children:`Kiểm tra dữ liệu trước khi import`}),(0,L.jsx)(`p`,{children:`Dán dữ liệu từ Excel theo thứ tự: mã thiết bị, tên, khoa/phòng, hạn đăng kiểm.`})]}),(0,L.jsxs)(p,{variant:n>0?`danger`:`success`,children:[n,` dòng lỗi`]})]}),(0,L.jsx)(`textarea`,{className:`ops-import-box`,value:U,onChange:e=>Se(e.target.value),placeholder:`TB-001 Máy thở ICU 31/12/2026`}),(0,L.jsxs)(f,{className:`ops-table compact`,children:[(0,L.jsx)(u,{children:(0,L.jsxs)(c,{children:[(0,L.jsx)(l,{children:`Dòng`}),(0,L.jsx)(l,{children:`Mã`}),(0,L.jsx)(l,{children:`Tên`}),(0,L.jsx)(l,{children:`Khoa/phòng`}),(0,L.jsx)(l,{children:`Kết quả`})]})}),(0,L.jsx)(o,{children:Y.length===0?(0,L.jsx)(c,{children:(0,L.jsx)(s,{colSpan:5,style:{textAlign:`center`,padding:`2rem`},children:`Chưa có dữ liệu để kiểm tra.`})}):Y.map(e=>(0,L.jsxs)(c,{children:[(0,L.jsx)(s,{children:e.rowNumber}),(0,L.jsx)(s,{children:e.deviceId||`—`}),(0,L.jsx)(s,{children:e.name||`—`}),(0,L.jsx)(s,{children:e.department||`—`}),(0,L.jsx)(s,{children:e.issues.length===0?(0,L.jsx)(p,{variant:`success`,children:`Hợp lệ`}):(0,L.jsx)(`div`,{className:`ops-reason-list`,children:e.issues.map(e=>(0,L.jsx)(`span`,{children:e},e))})})]},e.rowNumber))})]})]})]})})(),(0,L.jsx)(`div`,{className:`ops-footnote`,children:`Trạng thái quy trình hồ sơ và chi phí đang được lưu nội bộ trên trình duyệt. Khi có sheet/API riêng, phần này có thể nối backend để dùng chung nhiều máy.`})]})};export{U as default};
 
 
assets/Operations-OxdQ2jh6.css DELETED
@@ -1 +0,0 @@
1
- .operations-page{flex-direction:column;gap:20px;display:flex}.operations-header{justify-content:space-between;align-items:flex-start;gap:18px;display:flex}.operations-header h1{color:var(--text-primary);align-items:center;gap:10px;margin:0 0 6px;font-size:1.45rem;font-weight:820;display:flex}.operations-header h1 svg{color:#0f766e}.operations-header p{max-width:820px;color:var(--text-secondary);margin:0;line-height:1.5}.operations-loading{color:var(--text-secondary);background:#fff;border:1px solid #dbe5ee;border-radius:8px;flex:none;padding:10px 14px;font-weight:760}.ops-stat-grid{grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px;display:grid}.ops-stat-card{border-left:4px solid #64748b;box-shadow:none!important;border-radius:8px!important}.ops-stat-card .card-body{grid-template-columns:auto minmax(0,1fr);align-items:center;gap:4px 12px;padding:16px;display:grid}.ops-stat-card svg{color:#0f766e;grid-row:span 2}.ops-stat-card strong{color:#0f172a;font-variant-numeric:tabular-nums;font-size:1.45rem;font-weight:850;line-height:1}.ops-stat-card span{color:var(--text-secondary);font-size:.78rem;font-weight:760;line-height:1.25}.ops-stat-card.is-danger{border-left-color:#ef4444}.ops-stat-card.is-warning{border-left-color:#f59e0b}.ops-stat-card.is-primary{border-left-color:#2563eb}.ops-stat-card.is-success{border-left-color:#10b981}.operations-tabs{background:#f8fafc;border:1px solid #dbe5ee;border-radius:8px;flex-wrap:wrap;gap:8px;padding:6px;display:flex}.operations-tabs button{min-height:38px;color:var(--text-secondary);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:7px;justify-content:center;align-items:center;gap:8px;padding:8px 12px;font-family:inherit;font-weight:760;display:inline-flex}.operations-tabs button:hover,.operations-tabs button.active{color:#0f766e;background:#fff;border-color:#b6d7db;box-shadow:0 6px 14px #0f172a0f}.ops-split{grid-template-columns:minmax(0,1.45fr) minmax(330px,.8fr);align-items:start;gap:16px;display:grid}.ops-panel,.ops-calendar-panel,.ops-side-panel{background:#fff;border:1px solid #dbe5ee;border-radius:8px;padding:18px;box-shadow:0 10px 24px #0f172a0a}.ops-section-header{justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:16px;display:flex}.ops-section-header h2{color:var(--text-primary);margin:0 0 6px;font-size:1.05rem;font-weight:820}.ops-section-header p{max-width:720px;color:var(--text-secondary);margin:0;font-size:.9rem;line-height:1.45}.ops-calendar-actions{flex-wrap:wrap;justify-content:flex-end;gap:8px;display:flex}.ops-calendar-weekdays,.ops-calendar-grid{grid-template-columns:repeat(7,minmax(0,1fr));display:grid}.ops-calendar-weekdays{gap:6px;margin-bottom:6px}.ops-calendar-weekdays span{color:var(--text-secondary);text-align:center;font-size:.72rem;font-weight:850}.ops-calendar-grid{gap:6px}.ops-calendar-day{background:#fbfdfe;border:1px solid #dbe5ee;border-radius:8px;flex-direction:column;gap:5px;min-height:104px;padding:8px;display:flex;overflow:hidden}.ops-calendar-day.is-muted{opacity:.45}.ops-calendar-day.has-urgent{background:#fffaf0;border-color:#f59e0b}.ops-calendar-date{color:#0f172a;font-variant-numeric:tabular-nums;font-weight:850}.ops-calendar-chip{color:#334155;text-overflow:ellipsis;white-space:nowrap;background:#eef2f7;border-radius:5px;padding:3px 6px;font-size:.7rem;font-weight:760;line-height:1.15;display:block;overflow:hidden}.ops-calendar-chip.is-expired{color:#b91c1c;background:#fee2e2}.ops-calendar-chip.is-warning{color:#b45309;background:#fef3c7}.ops-calendar-chip.is-valid,.ops-calendar-chip.is-sent{color:#047857;background:#d1fae5}.ops-calendar-day small{color:var(--text-secondary);font-size:.7rem;font-weight:760}.ops-side-panel h3{color:var(--text-primary);margin:0 0 12px;font-size:1rem}.ops-month-list,.ops-cost-list,.ops-open-repairs{flex-direction:column;gap:8px;display:flex}.ops-month-item,.ops-cost-item,.ops-open-repair{background:#fbfdfe;border:1px solid #e2e8f0;border-radius:8px;justify-content:space-between;align-items:flex-start;gap:12px;padding:10px 12px;display:flex}.ops-month-item div,.ops-cost-item div,.ops-open-repair{min-width:0}.ops-month-item strong,.ops-cost-item strong,.ops-open-repair strong{color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;font-size:.9rem;line-height:1.3;display:block;overflow:hidden}.ops-month-item span,.ops-cost-item span,.ops-open-repair span,.ops-cost-item small{color:var(--text-secondary);font-size:.78rem;line-height:1.35;display:block}.ops-table.table-container{box-shadow:none;border-color:#dbe5ee;border-radius:8px}.ops-table .data-table{min-width:980px}.ops-table.compact .data-table{min-width:680px}.ops-table .data-table th,.ops-table .data-table td{vertical-align:middle;padding:12px 14px}.ops-table .data-table th{color:#223654;letter-spacing:.08em;background:#f5f8fb;border-bottom:1px solid #d6e2ea;font-size:.72rem}.ops-select,.ops-inline-input,.ops-cost-form input,.ops-cost-form select,.ops-qr-layout select{width:100%;min-height:38px;color:var(--text-primary);background:#fff;border:1px solid #cbd5e1;border-radius:8px;outline:none;padding:8px 10px;font-family:inherit;font-weight:650}.ops-select:focus,.ops-inline-input:focus,.ops-cost-form input:focus,.ops-cost-form select:focus,.ops-qr-layout select:focus,.ops-import-box:focus{border-color:#0f766e;box-shadow:0 0 0 2px #0f766e1f}.ops-reason-list{flex-wrap:wrap;gap:6px;display:flex}.ops-reason-list span{color:var(--text-secondary);background:#eef2f7;border-radius:6px;padding:4px 7px;font-size:.75rem;font-weight:700;display:inline-flex}.ops-timeline{flex-direction:column;gap:0;display:flex}.ops-timeline-item{grid-template-columns:20px minmax(0,1fr);gap:12px;padding-bottom:16px;display:grid;position:relative}.ops-timeline-item:before{content:"";background:#dbe5ee;width:1px;position:absolute;top:18px;bottom:0;left:6px}.ops-timeline-dot{background:#0f766e;border-radius:50%;width:13px;height:13px;margin-top:8px;box-shadow:0 0 0 4px #eef6fb}.ops-timeline-content{background:#fbfdfe;border:1px solid #e2e8f0;border-radius:8px;padding:12px}.ops-timeline-meta{color:var(--text-secondary);flex-wrap:wrap;align-items:center;gap:8px;font-size:.78rem;font-weight:720;display:flex}.ops-timeline-content h3{color:var(--text-primary);margin:8px 0 4px;font-size:.95rem}.ops-timeline-content p{color:var(--text-secondary);margin:0;font-size:.86rem}.ops-timeline-content small{color:#0f766e;margin-top:6px;font-weight:760;display:block}.ops-cost-form{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:14px;display:grid}.ops-cost-form button{grid-column:1/-1}.ops-cost-item{align-items:center}.ops-qr-layout{flex-direction:column;gap:14px;display:flex}.ops-qr-picker{color:var(--text-primary);flex-direction:column;gap:7px;font-size:.78rem;font-weight:800;display:flex}.ops-qr-workspace{grid-template-columns:minmax(180px,220px) minmax(0,1fr);align-items:start;gap:16px;display:grid}.ops-qr-card{background:linear-gradient(#fff 0%,#f8fbfd 100%);border:1px solid #dbe5ee;border-radius:8px;justify-content:center;align-items:center;min-height:204px;padding:20px;display:flex}.ops-qr-info{background:#fbfdfe;border:1px solid #dbe5ee;border-radius:8px;flex-direction:column;gap:10px;min-width:0;padding:14px;display:flex}.ops-qr-meta{flex-direction:column;gap:4px;min-width:0;display:flex}.ops-qr-info strong{color:var(--text-primary);font-size:1rem;line-height:1.35}.ops-qr-info span,.ops-qr-info code{color:var(--text-secondary);font-size:.82rem}.ops-qr-info code{overflow-wrap:anywhere;word-break:break-word;background:#f1f5f9;border-radius:8px;padding:8px;display:block}.ops-qr-actions{flex-wrap:wrap;gap:8px;display:flex}.ops-import-box{width:100%;min-height:120px;color:var(--text-primary);resize:vertical;background:#fff;border:1px solid #cbd5e1;border-radius:8px;outline:none;margin-bottom:12px;padding:12px;font-family:Cascadia Mono,Consolas,monospace;font-size:.86rem}.ops-muted-icon{color:#0f766e}.ops-empty{color:var(--text-secondary);text-align:center;border:1px dashed #cbd5e1;border-radius:8px;padding:20px}.ops-footnote{color:var(--text-secondary);background:#fbfdfe;border:1px solid #dbe5ee;border-radius:8px;padding:12px 14px;font-size:.84rem;line-height:1.45}@media (width<=1100px){.ops-split{grid-template-columns:1fr}}@media (width<=720px){.operations-header,.ops-section-header{flex-direction:column}.operations-tabs button{flex:160px}.ops-calendar-day{min-height:78px;padding:6px}.ops-calendar-chip{font-size:.64rem}.ops-cost-form,.ops-qr-workspace{grid-template-columns:1fr}}