Deploy LightRAG AI Assistant with Git LFS for binary assets and fonts
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .clinerules/01-basic.md +257 -0
- .dockerignore +69 -0
- .env.huggingface +40 -0
- .gitattributes +3 -35
- .github/CONTRIBUTING.md +163 -0
- .github/ISSUE_TEMPLATE/bug_report.yml +61 -0
- .github/ISSUE_TEMPLATE/config.yml +1 -0
- .github/ISSUE_TEMPLATE/feature_request.yml +26 -0
- .github/ISSUE_TEMPLATE/question.yml +26 -0
- .github/dependabot.yml +206 -0
- .github/pull_request_template.md +32 -0
- .github/workflows/copilot-setup-steps.yml +58 -0
- .github/workflows/docker-build-lite.yml +91 -0
- .github/workflows/docker-build-manual.yml +87 -0
- .github/workflows/docker-publish.yml +90 -0
- .github/workflows/linting.yaml +134 -0
- .github/workflows/pypi-publish.yml +101 -0
- .github/workflows/stale.yaml +27 -0
- .github/workflows/tests.yml +56 -0
- .gitignore +86 -0
- .pre-commit-config.yaml +28 -0
- AGENTS.md +61 -0
- Dockerfile +109 -0
- Dockerfile.lite +110 -0
- LICENSE +21 -0
- MANIFEST.in +4 -0
- Makefile +99 -0
- README-zh.md +433 -0
- android-chrome-512x512.png → README.assets/b2aaf634151b4706892693ffb43d9093.png +2 -2
- apple-touch-icon.png → README.assets/iShot_2025-03-23_12.40.08.png +2 -2
- README.md +435 -2
- SECURITY.md +18 -0
- _redirects +0 -1
- assets/AIAssistant-CUeVd4uO.css +0 -1
- assets/AIAssistant-foLVZGCT.js +0 -1
- assets/Dashboard-Bu4sLdM_.js +0 -1
- assets/Dashboard-DplGvfIe.css +0 -1
- assets/DeviceList-uEjUqpus.js +0 -1
- assets/DeviceProfile-CSWlLc0Y.js +0 -1
- assets/Devices-Cc89Wtut.js +0 -1
- assets/Devices-DMAvR0Dh.css +0 -1
- assets/EvidenceLinks-A2VHKnJo.js +0 -2
- assets/EvidenceLinks-BVpp2_oz.css +0 -1
- assets/GspLog-BEwbqtYT.js +0 -1
- assets/GspLog-Bk61_0BY.css +0 -1
- assets/LiteWrite.png +3 -0
- assets/Login-Cq7VUOkg.css +0 -1
- assets/Login-Dv6ikErd.js +0 -1
- assets/Operations-D1k_u3uX.js +0 -1
- 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 |
-
*
|
| 2 |
-
*
|
| 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 |
+

|
| 102 |
+
*图1:LightRAG索引流程图 - 图片来源:[Source](https://learnopencv.com/lightrag/)*
|
| 103 |
+

|
| 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 |
+

|
| 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 |
+
[](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:
|
|
|
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+

|
| 112 |
+
*Figure 1: LightRAG Indexing Flowchart - Img Caption : [Source](https://learnopencv.com/lightrag/)*
|
| 113 |
+

|
| 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 |
+

|
| 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 |
+
[](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
|
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}}
|
|
|
|
|
|