chore: upload MAC codebase to HF Space
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +126 -0
- .gitattributes +4 -35
- .gitignore +82 -0
- .vscode/settings.json +3 -0
- Dockerfile +27 -0
- README.md +252 -10
- alembic.ini +119 -0
- alembic/README +1 -0
- alembic/env.py +87 -0
- alembic/script.py.mako +26 -0
- alembic/versions/20260426_0001_initial_schema.py +56 -0
- alembic/versions/20260427_0002_session1_tables.py +200 -0
- alembic/versions/20260427_0003_file_share_node_columns.py +54 -0
- build/MAC-Installer/Analysis-00.toc +0 -0
- build/MAC-Installer/EXE-00.toc +0 -0
- build/MAC-Installer/MAC-Installer.pkg +3 -0
- build/MAC-Installer/PKG-00.toc +0 -0
- build/MAC-Installer/PYZ-00.pyz +3 -0
- build/MAC-Installer/PYZ-00.toc +1247 -0
- build/MAC-Installer/base_library.zip +3 -0
- build/MAC-Installer/warn-MAC-Installer.txt +230 -0
- build/MAC-Installer/xref-MAC-Installer.html +0 -0
- build/spec/MAC-Installer.spec +39 -0
- delete later/Loader.svelte +208 -0
- delete later/MAC Loader.html +287 -0
- delete later/MBM-MAC Globe.html +0 -0
- dist/MAC-Installer.exe +3 -0
- docker-compose.worker.yml +104 -0
- docker-compose.yml +226 -0
- docs/ARCHITECTURE.md +536 -0
- docs/MAC-CONTEXT.md +883 -0
- docs/MAC-PROGRESS.md +202 -0
- frontend/build.sh +15 -0
- frontend/build/_app/env.js +1 -0
- frontend/build/_app/immutable/assets/0.DBvVKUFC.css +1 -0
- frontend/build/_app/immutable/assets/12.BQVrdhQn.css +1 -0
- frontend/build/_app/immutable/assets/13.M6eN8M_c.css +1 -0
- frontend/build/_app/immutable/assets/2.DfxUCL9T.css +1 -0
- frontend/build/_app/immutable/assets/5.Bb_sFVPM.css +1 -0
- frontend/build/_app/immutable/assets/8.D2JiE0Gd.css +1 -0
- frontend/build/_app/immutable/assets/Loader.CSywfDIO.css +1 -0
- frontend/build/_app/immutable/chunks/B8pdRQVM.js +1 -0
- frontend/build/_app/immutable/chunks/BIHI7g3E.js +1 -0
- frontend/build/_app/immutable/chunks/BOGzIfIj.js +11 -0
- frontend/build/_app/immutable/chunks/BRcwu1Xf.js +1 -0
- frontend/build/_app/immutable/chunks/BT_qo9Cc.js +1 -0
- frontend/build/_app/immutable/chunks/BcWCHg3k.js +1 -0
- frontend/build/_app/immutable/chunks/BjvCllst.js +1 -0
- frontend/build/_app/immutable/chunks/BkDXvb8s.js +1 -0
- frontend/build/_app/immutable/chunks/BprE2qdV.js +1 -0
.env.example
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ═══════════════════════════════════════════════════════════
|
| 2 |
+
# MAC — MBM AI Cloud | Local Server Configuration
|
| 3 |
+
# ═══════════════════════════════════════════════════════════
|
| 4 |
+
# Copy this to .env: cp .env.example .env
|
| 5 |
+
# Then run: docker compose up -d
|
| 6 |
+
# ═══════════════════════════════════════════════════════════
|
| 7 |
+
|
| 8 |
+
# ── App ───────────────────────────────────────────────────
|
| 9 |
+
MAC_ENV=development
|
| 10 |
+
MAC_HOST=0.0.0.0
|
| 11 |
+
MAC_PORT=8000
|
| 12 |
+
MAC_DEBUG=false
|
| 13 |
+
MAC_SECRET_KEY=change-me-to-random-string
|
| 14 |
+
MAC_CORS_ORIGINS=["*"]
|
| 15 |
+
MAC_WORKERS=4 # Uvicorn worker processes
|
| 16 |
+
|
| 17 |
+
# ── Network binding ────────────────────────────────────────
|
| 18 |
+
# Set APP_HOST to a specific IP to restrict which interface the app listens on.
|
| 19 |
+
# Leave as 0.0.0.0 to accept connections on all interfaces.
|
| 20 |
+
# The installer sets this to the system's configured static IP.
|
| 21 |
+
APP_HOST=0.0.0.0
|
| 22 |
+
APP_PORT=80
|
| 23 |
+
|
| 24 |
+
# ── Database (PostgreSQL — persistent storage) ────────────
|
| 25 |
+
DATABASE_URL=postgresql+asyncpg://mac:mac_password@localhost:5432/mac_db
|
| 26 |
+
PGADMIN_PORT=5050
|
| 27 |
+
PGADMIN_DEFAULT_EMAIL=admin@mbm.local
|
| 28 |
+
PGADMIN_DEFAULT_PASSWORD=ChangeThisStrongPassword!
|
| 29 |
+
|
| 30 |
+
# ── Redis (rate limiting & caching) ──────────────────────
|
| 31 |
+
REDIS_URL=redis://localhost:6379/0
|
| 32 |
+
|
| 33 |
+
# ── JWT Auth ──────────────────────────────────────────────
|
| 34 |
+
JWT_SECRET_KEY=change-me-jwt-secret-random-string
|
| 35 |
+
JWT_ALGORITHM=HS256
|
| 36 |
+
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
| 37 |
+
|
| 38 |
+
# ── vLLM Local GPU Inference ─────────────────────────────
|
| 39 |
+
# Each model runs its own vLLM instance on a separate port.
|
| 40 |
+
# Docker Compose sets these automatically via service names.
|
| 41 |
+
VLLM_BASE_URL=http://localhost:8001
|
| 42 |
+
VLLM_SPEED_URL=http://localhost:8001
|
| 43 |
+
VLLM_CODE_URL=http://localhost:8002
|
| 44 |
+
VLLM_REASONING_URL=http://localhost:8003
|
| 45 |
+
VLLM_INTELLIGENCE_URL=http://localhost:8004
|
| 46 |
+
VLLM_API_KEY=
|
| 47 |
+
VLLM_TIMEOUT=120 # HTTP timeout (seconds) for LLM requests
|
| 48 |
+
VLLM_HEALTH_TIMEOUT=5 # Timeout for model health checks
|
| 49 |
+
|
| 50 |
+
# ── Model Registry ────────────────────────────────────────
|
| 51 |
+
# Override the entire model list with a JSON array (leave empty for defaults)
|
| 52 |
+
# Each object needs: id, name, served_name, url_key, category,
|
| 53 |
+
# parameters, context_length, capabilities (list), specialty.
|
| 54 |
+
MAC_MODELS_JSON=
|
| 55 |
+
|
| 56 |
+
# Only enable specific models from the built-in list (comma-separated IDs)
|
| 57 |
+
# Example: MAC_ENABLED_MODELS=qwen2.5:7b,qwen2.5-coder:7b
|
| 58 |
+
MAC_ENABLED_MODELS=
|
| 59 |
+
|
| 60 |
+
# Which model ID the "auto" keyword falls back to (empty = first code model)
|
| 61 |
+
MAC_AUTO_FALLBACK=
|
| 62 |
+
|
| 63 |
+
# Default max_tokens when the client doesn't specify
|
| 64 |
+
MAC_DEFAULT_MAX_TOKENS=2048
|
| 65 |
+
|
| 66 |
+
# ── Open-source model auto-download (first app use) ─────
|
| 67 |
+
# Set to true to prefetch Hugging Face models into local cache after first use.
|
| 68 |
+
# Limit=0 means all detected open-source model repos.
|
| 69 |
+
MAC_MODEL_AUTO_DOWNLOAD_ON_USE=true
|
| 70 |
+
MAC_MODEL_AUTO_DOWNLOAD_LIMIT=0
|
| 71 |
+
|
| 72 |
+
# ── Docker Compose vLLM Tuning ────────────────────────────
|
| 73 |
+
# Adjust these to match your GPU VRAM. 24GB GPU example:
|
| 74 |
+
# Speed (7B) ≈ 5GB, Code (7B) ≈ 5GB, Reason (14B) ≈ 9GB → 19GB total
|
| 75 |
+
VLLM_SPEED_MODEL=Qwen/Qwen2.5-7B-Instruct
|
| 76 |
+
VLLM_SPEED_PORT=8001
|
| 77 |
+
VLLM_SPEED_GPU_MEM=0.22
|
| 78 |
+
VLLM_SPEED_MAX_LEN=8192
|
| 79 |
+
|
| 80 |
+
VLLM_CODE_MODEL=Qwen/Qwen2.5-Coder-7B-Instruct
|
| 81 |
+
VLLM_CODE_PORT=8002
|
| 82 |
+
VLLM_CODE_GPU_MEM=0.22
|
| 83 |
+
VLLM_CODE_MAX_LEN=8192
|
| 84 |
+
|
| 85 |
+
VLLM_REASON_MODEL=deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
|
| 86 |
+
VLLM_REASON_PORT=8003
|
| 87 |
+
VLLM_REASON_GPU_MEM=0.35
|
| 88 |
+
VLLM_REASON_MAX_LEN=8192
|
| 89 |
+
|
| 90 |
+
VLLM_DTYPE=auto # auto | float16 | bfloat16
|
| 91 |
+
|
| 92 |
+
# Intelligence slot (uncomment vllm-intel in docker-compose.yml first)
|
| 93 |
+
# VLLM_INTEL_MODEL=google/gemma-3-27b-it
|
| 94 |
+
# VLLM_INTEL_PORT=8004
|
| 95 |
+
# VLLM_INTEL_GPU_MEM=0.45
|
| 96 |
+
# VLLM_INTEL_MAX_LEN=4096
|
| 97 |
+
|
| 98 |
+
# ── Whisper / Speech-to-Text ─────────────────────────────
|
| 99 |
+
# Uncomment the whisper service in docker-compose.yml first.
|
| 100 |
+
# Uses OpenAI-compatible /v1/audio/transcriptions endpoint.
|
| 101 |
+
WHISPER_URL=http://localhost:8005
|
| 102 |
+
WHISPER_MODEL=Systran/faster-whisper-small
|
| 103 |
+
WHISPER_TIMEOUT=300
|
| 104 |
+
|
| 105 |
+
# ── Text-to-Speech ───────────────────────────────────────
|
| 106 |
+
# Uncomment the tts service in docker-compose.yml first.
|
| 107 |
+
# Uses OpenAI-compatible /v1/audio/speech endpoint.
|
| 108 |
+
TTS_URL=http://localhost:8006
|
| 109 |
+
TTS_MODEL=default
|
| 110 |
+
TTS_TIMEOUT=120
|
| 111 |
+
|
| 112 |
+
# ── Embeddings ────────────────────────────────────────────
|
| 113 |
+
# Optional separate embedding server. Leave empty to use VLLM_BASE_URL.
|
| 114 |
+
EMBEDDING_URL=
|
| 115 |
+
EMBEDDING_MODEL=nomic-embed-text
|
| 116 |
+
EMBEDDING_TIMEOUT=60
|
| 117 |
+
|
| 118 |
+
# ── Rate Limits ───────────────────────────────────────────
|
| 119 |
+
RATE_LIMIT_REQUESTS_PER_HOUR=100
|
| 120 |
+
RATE_LIMIT_TOKENS_PER_DAY=50000
|
| 121 |
+
|
| 122 |
+
# ── Qdrant (Vector DB for RAG) ───────────────────────────
|
| 123 |
+
QDRANT_URL=http://localhost:6333
|
| 124 |
+
|
| 125 |
+
# ── SearXNG (Web Search) ─────────────────────────────────
|
| 126 |
+
SEARXNG_URL=http://localhost:8888
|
.gitattributes
CHANGED
|
@@ -1,35 +1,4 @@
|
|
| 1 |
-
*.
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 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
|
|
|
|
| 1 |
+
*.exe filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
build/MAC-Installer/base_library.zip filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
build/MAC-Installer/MAC-Installer.pkg filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
build/MAC-Installer/PYZ-00.pyz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# Virtual environments
|
| 7 |
+
venv/
|
| 8 |
+
.venv/
|
| 9 |
+
env/
|
| 10 |
+
|
| 11 |
+
# Environment
|
| 12 |
+
.env
|
| 13 |
+
|
| 14 |
+
# Database
|
| 15 |
+
*.db
|
| 16 |
+
*.db-journal
|
| 17 |
+
|
| 18 |
+
# IDE
|
| 19 |
+
.vscode/
|
| 20 |
+
.idea/
|
| 21 |
+
*.swp
|
| 22 |
+
*.swo
|
| 23 |
+
|
| 24 |
+
# OS
|
| 25 |
+
.DS_Store
|
| 26 |
+
Thumbs.db
|
| 27 |
+
|
| 28 |
+
# Docs build artifacts
|
| 29 |
+
docs/*.pdf
|
| 30 |
+
docs/*.docx
|
| 31 |
+
|
| 32 |
+
# Frontend build artifacts
|
| 33 |
+
frontend/node_modules/
|
| 34 |
+
frontend/.svelte-kit/
|
| 35 |
+
frontend/build/
|
| 36 |
+
|
| 37 |
+
# Temp / scratch folders
|
| 38 |
+
delete later/
|
| 39 |
+
|
| 40 |
+
# PyInstaller build artifacts (keep dist/ for the released EXE)
|
| 41 |
+
build/pyi/
|
| 42 |
+
build/MAC-Installer/
|
| 43 |
+
installer/__pycache__/
|
| 44 |
+
|
| 45 |
+
# SSL certs (self-signed)
|
| 46 |
+
nginx/ssl/
|
| 47 |
+
|
| 48 |
+
# Logs
|
| 49 |
+
*.log
|
| 50 |
+
vllm-logs*.txt
|
| 51 |
+
|
| 52 |
+
# Testing
|
| 53 |
+
.pytest_cache/
|
| 54 |
+
.coverage
|
| 55 |
+
htmlcov/
|
| 56 |
+
|
| 57 |
+
# Uploads (user content)
|
| 58 |
+
uploads/*
|
| 59 |
+
!uploads/.gitkeep
|
| 60 |
+
|
| 61 |
+
# Logs
|
| 62 |
+
logs/
|
| 63 |
+
*.log
|
| 64 |
+
|
| 65 |
+
# Docker volumes
|
| 66 |
+
pgdata/
|
| 67 |
+
redisdata/
|
| 68 |
+
|
| 69 |
+
# Keys
|
| 70 |
+
*.pem
|
| 71 |
+
*.key
|
| 72 |
+
|
| 73 |
+
# Local/generated build artifacts
|
| 74 |
+
build/
|
| 75 |
+
dist/*
|
| 76 |
+
!dist/MAC-Installer.exe
|
| 77 |
+
frontend/build/
|
| 78 |
+
installer/build/
|
| 79 |
+
installer/dist/
|
| 80 |
+
|
| 81 |
+
# Local assistant config
|
| 82 |
+
.claude/
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"python-envs.defaultEnvManager": "ms-python.python:system"
|
| 3 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install system deps
|
| 6 |
+
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
| 7 |
+
|
| 8 |
+
# Install Python deps
|
| 9 |
+
COPY requirements.txt .
|
| 10 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 11 |
+
|
| 12 |
+
# Copy application code
|
| 13 |
+
COPY alembic.ini .
|
| 14 |
+
COPY alembic/ alembic/
|
| 15 |
+
COPY mac/ mac/
|
| 16 |
+
COPY frontend/ frontend/
|
| 17 |
+
|
| 18 |
+
# Don't run as root in production
|
| 19 |
+
RUN useradd -m appuser && chown -R appuser:appuser /app
|
| 20 |
+
USER appuser
|
| 21 |
+
|
| 22 |
+
EXPOSE 8000
|
| 23 |
+
|
| 24 |
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
| 25 |
+
CMD curl -f http://localhost:8000/api/v1 || exit 1
|
| 26 |
+
|
| 27 |
+
CMD sh -c "alembic upgrade head && uvicorn mac.main:app --host 0.0.0.0 --port 8000 --workers ${MAC_WORKERS:-4}"
|
README.md
CHANGED
|
@@ -1,10 +1,252 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: MAC
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned:
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: MAC - MBM AI Cloud
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: true
|
| 8 |
+
license: mit
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
<p align="center">
|
| 12 |
+
<img src="logo.png" alt="MAC — MBM AI Cloud" width="160" />
|
| 13 |
+
</p>
|
| 14 |
+
|
| 15 |
+
<h1 align="center">MAC — MBM AI Cloud</h1>
|
| 16 |
+
|
| 17 |
+
<p align="center">
|
| 18 |
+
<strong>Self-hosted AI platform for MBM University Jodhpur.</strong><br/>
|
| 19 |
+
Private ChatGPT-style chat, Jupyter-style notebooks, RAG over college documents,<br/>
|
| 20 |
+
face-based attendance, AI exam grading — all running on the college's own GPUs.
|
| 21 |
+
</p>
|
| 22 |
+
|
| 23 |
+
<p align="center">
|
| 24 |
+
<img src="https://img.shields.io/badge/Python-3.11+-3776AB?style=flat-square&logo=python&logoColor=white" />
|
| 25 |
+
<img src="https://img.shields.io/badge/FastAPI-0.115-009688?style=flat-square&logo=fastapi&logoColor=white" />
|
| 26 |
+
<img src="https://img.shields.io/badge/SvelteKit-2-FF3E00?style=flat-square&logo=svelte&logoColor=white" />
|
| 27 |
+
<img src="https://img.shields.io/badge/PostgreSQL-16-4169E1?style=flat-square&logo=postgresql&logoColor=white" />
|
| 28 |
+
<img src="https://img.shields.io/badge/Redis-7-DC382D?style=flat-square&logo=redis&logoColor=white" />
|
| 29 |
+
<img src="https://img.shields.io/badge/vLLM-inference-6B4FBB?style=flat-square" />
|
| 30 |
+
<img src="https://img.shields.io/badge/Docker-Compose-2496ED?style=flat-square&logo=docker&logoColor=white" />
|
| 31 |
+
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" />
|
| 32 |
+
</p>
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## What is MAC?
|
| 37 |
+
|
| 38 |
+
**MAC (MBM AI Cloud)** is a fully on-premise AI platform built for MBM University, Jodhpur. It gives students, faculty, and admins a unified interface for AI-powered tools — with **zero external API calls**. All inference runs locally on the university's GPU cluster via [vLLM](https://github.com/vllm-project/vllm).
|
| 39 |
+
|
| 40 |
+
> Think: a private, self-hosted ChatGPT + Jupyter + Google Classroom, built and controlled entirely by the university.
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## Features
|
| 45 |
+
|
| 46 |
+
| Feature | Description |
|
| 47 |
+
|---|---|
|
| 48 |
+
| **AI Chat** | Streaming chat with open-source LLMs (Qwen, DeepSeek, etc.). Custom system prompts, guardrails, multi-language support (19 Indian languages). |
|
| 49 |
+
| **Notebooks** | Kaggle/Colab-style code execution cells (Python, JS, SQL) backed by Docker kernel containers or remote GPU workers. |
|
| 50 |
+
| **RAG Search** | Upload PDFs and documents; query them with AI-augmented answers. Per-subject collections. |
|
| 51 |
+
| **Attendance** | Face-capture based check-in. Faculty creates session → students selfie-check-in → export CSV/PDF. |
|
| 52 |
+
| **Copy Check** | Upload exam answer sheets; AI grades per-question with marks + feedback; plagiarism detection across submissions. |
|
| 53 |
+
| **Doubts Forum** | Students post questions; AI drafts answers; faculty moderates. |
|
| 54 |
+
| **File Sharing** | Admin/faculty distribute class materials; per-file download analytics. |
|
| 55 |
+
| **API Keys** | Scoped `mac_sk_*` API keys for students to access models from anywhere (OpenAI-compatible endpoint). |
|
| 56 |
+
| **Multi-node Cluster** | GPU worker nodes register via one-time token, send heartbeats every 10 s; master load-balances LLM requests by GPU utilisation. |
|
| 57 |
+
| **Admin Console** | Feature flags, quota overrides, guardrail rules, cluster management, system diagnostics. |
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## Architecture
|
| 62 |
+
|
| 63 |
+
```
|
| 64 |
+
Browser / API Client
|
| 65 |
+
│ HTTPS
|
| 66 |
+
▼
|
| 67 |
+
Nginx (port 80/443)
|
| 68 |
+
│
|
| 69 |
+
├─ / → SvelteKit PWA (static)
|
| 70 |
+
└─ /api/v1/* → FastAPI backend
|
| 71 |
+
│
|
| 72 |
+
┌─────────────┼──────────────┬────────────┐
|
| 73 |
+
▼ ▼ ▼ ▼
|
| 74 |
+
PostgreSQL Redis Qdrant SearXNG
|
| 75 |
+
(primary DB) (JWT blacklist (RAG vectors) (web search)
|
| 76 |
+
rate limits)
|
| 77 |
+
│
|
| 78 |
+
load_balancer.get_best_worker()
|
| 79 |
+
│
|
| 80 |
+
GPU Worker Nodes (LAN)
|
| 81 |
+
└── vLLM (OpenAI-compatible)
|
| 82 |
+
└── worker_agent.py (heartbeat every 10 s)
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
**Routing algorithm:** `gpu_util × 0.5 + vram_ratio × 0.3` — workers stale after 30 s are skipped.
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## Repository Layout
|
| 90 |
+
|
| 91 |
+
```
|
| 92 |
+
mac/ FastAPI backend
|
| 93 |
+
routers/ API route handlers (thin — parse, auth, call service)
|
| 94 |
+
services/ Business logic (no HTTP types)
|
| 95 |
+
models/ SQLAlchemy ORM models
|
| 96 |
+
schemas/ Pydantic request/response schemas
|
| 97 |
+
middleware/ Auth, rate-limit, feature-gate middleware
|
| 98 |
+
utils/ JWT, security helpers
|
| 99 |
+
|
| 100 |
+
frontend/ SvelteKit 2 PWA
|
| 101 |
+
src/routes/ Page components (chat, dashboard, notebooks, rag, …)
|
| 102 |
+
src/lib/ API client, stores, i18n (19 languages), utils
|
| 103 |
+
|
| 104 |
+
alembic/ Database migration environment + versioned revisions
|
| 105 |
+
installer/ Windows GUI installer (PyInstaller + Tkinter)
|
| 106 |
+
nginx/ Reverse proxy config (HTTP + HTTPS)
|
| 107 |
+
tests/ pytest suite
|
| 108 |
+
dist/ Built installer — MAC-Installer.exe
|
| 109 |
+
docker-compose.yml Master node deployment stack
|
| 110 |
+
docker-compose.worker.yml Worker node deployment stack
|
| 111 |
+
worker_agent.py Worker enrollment + heartbeat agent
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## Quick Start
|
| 117 |
+
|
| 118 |
+
**Prerequisites:** Docker Desktop, Python 3.11+, Git
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
git clone https://github.com/mbmuniversity2026/MAC.git
|
| 122 |
+
cd MAC
|
| 123 |
+
cp .env.example .env # edit DB password, model paths, etc.
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Start all services:
|
| 127 |
+
|
| 128 |
+
```bash
|
| 129 |
+
docker compose up -d --build
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
Open **http://localhost** — the setup wizard runs on first boot to create the admin account.
|
| 133 |
+
API docs: **http://localhost:8000/docs**
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## Adding a GPU Worker Node
|
| 138 |
+
|
| 139 |
+
On the **master**, mint an enrollment token:
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
curl -X POST http://MASTER_IP:8000/api/v1/cluster/enroll-token \
|
| 143 |
+
-H "Authorization: Bearer ADMIN_JWT" \
|
| 144 |
+
-d '{"label": "Lab PC 1", "expires_hours": 24}'
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
On the **worker PC**, create a `.env` with:
|
| 148 |
+
|
| 149 |
+
```env
|
| 150 |
+
MAC_MASTER_URL=http://MASTER_IP:8000
|
| 151 |
+
MAC_ENROLL_TOKEN=<token from above>
|
| 152 |
+
MAC_VLLM_PORT=8001
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
Then start the worker stack:
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
docker compose -f docker-compose.worker.yml up -d
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
Approve the node in **Admin → Cluster** tab. The node starts receiving LLM requests immediately.
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## Windows Installer
|
| 166 |
+
|
| 167 |
+
A standalone GUI installer (`dist/MAC-Installer.exe`) handles everything:
|
| 168 |
+
- Clones the repo, configures `.env`, sets a static IP on the network adapter, starts all Docker services.
|
| 169 |
+
|
| 170 |
+
To rebuild it:
|
| 171 |
+
|
| 172 |
+
```powershell
|
| 173 |
+
powershell -ExecutionPolicy Bypass -File .\installer\build_installer.ps1
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## Tech Stack
|
| 179 |
+
|
| 180 |
+
| Layer | Technology |
|
| 181 |
+
|---|---|
|
| 182 |
+
| Backend API | FastAPI 0.115, Python 3.11+ |
|
| 183 |
+
| Database | PostgreSQL 16 + Alembic |
|
| 184 |
+
| Cache / Rate-limit / Blacklist | Redis 7 |
|
| 185 |
+
| LLM inference | vLLM (OpenAI-compatible, GPU) |
|
| 186 |
+
| Vector DB | Qdrant |
|
| 187 |
+
| Web search | SearXNG |
|
| 188 |
+
| Frontend | SvelteKit 2 + Svelte 5 + Tailwind CSS 3 + Vite 6 |
|
| 189 |
+
| Reverse proxy | Nginx |
|
| 190 |
+
| Containerisation | Docker Compose |
|
| 191 |
+
| Installer | PyInstaller (Windows) |
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
## Documentation
|
| 196 |
+
|
| 197 |
+
| Document | Description |
|
| 198 |
+
|---|---|
|
| 199 |
+
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Full architecture, subsystem deep-dives, deployment guide |
|
| 200 |
+
| [docs/MAC-CONTEXT.md](docs/MAC-CONTEXT.md) | Complete agent context — stack, auth, routing, design decisions |
|
| 201 |
+
| [docs/MAC-PROGRESS.md](docs/MAC-PROGRESS.md) | Build progress log and roadmap |
|
| 202 |
+
|
| 203 |
+
---
|
| 204 |
+
|
| 205 |
+
## License
|
| 206 |
+
|
| 207 |
+
MIT © MBM University Jodhpur
|
| 208 |
+
|
| 209 |
+
Environment flags:
|
| 210 |
+
- MAC_MODEL_AUTO_DOWNLOAD_ON_USE=true
|
| 211 |
+
- MAC_MODEL_AUTO_DOWNLOAD_LIMIT=0
|
| 212 |
+
|
| 213 |
+
This enables background pulling for configured open-source repositories when API usage begins.
|
| 214 |
+
|
| 215 |
+
## Testing
|
| 216 |
+
|
| 217 |
+
Run full tests:
|
| 218 |
+
|
| 219 |
+
```bash
|
| 220 |
+
pytest
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
Run CPU-safe subset (no GPU-specific tests):
|
| 224 |
+
|
| 225 |
+
```bash
|
| 226 |
+
pytest -k "not gpu"
|
| 227 |
+
```
|
| 228 |
+
|
| 229 |
+
## Windows Installer
|
| 230 |
+
|
| 231 |
+
Build the standalone installer executable:
|
| 232 |
+
|
| 233 |
+
```powershell
|
| 234 |
+
powershell -ExecutionPolicy Bypass -File .\installer\build_installer.ps1
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
Output artifact:
|
| 238 |
+
- dist/MAC-Installer.exe
|
| 239 |
+
|
| 240 |
+
The installer uses embedded base64 branding assets from installer/embedded_assets.py so branding is retained even if source image files are unavailable during runtime.
|
| 241 |
+
|
| 242 |
+
## Security and Operations Notes
|
| 243 |
+
|
| 244 |
+
- Keep .env secrets private and never commit credentials.
|
| 245 |
+
- PostgreSQL is the canonical datastore in deployment.
|
| 246 |
+
- Apply migrations via Alembic before serving traffic.
|
| 247 |
+
- Use scoped API keys for automation instead of sharing admin JWTs.
|
| 248 |
+
|
| 249 |
+
## License
|
| 250 |
+
|
| 251 |
+
Internal/Institutional project repository.
|
| 252 |
+
|
alembic.ini
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A generic, single database configuration.
|
| 2 |
+
|
| 3 |
+
[alembic]
|
| 4 |
+
# path to migration scripts
|
| 5 |
+
# Use forward slashes (/) also on windows to provide an os agnostic path
|
| 6 |
+
script_location = alembic
|
| 7 |
+
|
| 8 |
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
| 9 |
+
# Uncomment the line below if you want the files to be prepended with date and time
|
| 10 |
+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
| 11 |
+
# for all available tokens
|
| 12 |
+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
| 13 |
+
|
| 14 |
+
# sys.path path, will be prepended to sys.path if present.
|
| 15 |
+
# defaults to the current working directory.
|
| 16 |
+
prepend_sys_path = .
|
| 17 |
+
|
| 18 |
+
# timezone to use when rendering the date within the migration file
|
| 19 |
+
# as well as the filename.
|
| 20 |
+
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
| 21 |
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
| 22 |
+
# string value is passed to ZoneInfo()
|
| 23 |
+
# leave blank for localtime
|
| 24 |
+
# timezone =
|
| 25 |
+
|
| 26 |
+
# max length of characters to apply to the "slug" field
|
| 27 |
+
# truncate_slug_length = 40
|
| 28 |
+
|
| 29 |
+
# set to 'true' to run the environment during
|
| 30 |
+
# the 'revision' command, regardless of autogenerate
|
| 31 |
+
# revision_environment = false
|
| 32 |
+
|
| 33 |
+
# set to 'true' to allow .pyc and .pyo files without
|
| 34 |
+
# a source .py file to be detected as revisions in the
|
| 35 |
+
# versions/ directory
|
| 36 |
+
# sourceless = false
|
| 37 |
+
|
| 38 |
+
# version location specification; This defaults
|
| 39 |
+
# to alembic/versions. When using multiple version
|
| 40 |
+
# directories, initial revisions must be specified with --version-path.
|
| 41 |
+
# The path separator used here should be the separator specified by "version_path_separator" below.
|
| 42 |
+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
| 43 |
+
|
| 44 |
+
# version path separator; As mentioned above, this is the character used to split
|
| 45 |
+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
| 46 |
+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
| 47 |
+
# Valid values for version_path_separator are:
|
| 48 |
+
#
|
| 49 |
+
# version_path_separator = :
|
| 50 |
+
# version_path_separator = ;
|
| 51 |
+
# version_path_separator = space
|
| 52 |
+
# version_path_separator = newline
|
| 53 |
+
#
|
| 54 |
+
# Use os.pathsep. Default configuration used for new projects.
|
| 55 |
+
version_path_separator = os
|
| 56 |
+
|
| 57 |
+
# set to 'true' to search source files recursively
|
| 58 |
+
# in each "version_locations" directory
|
| 59 |
+
# new in Alembic version 1.10
|
| 60 |
+
# recursive_version_locations = false
|
| 61 |
+
|
| 62 |
+
# the output encoding used when revision files
|
| 63 |
+
# are written from script.py.mako
|
| 64 |
+
# output_encoding = utf-8
|
| 65 |
+
|
| 66 |
+
sqlalchemy.url = driver://user:pass@localhost/dbname
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
[post_write_hooks]
|
| 70 |
+
# post_write_hooks defines scripts or Python functions that are run
|
| 71 |
+
# on newly generated revision scripts. See the documentation for further
|
| 72 |
+
# detail and examples
|
| 73 |
+
|
| 74 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
| 75 |
+
# hooks = black
|
| 76 |
+
# black.type = console_scripts
|
| 77 |
+
# black.entrypoint = black
|
| 78 |
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
| 79 |
+
|
| 80 |
+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
| 81 |
+
# hooks = ruff
|
| 82 |
+
# ruff.type = exec
|
| 83 |
+
# ruff.executable = %(here)s/.venv/bin/ruff
|
| 84 |
+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
| 85 |
+
|
| 86 |
+
# Logging configuration
|
| 87 |
+
[loggers]
|
| 88 |
+
keys = root,sqlalchemy,alembic
|
| 89 |
+
|
| 90 |
+
[handlers]
|
| 91 |
+
keys = console
|
| 92 |
+
|
| 93 |
+
[formatters]
|
| 94 |
+
keys = generic
|
| 95 |
+
|
| 96 |
+
[logger_root]
|
| 97 |
+
level = WARNING
|
| 98 |
+
handlers = console
|
| 99 |
+
qualname =
|
| 100 |
+
|
| 101 |
+
[logger_sqlalchemy]
|
| 102 |
+
level = WARNING
|
| 103 |
+
handlers =
|
| 104 |
+
qualname = sqlalchemy.engine
|
| 105 |
+
|
| 106 |
+
[logger_alembic]
|
| 107 |
+
level = INFO
|
| 108 |
+
handlers =
|
| 109 |
+
qualname = alembic
|
| 110 |
+
|
| 111 |
+
[handler_console]
|
| 112 |
+
class = StreamHandler
|
| 113 |
+
args = (sys.stderr,)
|
| 114 |
+
level = NOTSET
|
| 115 |
+
formatter = generic
|
| 116 |
+
|
| 117 |
+
[formatter_generic]
|
| 118 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
| 119 |
+
datefmt = %H:%M:%S
|
alembic/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Generic single-database configuration.
|
alembic/env.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Alembic migrations env.
|
| 2 |
+
|
| 3 |
+
The application engine is async (asyncpg / aiosqlite); migrations use a
|
| 4 |
+
sync engine derived from the same URL via the conversion below. Standard
|
| 5 |
+
pattern — keeps Alembic simple and avoids the async-engine-of-sync-URL
|
| 6 |
+
mismatch that an earlier revision of this file had.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from logging.config import fileConfig
|
| 10 |
+
|
| 11 |
+
from sqlalchemy import pool, engine_from_config
|
| 12 |
+
|
| 13 |
+
from alembic import context
|
| 14 |
+
|
| 15 |
+
# this is the Alembic Config object, which provides
|
| 16 |
+
# access to the values within the .ini file in use.
|
| 17 |
+
config = context.config
|
| 18 |
+
|
| 19 |
+
# Interpret the config file for Python logging.
|
| 20 |
+
if config.config_file_name is not None:
|
| 21 |
+
fileConfig(config.config_file_name)
|
| 22 |
+
|
| 23 |
+
# Import all models so Base.metadata knows about them
|
| 24 |
+
from mac.database import Base # noqa: E402
|
| 25 |
+
import mac.models.user # noqa: F401, E402
|
| 26 |
+
import mac.models.guardrail # noqa: F401, E402
|
| 27 |
+
import mac.models.quota # noqa: F401, E402
|
| 28 |
+
import mac.models.rag # noqa: F401, E402
|
| 29 |
+
import mac.models.node # noqa: F401, E402
|
| 30 |
+
import mac.models.attendance # noqa: F401, E402
|
| 31 |
+
import mac.models.doubt # noqa: F401, E402
|
| 32 |
+
import mac.models.notification # noqa: F401, E402
|
| 33 |
+
import mac.models.agent # noqa: F401, E402
|
| 34 |
+
import mac.models.notebook # noqa: F401, E402
|
| 35 |
+
import mac.models.copy_check # noqa: F401, E402
|
| 36 |
+
import mac.models.model_submission # noqa: F401, E402
|
| 37 |
+
# Session 1: new tables
|
| 38 |
+
import mac.models.feature_flag # noqa: F401, E402
|
| 39 |
+
import mac.models.academic # noqa: F401, E402
|
| 40 |
+
import mac.models.cluster # noqa: F401, E402
|
| 41 |
+
import mac.models.file_share # noqa: F401, E402
|
| 42 |
+
import mac.models.video # noqa: F401, E402
|
| 43 |
+
import mac.models.system_config # noqa: F401, E402
|
| 44 |
+
|
| 45 |
+
target_metadata = Base.metadata
|
| 46 |
+
|
| 47 |
+
# Override sqlalchemy.url from config.py settings
|
| 48 |
+
from mac.config import settings # noqa: E402
|
| 49 |
+
config.set_main_option("sqlalchemy.url", settings.database_url.replace("+aiosqlite", "").replace("+asyncpg", "+psycopg2"))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def run_migrations_offline() -> None:
|
| 53 |
+
"""Run migrations in 'offline' mode."""
|
| 54 |
+
url = config.get_main_option("sqlalchemy.url")
|
| 55 |
+
context.configure(
|
| 56 |
+
url=url,
|
| 57 |
+
target_metadata=target_metadata,
|
| 58 |
+
literal_binds=True,
|
| 59 |
+
dialect_opts={"paramstyle": "named"},
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
with context.begin_transaction():
|
| 63 |
+
context.run_migrations()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def do_run_migrations(connection):
|
| 67 |
+
context.configure(connection=connection, target_metadata=target_metadata)
|
| 68 |
+
with context.begin_transaction():
|
| 69 |
+
context.run_migrations()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def run_migrations_online() -> None:
|
| 73 |
+
"""Run migrations in 'online' mode with a sync engine."""
|
| 74 |
+
connectable = engine_from_config(
|
| 75 |
+
config.get_section(config.config_ini_section, {}),
|
| 76 |
+
prefix="sqlalchemy.",
|
| 77 |
+
poolclass=pool.NullPool,
|
| 78 |
+
)
|
| 79 |
+
with connectable.connect() as connection:
|
| 80 |
+
do_run_migrations(connection)
|
| 81 |
+
connectable.dispose()
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if context.is_offline_mode():
|
| 85 |
+
run_migrations_offline()
|
| 86 |
+
else:
|
| 87 |
+
run_migrations_online()
|
alembic/script.py.mako
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""${message}
|
| 2 |
+
|
| 3 |
+
Revision ID: ${up_revision}
|
| 4 |
+
Revises: ${down_revision | comma,n}
|
| 5 |
+
Create Date: ${create_date}
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
${imports if imports else ""}
|
| 13 |
+
|
| 14 |
+
# revision identifiers, used by Alembic.
|
| 15 |
+
revision: str = ${repr(up_revision)}
|
| 16 |
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
| 17 |
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
| 18 |
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def upgrade() -> None:
|
| 22 |
+
${upgrades if upgrades else "pass"}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def downgrade() -> None:
|
| 26 |
+
${downgrades if downgrades else "pass"}
|
alembic/versions/20260426_0001_initial_schema.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Initial schema — bootstrap all tables from Base.metadata.
|
| 2 |
+
|
| 3 |
+
Captures the entire schema (existing tables + Session 1 additions) in one
|
| 4 |
+
shot. Subsequent migrations should use op.create_table / op.add_column
|
| 5 |
+
normally; this one is the baseline.
|
| 6 |
+
|
| 7 |
+
Revision ID: 20260426_0001
|
| 8 |
+
Revises:
|
| 9 |
+
Create Date: 2026-04-26
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from alembic import op
|
| 13 |
+
import sqlalchemy as sa # noqa: F401 (kept for op.batch_alter_table users)
|
| 14 |
+
|
| 15 |
+
# revision identifiers, used by Alembic.
|
| 16 |
+
revision = "20260426_0001"
|
| 17 |
+
down_revision = None
|
| 18 |
+
branch_labels = None
|
| 19 |
+
depends_on = None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _all_models_loaded():
|
| 23 |
+
"""Import every model module so Base.metadata is fully populated."""
|
| 24 |
+
import mac.models.user # noqa: F401
|
| 25 |
+
import mac.models.guardrail # noqa: F401
|
| 26 |
+
import mac.models.quota # noqa: F401
|
| 27 |
+
import mac.models.rag # noqa: F401
|
| 28 |
+
import mac.models.node # noqa: F401
|
| 29 |
+
import mac.models.attendance # noqa: F401
|
| 30 |
+
import mac.models.doubt # noqa: F401
|
| 31 |
+
import mac.models.notification # noqa: F401
|
| 32 |
+
import mac.models.agent # noqa: F401
|
| 33 |
+
import mac.models.notebook # noqa: F401
|
| 34 |
+
import mac.models.copy_check # noqa: F401
|
| 35 |
+
import mac.models.model_submission # noqa: F401
|
| 36 |
+
# Session 1 additions
|
| 37 |
+
import mac.models.feature_flag # noqa: F401
|
| 38 |
+
import mac.models.academic # noqa: F401
|
| 39 |
+
import mac.models.cluster # noqa: F401
|
| 40 |
+
import mac.models.file_share # noqa: F401
|
| 41 |
+
import mac.models.video # noqa: F401
|
| 42 |
+
import mac.models.system_config # noqa: F401
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def upgrade() -> None:
|
| 46 |
+
_all_models_loaded()
|
| 47 |
+
from mac.database import Base
|
| 48 |
+
bind = op.get_bind()
|
| 49 |
+
Base.metadata.create_all(bind=bind, checkfirst=True)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def downgrade() -> None:
|
| 53 |
+
_all_models_loaded()
|
| 54 |
+
from mac.database import Base
|
| 55 |
+
bind = op.get_bind()
|
| 56 |
+
Base.metadata.drop_all(bind=bind, checkfirst=True)
|
alembic/versions/20260427_0002_session1_tables.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Session 1 new tables + User column additions.
|
| 2 |
+
|
| 3 |
+
Revision ID: 0002
|
| 4 |
+
Revises: 0001
|
| 5 |
+
Create Date: 2026-04-27
|
| 6 |
+
"""
|
| 7 |
+
from alembic import op
|
| 8 |
+
import sqlalchemy as sa
|
| 9 |
+
|
| 10 |
+
revision = "20260427_0002"
|
| 11 |
+
down_revision = "20260426_0001"
|
| 12 |
+
branch_labels = None
|
| 13 |
+
depends_on = None
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _table_exists(inspector, table_name: str) -> bool:
|
| 17 |
+
return table_name in inspector.get_table_names()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _column_exists(inspector, table_name: str, column_name: str) -> bool:
|
| 21 |
+
if not _table_exists(inspector, table_name):
|
| 22 |
+
return False
|
| 23 |
+
return any(col["name"] == column_name for col in inspector.get_columns(table_name))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _index_exists(inspector, table_name: str, index_name: str) -> bool:
|
| 27 |
+
if not _table_exists(inspector, table_name):
|
| 28 |
+
return False
|
| 29 |
+
return any(idx["name"] == index_name for idx in inspector.get_indexes(table_name))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _safe_create_index(table_name: str, index_name: str, columns: list[str]) -> None:
|
| 33 |
+
bind = op.get_bind()
|
| 34 |
+
insp = sa.inspect(bind)
|
| 35 |
+
if not _table_exists(insp, table_name):
|
| 36 |
+
return
|
| 37 |
+
if _index_exists(insp, table_name, index_name):
|
| 38 |
+
return
|
| 39 |
+
existing_cols = {col["name"] for col in insp.get_columns(table_name)}
|
| 40 |
+
if not all(col in existing_cols for col in columns):
|
| 41 |
+
return
|
| 42 |
+
op.create_index(index_name, table_name, columns)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def upgrade() -> None:
|
| 46 |
+
bind = op.get_bind()
|
| 47 |
+
insp = sa.inspect(bind)
|
| 48 |
+
|
| 49 |
+
# ── feature_flags ──────────────────────────────────────
|
| 50 |
+
if not _table_exists(insp, "feature_flags"):
|
| 51 |
+
op.create_table(
|
| 52 |
+
"feature_flags",
|
| 53 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 54 |
+
sa.Column("key", sa.String(100), nullable=False, unique=True),
|
| 55 |
+
sa.Column("label", sa.String(200), nullable=False, default=""),
|
| 56 |
+
sa.Column("description", sa.Text, nullable=True),
|
| 57 |
+
sa.Column("enabled", sa.Boolean, nullable=False, default=True),
|
| 58 |
+
sa.Column("allowed_roles", sa.JSON, nullable=True),
|
| 59 |
+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
|
| 60 |
+
sa.Column("updated_by", sa.String(36), nullable=True),
|
| 61 |
+
)
|
| 62 |
+
_safe_create_index("feature_flags", "ix_feature_flags_key", ["key"])
|
| 63 |
+
|
| 64 |
+
# ── system_config ──────────────────────────────────────
|
| 65 |
+
if not _table_exists(insp, "system_config"):
|
| 66 |
+
op.create_table(
|
| 67 |
+
"system_config",
|
| 68 |
+
sa.Column("key", sa.String(100), primary_key=True),
|
| 69 |
+
sa.Column("value", sa.Text, nullable=True),
|
| 70 |
+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
# ── branches ──────────────────────────────────────────
|
| 74 |
+
if not _table_exists(insp, "branches"):
|
| 75 |
+
op.create_table(
|
| 76 |
+
"branches",
|
| 77 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 78 |
+
sa.Column("name", sa.String(150), nullable=False),
|
| 79 |
+
sa.Column("code", sa.String(20), nullable=False, unique=True),
|
| 80 |
+
sa.Column("hod_id", sa.String(36), nullable=True),
|
| 81 |
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# ── sections ──────────────────────────────────────────
|
| 85 |
+
if not _table_exists(insp, "sections"):
|
| 86 |
+
op.create_table(
|
| 87 |
+
"sections",
|
| 88 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 89 |
+
sa.Column("branch_id", sa.String(36), sa.ForeignKey("branches.id", ondelete="CASCADE"), nullable=False),
|
| 90 |
+
sa.Column("name", sa.String(50), nullable=False),
|
| 91 |
+
sa.Column("year", sa.Integer, nullable=False),
|
| 92 |
+
sa.Column("faculty_id", sa.String(36), nullable=True),
|
| 93 |
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
| 94 |
+
)
|
| 95 |
+
_safe_create_index("sections", "ix_sections_branch", ["branch_id"])
|
| 96 |
+
|
| 97 |
+
# ── cluster_heartbeats ─────────────────────────────────
|
| 98 |
+
if not _table_exists(insp, "cluster_heartbeats"):
|
| 99 |
+
op.create_table(
|
| 100 |
+
"cluster_heartbeats",
|
| 101 |
+
sa.Column("id", sa.BigInteger, primary_key=True, autoincrement=True),
|
| 102 |
+
sa.Column("node_id", sa.String(36), sa.ForeignKey("worker_nodes.id", ondelete="CASCADE"), nullable=False),
|
| 103 |
+
sa.Column("gpu_util", sa.SmallInteger, nullable=True),
|
| 104 |
+
sa.Column("cpu_util", sa.SmallInteger, nullable=True),
|
| 105 |
+
sa.Column("ram_used_mb", sa.Integer, nullable=True),
|
| 106 |
+
sa.Column("vram_used_mb", sa.Integer, nullable=True),
|
| 107 |
+
sa.Column("active_model", sa.String(128), nullable=True),
|
| 108 |
+
sa.Column("queue_depth", sa.SmallInteger, nullable=True),
|
| 109 |
+
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
|
| 110 |
+
)
|
| 111 |
+
_safe_create_index("cluster_heartbeats", "idx_hb_node_time", ["node_id", "recorded_at"])
|
| 112 |
+
|
| 113 |
+
# ── shared_files ───────────────────────────────────────
|
| 114 |
+
if not _table_exists(insp, "shared_files"):
|
| 115 |
+
op.create_table(
|
| 116 |
+
"shared_files",
|
| 117 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 118 |
+
sa.Column("owner_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
| 119 |
+
sa.Column("filename", sa.String(500), nullable=False),
|
| 120 |
+
sa.Column("original_name", sa.String(500), nullable=False),
|
| 121 |
+
sa.Column("mime_type", sa.String(100), nullable=True),
|
| 122 |
+
sa.Column("size_bytes", sa.Integer, nullable=False, default=0),
|
| 123 |
+
sa.Column("is_public", sa.Boolean, nullable=False, default=False),
|
| 124 |
+
sa.Column("share_token", sa.String(64), nullable=True, unique=True),
|
| 125 |
+
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True),
|
| 126 |
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
| 127 |
+
)
|
| 128 |
+
_safe_create_index("shared_files", "ix_shared_files_owner", ["owner_id"])
|
| 129 |
+
_safe_create_index("shared_files", "ix_shared_files_token", ["share_token"])
|
| 130 |
+
|
| 131 |
+
# ── file_downloads ─────────────────────────────────────
|
| 132 |
+
if not _table_exists(insp, "file_downloads"):
|
| 133 |
+
op.create_table(
|
| 134 |
+
"file_downloads",
|
| 135 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 136 |
+
sa.Column("file_id", sa.String(36), sa.ForeignKey("shared_files.id", ondelete="CASCADE"), nullable=False),
|
| 137 |
+
sa.Column("downloader_id", sa.String(36), nullable=True),
|
| 138 |
+
sa.Column("ip_address", sa.String(45), nullable=True),
|
| 139 |
+
sa.Column("downloaded_at", sa.DateTime(timezone=True), nullable=True),
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
# ── video_projects ─────────────────────────────────────
|
| 143 |
+
if not _table_exists(insp, "video_projects"):
|
| 144 |
+
op.create_table(
|
| 145 |
+
"video_projects",
|
| 146 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 147 |
+
sa.Column("owner_id", sa.String(36), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
| 148 |
+
sa.Column("title", sa.String(300), nullable=False),
|
| 149 |
+
sa.Column("status", sa.String(30), nullable=False, default="draft"),
|
| 150 |
+
sa.Column("config", sa.JSON, nullable=True),
|
| 151 |
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
| 152 |
+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# ── video_jobs ─────────────────────────────────────────
|
| 156 |
+
if not _table_exists(insp, "video_jobs"):
|
| 157 |
+
op.create_table(
|
| 158 |
+
"video_jobs",
|
| 159 |
+
sa.Column("id", sa.String(36), primary_key=True),
|
| 160 |
+
sa.Column("project_id", sa.String(36), sa.ForeignKey("video_projects.id", ondelete="CASCADE"), nullable=False),
|
| 161 |
+
sa.Column("status", sa.String(30), nullable=False, default="queued"),
|
| 162 |
+
sa.Column("progress_pct", sa.Integer, nullable=False, default=0),
|
| 163 |
+
sa.Column("output_path", sa.String(500), nullable=True),
|
| 164 |
+
sa.Column("error", sa.Text, nullable=True),
|
| 165 |
+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
| 166 |
+
sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
# ── User column additions ──────────────────────────────
|
| 170 |
+
user_cols = {col["name"] for col in insp.get_columns("users")}
|
| 171 |
+
with op.batch_alter_table("users") as batch:
|
| 172 |
+
if "branch_id" not in user_cols:
|
| 173 |
+
batch.add_column(sa.Column("branch_id", sa.String(36), nullable=True))
|
| 174 |
+
if "section_id" not in user_cols:
|
| 175 |
+
batch.add_column(sa.Column("section_id", sa.String(36), nullable=True))
|
| 176 |
+
if "year" not in user_cols:
|
| 177 |
+
batch.add_column(sa.Column("year", sa.Integer, nullable=True))
|
| 178 |
+
if "can_create_users" not in user_cols:
|
| 179 |
+
batch.add_column(sa.Column("can_create_users", sa.Boolean, nullable=False, server_default="0"))
|
| 180 |
+
if "is_founder" not in user_cols:
|
| 181 |
+
batch.add_column(sa.Column("is_founder", sa.Boolean, nullable=False, server_default="0"))
|
| 182 |
+
if "storage_quota_mb" not in user_cols:
|
| 183 |
+
batch.add_column(sa.Column("storage_quota_mb", sa.Integer, nullable=False, server_default="2048"))
|
| 184 |
+
if "storage_used_mb" not in user_cols:
|
| 185 |
+
batch.add_column(sa.Column("storage_used_mb", sa.Integer, nullable=False, server_default="0"))
|
| 186 |
+
if "cc_enabled" not in user_cols:
|
| 187 |
+
batch.add_column(sa.Column("cc_enabled", sa.Boolean, nullable=False, server_default="1"))
|
| 188 |
+
if "forced_theme" not in user_cols:
|
| 189 |
+
batch.add_column(sa.Column("forced_theme", sa.String(8), nullable=True))
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def downgrade() -> None:
|
| 193 |
+
with op.batch_alter_table("users") as batch:
|
| 194 |
+
for col in ["branch_id", "section_id", "year", "can_create_users",
|
| 195 |
+
"is_founder", "storage_quota_mb", "storage_used_mb", "cc_enabled", "forced_theme"]:
|
| 196 |
+
batch.drop_column(col)
|
| 197 |
+
|
| 198 |
+
for table in ["video_jobs", "video_projects", "file_downloads", "shared_files",
|
| 199 |
+
"cluster_heartbeats", "sections", "branches", "system_config", "feature_flags"]:
|
| 200 |
+
op.drop_table(table)
|
alembic/versions/20260427_0003_file_share_node_columns.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Add missing columns: file_share full schema, node notebook_port/tags.
|
| 2 |
+
|
| 3 |
+
Revision ID: 0003
|
| 4 |
+
Revises: 0002
|
| 5 |
+
Create Date: 2026-04-27
|
| 6 |
+
"""
|
| 7 |
+
from alembic import op
|
| 8 |
+
import sqlalchemy as sa
|
| 9 |
+
|
| 10 |
+
revision = "20260427_0003"
|
| 11 |
+
down_revision = "20260427_0002"
|
| 12 |
+
branch_labels = None
|
| 13 |
+
depends_on = None
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def upgrade() -> None:
|
| 17 |
+
bind = op.get_bind()
|
| 18 |
+
insp = sa.inspect(bind)
|
| 19 |
+
|
| 20 |
+
# ── shared_files — add columns the model expects ────────
|
| 21 |
+
shared_file_cols = {col["name"] for col in insp.get_columns("shared_files")}
|
| 22 |
+
with op.batch_alter_table("shared_files") as batch:
|
| 23 |
+
# Rename original_name → display_name (SQLite can't rename; add + copy approach)
|
| 24 |
+
if "display_name" not in shared_file_cols:
|
| 25 |
+
batch.add_column(sa.Column("display_name", sa.String(512), nullable=True))
|
| 26 |
+
if "storage_path" not in shared_file_cols:
|
| 27 |
+
batch.add_column(sa.Column("storage_path", sa.String(1024), nullable=True))
|
| 28 |
+
if "uploaded_by" not in shared_file_cols:
|
| 29 |
+
batch.add_column(sa.Column("uploaded_by", sa.String(36), nullable=True))
|
| 30 |
+
if "recipient_type" not in shared_file_cols:
|
| 31 |
+
batch.add_column(sa.Column("recipient_type", sa.String(16), nullable=True, server_default="all"))
|
| 32 |
+
if "recipient_json" not in shared_file_cols:
|
| 33 |
+
batch.add_column(sa.Column("recipient_json", sa.JSON, nullable=True))
|
| 34 |
+
if "download_count" not in shared_file_cols:
|
| 35 |
+
batch.add_column(sa.Column("download_count", sa.Integer, nullable=False, server_default="0"))
|
| 36 |
+
|
| 37 |
+
# ── worker_nodes — notebook_port and tags ────────────────
|
| 38 |
+
worker_cols = {col["name"] for col in insp.get_columns("worker_nodes")}
|
| 39 |
+
with op.batch_alter_table("worker_nodes") as batch:
|
| 40 |
+
if "notebook_port" not in worker_cols:
|
| 41 |
+
batch.add_column(sa.Column("notebook_port", sa.Integer, nullable=True))
|
| 42 |
+
if "tags" not in worker_cols:
|
| 43 |
+
batch.add_column(sa.Column("tags", sa.String(500), nullable=True))
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def downgrade() -> None:
|
| 47 |
+
with op.batch_alter_table("worker_nodes") as batch:
|
| 48 |
+
batch.drop_column("tags")
|
| 49 |
+
batch.drop_column("notebook_port")
|
| 50 |
+
|
| 51 |
+
with op.batch_alter_table("shared_files") as batch:
|
| 52 |
+
for col in ["download_count", "recipient_json", "recipient_type",
|
| 53 |
+
"uploaded_by", "storage_path", "display_name"]:
|
| 54 |
+
batch.drop_column(col)
|
build/MAC-Installer/Analysis-00.toc
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/MAC-Installer/EXE-00.toc
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/MAC-Installer/MAC-Installer.pkg
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:01d1f3ab68de8b6a13cecfa86d47adea6a04faff9b64db7ddf77535da692901a
|
| 3 |
+
size 29471354
|
build/MAC-Installer/PKG-00.toc
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/MAC-Installer/PYZ-00.pyz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cd3cd2d907308ece01949fbbfd3225f129bc9a4a282c99b0dff6e11a86744c04
|
| 3 |
+
size 4440722
|
build/MAC-Installer/PYZ-00.toc
ADDED
|
@@ -0,0 +1,1247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
('D:\\MAC\\build\\MAC-Installer\\PYZ-00.pyz',
|
| 2 |
+
[('PIL',
|
| 3 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\__init__.py',
|
| 4 |
+
'PYMODULE-2'),
|
| 5 |
+
('PIL.AvifImagePlugin',
|
| 6 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\AvifImagePlugin.py',
|
| 7 |
+
'PYMODULE-2'),
|
| 8 |
+
('PIL.BlpImagePlugin',
|
| 9 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\BlpImagePlugin.py',
|
| 10 |
+
'PYMODULE-2'),
|
| 11 |
+
('PIL.BmpImagePlugin',
|
| 12 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\BmpImagePlugin.py',
|
| 13 |
+
'PYMODULE-2'),
|
| 14 |
+
('PIL.BufrStubImagePlugin',
|
| 15 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\BufrStubImagePlugin.py',
|
| 16 |
+
'PYMODULE-2'),
|
| 17 |
+
('PIL.CurImagePlugin',
|
| 18 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\CurImagePlugin.py',
|
| 19 |
+
'PYMODULE-2'),
|
| 20 |
+
('PIL.DcxImagePlugin',
|
| 21 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\DcxImagePlugin.py',
|
| 22 |
+
'PYMODULE-2'),
|
| 23 |
+
('PIL.DdsImagePlugin',
|
| 24 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\DdsImagePlugin.py',
|
| 25 |
+
'PYMODULE-2'),
|
| 26 |
+
('PIL.EpsImagePlugin',
|
| 27 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\EpsImagePlugin.py',
|
| 28 |
+
'PYMODULE-2'),
|
| 29 |
+
('PIL.ExifTags',
|
| 30 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ExifTags.py',
|
| 31 |
+
'PYMODULE-2'),
|
| 32 |
+
('PIL.FitsImagePlugin',
|
| 33 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\FitsImagePlugin.py',
|
| 34 |
+
'PYMODULE-2'),
|
| 35 |
+
('PIL.FliImagePlugin',
|
| 36 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\FliImagePlugin.py',
|
| 37 |
+
'PYMODULE-2'),
|
| 38 |
+
('PIL.FpxImagePlugin',
|
| 39 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\FpxImagePlugin.py',
|
| 40 |
+
'PYMODULE-2'),
|
| 41 |
+
('PIL.FtexImagePlugin',
|
| 42 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\FtexImagePlugin.py',
|
| 43 |
+
'PYMODULE-2'),
|
| 44 |
+
('PIL.GbrImagePlugin',
|
| 45 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\GbrImagePlugin.py',
|
| 46 |
+
'PYMODULE-2'),
|
| 47 |
+
('PIL.GifImagePlugin',
|
| 48 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\GifImagePlugin.py',
|
| 49 |
+
'PYMODULE-2'),
|
| 50 |
+
('PIL.GimpGradientFile',
|
| 51 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\GimpGradientFile.py',
|
| 52 |
+
'PYMODULE-2'),
|
| 53 |
+
('PIL.GimpPaletteFile',
|
| 54 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\GimpPaletteFile.py',
|
| 55 |
+
'PYMODULE-2'),
|
| 56 |
+
('PIL.GribStubImagePlugin',
|
| 57 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\GribStubImagePlugin.py',
|
| 58 |
+
'PYMODULE-2'),
|
| 59 |
+
('PIL.Hdf5StubImagePlugin',
|
| 60 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\Hdf5StubImagePlugin.py',
|
| 61 |
+
'PYMODULE-2'),
|
| 62 |
+
('PIL.IcnsImagePlugin',
|
| 63 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\IcnsImagePlugin.py',
|
| 64 |
+
'PYMODULE-2'),
|
| 65 |
+
('PIL.IcoImagePlugin',
|
| 66 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\IcoImagePlugin.py',
|
| 67 |
+
'PYMODULE-2'),
|
| 68 |
+
('PIL.ImImagePlugin',
|
| 69 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImImagePlugin.py',
|
| 70 |
+
'PYMODULE-2'),
|
| 71 |
+
('PIL.Image',
|
| 72 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\Image.py',
|
| 73 |
+
'PYMODULE-2'),
|
| 74 |
+
('PIL.ImageChops',
|
| 75 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageChops.py',
|
| 76 |
+
'PYMODULE-2'),
|
| 77 |
+
('PIL.ImageCms',
|
| 78 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageCms.py',
|
| 79 |
+
'PYMODULE-2'),
|
| 80 |
+
('PIL.ImageColor',
|
| 81 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageColor.py',
|
| 82 |
+
'PYMODULE-2'),
|
| 83 |
+
('PIL.ImageFile',
|
| 84 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageFile.py',
|
| 85 |
+
'PYMODULE-2'),
|
| 86 |
+
('PIL.ImageFilter',
|
| 87 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageFilter.py',
|
| 88 |
+
'PYMODULE-2'),
|
| 89 |
+
('PIL.ImageMath',
|
| 90 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageMath.py',
|
| 91 |
+
'PYMODULE-2'),
|
| 92 |
+
('PIL.ImageMode',
|
| 93 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageMode.py',
|
| 94 |
+
'PYMODULE-2'),
|
| 95 |
+
('PIL.ImageOps',
|
| 96 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageOps.py',
|
| 97 |
+
'PYMODULE-2'),
|
| 98 |
+
('PIL.ImagePalette',
|
| 99 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImagePalette.py',
|
| 100 |
+
'PYMODULE-2'),
|
| 101 |
+
('PIL.ImageQt',
|
| 102 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageQt.py',
|
| 103 |
+
'PYMODULE-2'),
|
| 104 |
+
('PIL.ImageSequence',
|
| 105 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageSequence.py',
|
| 106 |
+
'PYMODULE-2'),
|
| 107 |
+
('PIL.ImageShow',
|
| 108 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageShow.py',
|
| 109 |
+
'PYMODULE-2'),
|
| 110 |
+
('PIL.ImageTk',
|
| 111 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageTk.py',
|
| 112 |
+
'PYMODULE-2'),
|
| 113 |
+
('PIL.ImageWin',
|
| 114 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImageWin.py',
|
| 115 |
+
'PYMODULE-2'),
|
| 116 |
+
('PIL.ImtImagePlugin',
|
| 117 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\ImtImagePlugin.py',
|
| 118 |
+
'PYMODULE-2'),
|
| 119 |
+
('PIL.IptcImagePlugin',
|
| 120 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\IptcImagePlugin.py',
|
| 121 |
+
'PYMODULE-2'),
|
| 122 |
+
('PIL.Jpeg2KImagePlugin',
|
| 123 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\Jpeg2KImagePlugin.py',
|
| 124 |
+
'PYMODULE-2'),
|
| 125 |
+
('PIL.JpegImagePlugin',
|
| 126 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\JpegImagePlugin.py',
|
| 127 |
+
'PYMODULE-2'),
|
| 128 |
+
('PIL.JpegPresets',
|
| 129 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\JpegPresets.py',
|
| 130 |
+
'PYMODULE-2'),
|
| 131 |
+
('PIL.McIdasImagePlugin',
|
| 132 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\McIdasImagePlugin.py',
|
| 133 |
+
'PYMODULE-2'),
|
| 134 |
+
('PIL.MicImagePlugin',
|
| 135 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\MicImagePlugin.py',
|
| 136 |
+
'PYMODULE-2'),
|
| 137 |
+
('PIL.MpegImagePlugin',
|
| 138 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\MpegImagePlugin.py',
|
| 139 |
+
'PYMODULE-2'),
|
| 140 |
+
('PIL.MpoImagePlugin',
|
| 141 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\MpoImagePlugin.py',
|
| 142 |
+
'PYMODULE-2'),
|
| 143 |
+
('PIL.MspImagePlugin',
|
| 144 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\MspImagePlugin.py',
|
| 145 |
+
'PYMODULE-2'),
|
| 146 |
+
('PIL.PaletteFile',
|
| 147 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PaletteFile.py',
|
| 148 |
+
'PYMODULE-2'),
|
| 149 |
+
('PIL.PalmImagePlugin',
|
| 150 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PalmImagePlugin.py',
|
| 151 |
+
'PYMODULE-2'),
|
| 152 |
+
('PIL.PcdImagePlugin',
|
| 153 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PcdImagePlugin.py',
|
| 154 |
+
'PYMODULE-2'),
|
| 155 |
+
('PIL.PcxImagePlugin',
|
| 156 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PcxImagePlugin.py',
|
| 157 |
+
'PYMODULE-2'),
|
| 158 |
+
('PIL.PdfImagePlugin',
|
| 159 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PdfImagePlugin.py',
|
| 160 |
+
'PYMODULE-2'),
|
| 161 |
+
('PIL.PdfParser',
|
| 162 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PdfParser.py',
|
| 163 |
+
'PYMODULE-2'),
|
| 164 |
+
('PIL.PixarImagePlugin',
|
| 165 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PixarImagePlugin.py',
|
| 166 |
+
'PYMODULE-2'),
|
| 167 |
+
('PIL.PngImagePlugin',
|
| 168 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PngImagePlugin.py',
|
| 169 |
+
'PYMODULE-2'),
|
| 170 |
+
('PIL.PpmImagePlugin',
|
| 171 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PpmImagePlugin.py',
|
| 172 |
+
'PYMODULE-2'),
|
| 173 |
+
('PIL.PsdImagePlugin',
|
| 174 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\PsdImagePlugin.py',
|
| 175 |
+
'PYMODULE-2'),
|
| 176 |
+
('PIL.QoiImagePlugin',
|
| 177 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\QoiImagePlugin.py',
|
| 178 |
+
'PYMODULE-2'),
|
| 179 |
+
('PIL.SgiImagePlugin',
|
| 180 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\SgiImagePlugin.py',
|
| 181 |
+
'PYMODULE-2'),
|
| 182 |
+
('PIL.SpiderImagePlugin',
|
| 183 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\SpiderImagePlugin.py',
|
| 184 |
+
'PYMODULE-2'),
|
| 185 |
+
('PIL.SunImagePlugin',
|
| 186 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\SunImagePlugin.py',
|
| 187 |
+
'PYMODULE-2'),
|
| 188 |
+
('PIL.TgaImagePlugin',
|
| 189 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\TgaImagePlugin.py',
|
| 190 |
+
'PYMODULE-2'),
|
| 191 |
+
('PIL.TiffImagePlugin',
|
| 192 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\TiffImagePlugin.py',
|
| 193 |
+
'PYMODULE-2'),
|
| 194 |
+
('PIL.TiffTags',
|
| 195 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\TiffTags.py',
|
| 196 |
+
'PYMODULE-2'),
|
| 197 |
+
('PIL.WebPImagePlugin',
|
| 198 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\WebPImagePlugin.py',
|
| 199 |
+
'PYMODULE-2'),
|
| 200 |
+
('PIL.WmfImagePlugin',
|
| 201 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\WmfImagePlugin.py',
|
| 202 |
+
'PYMODULE-2'),
|
| 203 |
+
('PIL.XVThumbImagePlugin',
|
| 204 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\XVThumbImagePlugin.py',
|
| 205 |
+
'PYMODULE-2'),
|
| 206 |
+
('PIL.XbmImagePlugin',
|
| 207 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\XbmImagePlugin.py',
|
| 208 |
+
'PYMODULE-2'),
|
| 209 |
+
('PIL.XpmImagePlugin',
|
| 210 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\XpmImagePlugin.py',
|
| 211 |
+
'PYMODULE-2'),
|
| 212 |
+
('PIL._binary',
|
| 213 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\_binary.py',
|
| 214 |
+
'PYMODULE-2'),
|
| 215 |
+
('PIL._deprecate',
|
| 216 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\_deprecate.py',
|
| 217 |
+
'PYMODULE-2'),
|
| 218 |
+
('PIL._typing',
|
| 219 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\_typing.py',
|
| 220 |
+
'PYMODULE-2'),
|
| 221 |
+
('PIL._util',
|
| 222 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\_util.py',
|
| 223 |
+
'PYMODULE-2'),
|
| 224 |
+
('PIL._version',
|
| 225 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\_version.py',
|
| 226 |
+
'PYMODULE-2'),
|
| 227 |
+
('PIL.features',
|
| 228 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\PIL\\features.py',
|
| 229 |
+
'PYMODULE-2'),
|
| 230 |
+
('__future__', 'C:\\Python313\\Lib\\__future__.py', 'PYMODULE-2'),
|
| 231 |
+
('_aix_support', 'C:\\Python313\\Lib\\_aix_support.py', 'PYMODULE-2'),
|
| 232 |
+
('_colorize', 'C:\\Python313\\Lib\\_colorize.py', 'PYMODULE-2'),
|
| 233 |
+
('_compat_pickle', 'C:\\Python313\\Lib\\_compat_pickle.py', 'PYMODULE-2'),
|
| 234 |
+
('_compression', 'C:\\Python313\\Lib\\_compression.py', 'PYMODULE-2'),
|
| 235 |
+
('_ios_support', 'C:\\Python313\\Lib\\_ios_support.py', 'PYMODULE-2'),
|
| 236 |
+
('_opcode_metadata', 'C:\\Python313\\Lib\\_opcode_metadata.py', 'PYMODULE-2'),
|
| 237 |
+
('_py_abc', 'C:\\Python313\\Lib\\_py_abc.py', 'PYMODULE-2'),
|
| 238 |
+
('_pydatetime', 'C:\\Python313\\Lib\\_pydatetime.py', 'PYMODULE-2'),
|
| 239 |
+
('_pydecimal', 'C:\\Python313\\Lib\\_pydecimal.py', 'PYMODULE-2'),
|
| 240 |
+
('_pyrepl', 'C:\\Python313\\Lib\\_pyrepl\\__init__.py', 'PYMODULE-2'),
|
| 241 |
+
('_pyrepl.pager', 'C:\\Python313\\Lib\\_pyrepl\\pager.py', 'PYMODULE-2'),
|
| 242 |
+
('_strptime', 'C:\\Python313\\Lib\\_strptime.py', 'PYMODULE-2'),
|
| 243 |
+
('_threading_local', 'C:\\Python313\\Lib\\_threading_local.py', 'PYMODULE-2'),
|
| 244 |
+
('argparse', 'C:\\Python313\\Lib\\argparse.py', 'PYMODULE-2'),
|
| 245 |
+
('ast', 'C:\\Python313\\Lib\\ast.py', 'PYMODULE-2'),
|
| 246 |
+
('asyncio', 'C:\\Python313\\Lib\\asyncio\\__init__.py', 'PYMODULE-2'),
|
| 247 |
+
('asyncio.base_events',
|
| 248 |
+
'C:\\Python313\\Lib\\asyncio\\base_events.py',
|
| 249 |
+
'PYMODULE-2'),
|
| 250 |
+
('asyncio.base_futures',
|
| 251 |
+
'C:\\Python313\\Lib\\asyncio\\base_futures.py',
|
| 252 |
+
'PYMODULE-2'),
|
| 253 |
+
('asyncio.base_subprocess',
|
| 254 |
+
'C:\\Python313\\Lib\\asyncio\\base_subprocess.py',
|
| 255 |
+
'PYMODULE-2'),
|
| 256 |
+
('asyncio.base_tasks',
|
| 257 |
+
'C:\\Python313\\Lib\\asyncio\\base_tasks.py',
|
| 258 |
+
'PYMODULE-2'),
|
| 259 |
+
('asyncio.constants',
|
| 260 |
+
'C:\\Python313\\Lib\\asyncio\\constants.py',
|
| 261 |
+
'PYMODULE-2'),
|
| 262 |
+
('asyncio.coroutines',
|
| 263 |
+
'C:\\Python313\\Lib\\asyncio\\coroutines.py',
|
| 264 |
+
'PYMODULE-2'),
|
| 265 |
+
('asyncio.events', 'C:\\Python313\\Lib\\asyncio\\events.py', 'PYMODULE-2'),
|
| 266 |
+
('asyncio.exceptions',
|
| 267 |
+
'C:\\Python313\\Lib\\asyncio\\exceptions.py',
|
| 268 |
+
'PYMODULE-2'),
|
| 269 |
+
('asyncio.format_helpers',
|
| 270 |
+
'C:\\Python313\\Lib\\asyncio\\format_helpers.py',
|
| 271 |
+
'PYMODULE-2'),
|
| 272 |
+
('asyncio.futures', 'C:\\Python313\\Lib\\asyncio\\futures.py', 'PYMODULE-2'),
|
| 273 |
+
('asyncio.locks', 'C:\\Python313\\Lib\\asyncio\\locks.py', 'PYMODULE-2'),
|
| 274 |
+
('asyncio.log', 'C:\\Python313\\Lib\\asyncio\\log.py', 'PYMODULE-2'),
|
| 275 |
+
('asyncio.mixins', 'C:\\Python313\\Lib\\asyncio\\mixins.py', 'PYMODULE-2'),
|
| 276 |
+
('asyncio.proactor_events',
|
| 277 |
+
'C:\\Python313\\Lib\\asyncio\\proactor_events.py',
|
| 278 |
+
'PYMODULE-2'),
|
| 279 |
+
('asyncio.protocols',
|
| 280 |
+
'C:\\Python313\\Lib\\asyncio\\protocols.py',
|
| 281 |
+
'PYMODULE-2'),
|
| 282 |
+
('asyncio.queues', 'C:\\Python313\\Lib\\asyncio\\queues.py', 'PYMODULE-2'),
|
| 283 |
+
('asyncio.runners', 'C:\\Python313\\Lib\\asyncio\\runners.py', 'PYMODULE-2'),
|
| 284 |
+
('asyncio.selector_events',
|
| 285 |
+
'C:\\Python313\\Lib\\asyncio\\selector_events.py',
|
| 286 |
+
'PYMODULE-2'),
|
| 287 |
+
('asyncio.sslproto',
|
| 288 |
+
'C:\\Python313\\Lib\\asyncio\\sslproto.py',
|
| 289 |
+
'PYMODULE-2'),
|
| 290 |
+
('asyncio.staggered',
|
| 291 |
+
'C:\\Python313\\Lib\\asyncio\\staggered.py',
|
| 292 |
+
'PYMODULE-2'),
|
| 293 |
+
('asyncio.streams', 'C:\\Python313\\Lib\\asyncio\\streams.py', 'PYMODULE-2'),
|
| 294 |
+
('asyncio.subprocess',
|
| 295 |
+
'C:\\Python313\\Lib\\asyncio\\subprocess.py',
|
| 296 |
+
'PYMODULE-2'),
|
| 297 |
+
('asyncio.taskgroups',
|
| 298 |
+
'C:\\Python313\\Lib\\asyncio\\taskgroups.py',
|
| 299 |
+
'PYMODULE-2'),
|
| 300 |
+
('asyncio.tasks', 'C:\\Python313\\Lib\\asyncio\\tasks.py', 'PYMODULE-2'),
|
| 301 |
+
('asyncio.threads', 'C:\\Python313\\Lib\\asyncio\\threads.py', 'PYMODULE-2'),
|
| 302 |
+
('asyncio.timeouts',
|
| 303 |
+
'C:\\Python313\\Lib\\asyncio\\timeouts.py',
|
| 304 |
+
'PYMODULE-2'),
|
| 305 |
+
('asyncio.transports',
|
| 306 |
+
'C:\\Python313\\Lib\\asyncio\\transports.py',
|
| 307 |
+
'PYMODULE-2'),
|
| 308 |
+
('asyncio.trsock', 'C:\\Python313\\Lib\\asyncio\\trsock.py', 'PYMODULE-2'),
|
| 309 |
+
('asyncio.unix_events',
|
| 310 |
+
'C:\\Python313\\Lib\\asyncio\\unix_events.py',
|
| 311 |
+
'PYMODULE-2'),
|
| 312 |
+
('asyncio.windows_events',
|
| 313 |
+
'C:\\Python313\\Lib\\asyncio\\windows_events.py',
|
| 314 |
+
'PYMODULE-2'),
|
| 315 |
+
('asyncio.windows_utils',
|
| 316 |
+
'C:\\Python313\\Lib\\asyncio\\windows_utils.py',
|
| 317 |
+
'PYMODULE-2'),
|
| 318 |
+
('base64', 'C:\\Python313\\Lib\\base64.py', 'PYMODULE-2'),
|
| 319 |
+
('bdb', 'C:\\Python313\\Lib\\bdb.py', 'PYMODULE-2'),
|
| 320 |
+
('bisect', 'C:\\Python313\\Lib\\bisect.py', 'PYMODULE-2'),
|
| 321 |
+
('bz2', 'C:\\Python313\\Lib\\bz2.py', 'PYMODULE-2'),
|
| 322 |
+
('calendar', 'C:\\Python313\\Lib\\calendar.py', 'PYMODULE-2'),
|
| 323 |
+
('charset_normalizer',
|
| 324 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\__init__.py',
|
| 325 |
+
'PYMODULE-2'),
|
| 326 |
+
('charset_normalizer.api',
|
| 327 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\api.py',
|
| 328 |
+
'PYMODULE-2'),
|
| 329 |
+
('charset_normalizer.cd',
|
| 330 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\cd.py',
|
| 331 |
+
'PYMODULE-2'),
|
| 332 |
+
('charset_normalizer.constant',
|
| 333 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\constant.py',
|
| 334 |
+
'PYMODULE-2'),
|
| 335 |
+
('charset_normalizer.legacy',
|
| 336 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\legacy.py',
|
| 337 |
+
'PYMODULE-2'),
|
| 338 |
+
('charset_normalizer.models',
|
| 339 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\models.py',
|
| 340 |
+
'PYMODULE-2'),
|
| 341 |
+
('charset_normalizer.utils',
|
| 342 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\utils.py',
|
| 343 |
+
'PYMODULE-2'),
|
| 344 |
+
('charset_normalizer.version',
|
| 345 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\charset_normalizer\\version.py',
|
| 346 |
+
'PYMODULE-2'),
|
| 347 |
+
('cmd', 'C:\\Python313\\Lib\\cmd.py', 'PYMODULE-2'),
|
| 348 |
+
('code', 'C:\\Python313\\Lib\\code.py', 'PYMODULE-2'),
|
| 349 |
+
('codeop', 'C:\\Python313\\Lib\\codeop.py', 'PYMODULE-2'),
|
| 350 |
+
('colorsys', 'C:\\Python313\\Lib\\colorsys.py', 'PYMODULE-2'),
|
| 351 |
+
('concurrent', 'C:\\Python313\\Lib\\concurrent\\__init__.py', 'PYMODULE-2'),
|
| 352 |
+
('concurrent.futures',
|
| 353 |
+
'C:\\Python313\\Lib\\concurrent\\futures\\__init__.py',
|
| 354 |
+
'PYMODULE-2'),
|
| 355 |
+
('concurrent.futures._base',
|
| 356 |
+
'C:\\Python313\\Lib\\concurrent\\futures\\_base.py',
|
| 357 |
+
'PYMODULE-2'),
|
| 358 |
+
('concurrent.futures.process',
|
| 359 |
+
'C:\\Python313\\Lib\\concurrent\\futures\\process.py',
|
| 360 |
+
'PYMODULE-2'),
|
| 361 |
+
('concurrent.futures.thread',
|
| 362 |
+
'C:\\Python313\\Lib\\concurrent\\futures\\thread.py',
|
| 363 |
+
'PYMODULE-2'),
|
| 364 |
+
('contextlib', 'C:\\Python313\\Lib\\contextlib.py', 'PYMODULE-2'),
|
| 365 |
+
('contextvars', 'C:\\Python313\\Lib\\contextvars.py', 'PYMODULE-2'),
|
| 366 |
+
('copy', 'C:\\Python313\\Lib\\copy.py', 'PYMODULE-2'),
|
| 367 |
+
('csv', 'C:\\Python313\\Lib\\csv.py', 'PYMODULE-2'),
|
| 368 |
+
('ctypes', 'C:\\Python313\\Lib\\ctypes\\__init__.py', 'PYMODULE-2'),
|
| 369 |
+
('ctypes._aix', 'C:\\Python313\\Lib\\ctypes\\_aix.py', 'PYMODULE-2'),
|
| 370 |
+
('ctypes._endian', 'C:\\Python313\\Lib\\ctypes\\_endian.py', 'PYMODULE-2'),
|
| 371 |
+
('ctypes.macholib',
|
| 372 |
+
'C:\\Python313\\Lib\\ctypes\\macholib\\__init__.py',
|
| 373 |
+
'PYMODULE-2'),
|
| 374 |
+
('ctypes.macholib.dyld',
|
| 375 |
+
'C:\\Python313\\Lib\\ctypes\\macholib\\dyld.py',
|
| 376 |
+
'PYMODULE-2'),
|
| 377 |
+
('ctypes.macholib.dylib',
|
| 378 |
+
'C:\\Python313\\Lib\\ctypes\\macholib\\dylib.py',
|
| 379 |
+
'PYMODULE-2'),
|
| 380 |
+
('ctypes.macholib.framework',
|
| 381 |
+
'C:\\Python313\\Lib\\ctypes\\macholib\\framework.py',
|
| 382 |
+
'PYMODULE-2'),
|
| 383 |
+
('ctypes.util', 'C:\\Python313\\Lib\\ctypes\\util.py', 'PYMODULE-2'),
|
| 384 |
+
('ctypes.wintypes', 'C:\\Python313\\Lib\\ctypes\\wintypes.py', 'PYMODULE-2'),
|
| 385 |
+
('dataclasses', 'C:\\Python313\\Lib\\dataclasses.py', 'PYMODULE-2'),
|
| 386 |
+
('datetime', 'C:\\Python313\\Lib\\datetime.py', 'PYMODULE-2'),
|
| 387 |
+
('decimal', 'C:\\Python313\\Lib\\decimal.py', 'PYMODULE-2'),
|
| 388 |
+
('defusedxml',
|
| 389 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\__init__.py',
|
| 390 |
+
'PYMODULE-2'),
|
| 391 |
+
('defusedxml.ElementTree',
|
| 392 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\ElementTree.py',
|
| 393 |
+
'PYMODULE-2'),
|
| 394 |
+
('defusedxml.cElementTree',
|
| 395 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\cElementTree.py',
|
| 396 |
+
'PYMODULE-2'),
|
| 397 |
+
('defusedxml.common',
|
| 398 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\common.py',
|
| 399 |
+
'PYMODULE-2'),
|
| 400 |
+
('defusedxml.expatbuilder',
|
| 401 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\expatbuilder.py',
|
| 402 |
+
'PYMODULE-2'),
|
| 403 |
+
('defusedxml.expatreader',
|
| 404 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\expatreader.py',
|
| 405 |
+
'PYMODULE-2'),
|
| 406 |
+
('defusedxml.minidom',
|
| 407 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\minidom.py',
|
| 408 |
+
'PYMODULE-2'),
|
| 409 |
+
('defusedxml.pulldom',
|
| 410 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\pulldom.py',
|
| 411 |
+
'PYMODULE-2'),
|
| 412 |
+
('defusedxml.sax',
|
| 413 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\sax.py',
|
| 414 |
+
'PYMODULE-2'),
|
| 415 |
+
('defusedxml.xmlrpc',
|
| 416 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\defusedxml\\xmlrpc.py',
|
| 417 |
+
'PYMODULE-2'),
|
| 418 |
+
('difflib', 'C:\\Python313\\Lib\\difflib.py', 'PYMODULE-2'),
|
| 419 |
+
('dis', 'C:\\Python313\\Lib\\dis.py', 'PYMODULE-2'),
|
| 420 |
+
('doctest', 'C:\\Python313\\Lib\\doctest.py', 'PYMODULE-2'),
|
| 421 |
+
('email', 'C:\\Python313\\Lib\\email\\__init__.py', 'PYMODULE-2'),
|
| 422 |
+
('email._encoded_words',
|
| 423 |
+
'C:\\Python313\\Lib\\email\\_encoded_words.py',
|
| 424 |
+
'PYMODULE-2'),
|
| 425 |
+
('email._header_value_parser',
|
| 426 |
+
'C:\\Python313\\Lib\\email\\_header_value_parser.py',
|
| 427 |
+
'PYMODULE-2'),
|
| 428 |
+
('email._parseaddr',
|
| 429 |
+
'C:\\Python313\\Lib\\email\\_parseaddr.py',
|
| 430 |
+
'PYMODULE-2'),
|
| 431 |
+
('email._policybase',
|
| 432 |
+
'C:\\Python313\\Lib\\email\\_policybase.py',
|
| 433 |
+
'PYMODULE-2'),
|
| 434 |
+
('email.base64mime',
|
| 435 |
+
'C:\\Python313\\Lib\\email\\base64mime.py',
|
| 436 |
+
'PYMODULE-2'),
|
| 437 |
+
('email.charset', 'C:\\Python313\\Lib\\email\\charset.py', 'PYMODULE-2'),
|
| 438 |
+
('email.contentmanager',
|
| 439 |
+
'C:\\Python313\\Lib\\email\\contentmanager.py',
|
| 440 |
+
'PYMODULE-2'),
|
| 441 |
+
('email.encoders', 'C:\\Python313\\Lib\\email\\encoders.py', 'PYMODULE-2'),
|
| 442 |
+
('email.errors', 'C:\\Python313\\Lib\\email\\errors.py', 'PYMODULE-2'),
|
| 443 |
+
('email.feedparser',
|
| 444 |
+
'C:\\Python313\\Lib\\email\\feedparser.py',
|
| 445 |
+
'PYMODULE-2'),
|
| 446 |
+
('email.generator', 'C:\\Python313\\Lib\\email\\generator.py', 'PYMODULE-2'),
|
| 447 |
+
('email.header', 'C:\\Python313\\Lib\\email\\header.py', 'PYMODULE-2'),
|
| 448 |
+
('email.headerregistry',
|
| 449 |
+
'C:\\Python313\\Lib\\email\\headerregistry.py',
|
| 450 |
+
'PYMODULE-2'),
|
| 451 |
+
('email.iterators', 'C:\\Python313\\Lib\\email\\iterators.py', 'PYMODULE-2'),
|
| 452 |
+
('email.message', 'C:\\Python313\\Lib\\email\\message.py', 'PYMODULE-2'),
|
| 453 |
+
('email.parser', 'C:\\Python313\\Lib\\email\\parser.py', 'PYMODULE-2'),
|
| 454 |
+
('email.policy', 'C:\\Python313\\Lib\\email\\policy.py', 'PYMODULE-2'),
|
| 455 |
+
('email.quoprimime',
|
| 456 |
+
'C:\\Python313\\Lib\\email\\quoprimime.py',
|
| 457 |
+
'PYMODULE-2'),
|
| 458 |
+
('email.utils', 'C:\\Python313\\Lib\\email\\utils.py', 'PYMODULE-2'),
|
| 459 |
+
('embedded_assets', 'D:\\MAC\\installer\\embedded_assets.py', 'PYMODULE-2'),
|
| 460 |
+
('fileinput', 'C:\\Python313\\Lib\\fileinput.py', 'PYMODULE-2'),
|
| 461 |
+
('fnmatch', 'C:\\Python313\\Lib\\fnmatch.py', 'PYMODULE-2'),
|
| 462 |
+
('fractions', 'C:\\Python313\\Lib\\fractions.py', 'PYMODULE-2'),
|
| 463 |
+
('ftplib', 'C:\\Python313\\Lib\\ftplib.py', 'PYMODULE-2'),
|
| 464 |
+
('getopt', 'C:\\Python313\\Lib\\getopt.py', 'PYMODULE-2'),
|
| 465 |
+
('getpass', 'C:\\Python313\\Lib\\getpass.py', 'PYMODULE-2'),
|
| 466 |
+
('gettext', 'C:\\Python313\\Lib\\gettext.py', 'PYMODULE-2'),
|
| 467 |
+
('glob', 'C:\\Python313\\Lib\\glob.py', 'PYMODULE-2'),
|
| 468 |
+
('gzip', 'C:\\Python313\\Lib\\gzip.py', 'PYMODULE-2'),
|
| 469 |
+
('hashlib', 'C:\\Python313\\Lib\\hashlib.py', 'PYMODULE-2'),
|
| 470 |
+
('hmac', 'C:\\Python313\\Lib\\hmac.py', 'PYMODULE-2'),
|
| 471 |
+
('html', 'C:\\Python313\\Lib\\html\\__init__.py', 'PYMODULE-2'),
|
| 472 |
+
('html.entities', 'C:\\Python313\\Lib\\html\\entities.py', 'PYMODULE-2'),
|
| 473 |
+
('http', 'C:\\Python313\\Lib\\http\\__init__.py', 'PYMODULE-2'),
|
| 474 |
+
('http.client', 'C:\\Python313\\Lib\\http\\client.py', 'PYMODULE-2'),
|
| 475 |
+
('http.cookiejar', 'C:\\Python313\\Lib\\http\\cookiejar.py', 'PYMODULE-2'),
|
| 476 |
+
('http.server', 'C:\\Python313\\Lib\\http\\server.py', 'PYMODULE-2'),
|
| 477 |
+
('importlib', 'C:\\Python313\\Lib\\importlib\\__init__.py', 'PYMODULE-2'),
|
| 478 |
+
('importlib._abc', 'C:\\Python313\\Lib\\importlib\\_abc.py', 'PYMODULE-2'),
|
| 479 |
+
('importlib._bootstrap',
|
| 480 |
+
'C:\\Python313\\Lib\\importlib\\_bootstrap.py',
|
| 481 |
+
'PYMODULE-2'),
|
| 482 |
+
('importlib._bootstrap_external',
|
| 483 |
+
'C:\\Python313\\Lib\\importlib\\_bootstrap_external.py',
|
| 484 |
+
'PYMODULE-2'),
|
| 485 |
+
('importlib.abc', 'C:\\Python313\\Lib\\importlib\\abc.py', 'PYMODULE-2'),
|
| 486 |
+
('importlib.machinery',
|
| 487 |
+
'C:\\Python313\\Lib\\importlib\\machinery.py',
|
| 488 |
+
'PYMODULE-2'),
|
| 489 |
+
('importlib.metadata',
|
| 490 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\__init__.py',
|
| 491 |
+
'PYMODULE-2'),
|
| 492 |
+
('importlib.metadata._adapters',
|
| 493 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_adapters.py',
|
| 494 |
+
'PYMODULE-2'),
|
| 495 |
+
('importlib.metadata._collections',
|
| 496 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_collections.py',
|
| 497 |
+
'PYMODULE-2'),
|
| 498 |
+
('importlib.metadata._functools',
|
| 499 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_functools.py',
|
| 500 |
+
'PYMODULE-2'),
|
| 501 |
+
('importlib.metadata._itertools',
|
| 502 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_itertools.py',
|
| 503 |
+
'PYMODULE-2'),
|
| 504 |
+
('importlib.metadata._meta',
|
| 505 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_meta.py',
|
| 506 |
+
'PYMODULE-2'),
|
| 507 |
+
('importlib.metadata._text',
|
| 508 |
+
'C:\\Python313\\Lib\\importlib\\metadata\\_text.py',
|
| 509 |
+
'PYMODULE-2'),
|
| 510 |
+
('importlib.readers',
|
| 511 |
+
'C:\\Python313\\Lib\\importlib\\readers.py',
|
| 512 |
+
'PYMODULE-2'),
|
| 513 |
+
('importlib.resources',
|
| 514 |
+
'C:\\Python313\\Lib\\importlib\\resources\\__init__.py',
|
| 515 |
+
'PYMODULE-2'),
|
| 516 |
+
('importlib.resources._adapters',
|
| 517 |
+
'C:\\Python313\\Lib\\importlib\\resources\\_adapters.py',
|
| 518 |
+
'PYMODULE-2'),
|
| 519 |
+
('importlib.resources._common',
|
| 520 |
+
'C:\\Python313\\Lib\\importlib\\resources\\_common.py',
|
| 521 |
+
'PYMODULE-2'),
|
| 522 |
+
('importlib.resources._functional',
|
| 523 |
+
'C:\\Python313\\Lib\\importlib\\resources\\_functional.py',
|
| 524 |
+
'PYMODULE-2'),
|
| 525 |
+
('importlib.resources._itertools',
|
| 526 |
+
'C:\\Python313\\Lib\\importlib\\resources\\_itertools.py',
|
| 527 |
+
'PYMODULE-2'),
|
| 528 |
+
('importlib.resources.abc',
|
| 529 |
+
'C:\\Python313\\Lib\\importlib\\resources\\abc.py',
|
| 530 |
+
'PYMODULE-2'),
|
| 531 |
+
('importlib.resources.readers',
|
| 532 |
+
'C:\\Python313\\Lib\\importlib\\resources\\readers.py',
|
| 533 |
+
'PYMODULE-2'),
|
| 534 |
+
('importlib.util', 'C:\\Python313\\Lib\\importlib\\util.py', 'PYMODULE-2'),
|
| 535 |
+
('inspect', 'C:\\Python313\\Lib\\inspect.py', 'PYMODULE-2'),
|
| 536 |
+
('ipaddress', 'C:\\Python313\\Lib\\ipaddress.py', 'PYMODULE-2'),
|
| 537 |
+
('json', 'C:\\Python313\\Lib\\json\\__init__.py', 'PYMODULE-2'),
|
| 538 |
+
('json.decoder', 'C:\\Python313\\Lib\\json\\decoder.py', 'PYMODULE-2'),
|
| 539 |
+
('json.encoder', 'C:\\Python313\\Lib\\json\\encoder.py', 'PYMODULE-2'),
|
| 540 |
+
('json.scanner', 'C:\\Python313\\Lib\\json\\scanner.py', 'PYMODULE-2'),
|
| 541 |
+
('logging', 'C:\\Python313\\Lib\\logging\\__init__.py', 'PYMODULE-2'),
|
| 542 |
+
('lzma', 'C:\\Python313\\Lib\\lzma.py', 'PYMODULE-2'),
|
| 543 |
+
('mimetypes', 'C:\\Python313\\Lib\\mimetypes.py', 'PYMODULE-2'),
|
| 544 |
+
('multiprocessing',
|
| 545 |
+
'C:\\Python313\\Lib\\multiprocessing\\__init__.py',
|
| 546 |
+
'PYMODULE-2'),
|
| 547 |
+
('multiprocessing.connection',
|
| 548 |
+
'C:\\Python313\\Lib\\multiprocessing\\connection.py',
|
| 549 |
+
'PYMODULE-2'),
|
| 550 |
+
('multiprocessing.context',
|
| 551 |
+
'C:\\Python313\\Lib\\multiprocessing\\context.py',
|
| 552 |
+
'PYMODULE-2'),
|
| 553 |
+
('multiprocessing.dummy',
|
| 554 |
+
'C:\\Python313\\Lib\\multiprocessing\\dummy\\__init__.py',
|
| 555 |
+
'PYMODULE-2'),
|
| 556 |
+
('multiprocessing.dummy.connection',
|
| 557 |
+
'C:\\Python313\\Lib\\multiprocessing\\dummy\\connection.py',
|
| 558 |
+
'PYMODULE-2'),
|
| 559 |
+
('multiprocessing.forkserver',
|
| 560 |
+
'C:\\Python313\\Lib\\multiprocessing\\forkserver.py',
|
| 561 |
+
'PYMODULE-2'),
|
| 562 |
+
('multiprocessing.heap',
|
| 563 |
+
'C:\\Python313\\Lib\\multiprocessing\\heap.py',
|
| 564 |
+
'PYMODULE-2'),
|
| 565 |
+
('multiprocessing.managers',
|
| 566 |
+
'C:\\Python313\\Lib\\multiprocessing\\managers.py',
|
| 567 |
+
'PYMODULE-2'),
|
| 568 |
+
('multiprocessing.pool',
|
| 569 |
+
'C:\\Python313\\Lib\\multiprocessing\\pool.py',
|
| 570 |
+
'PYMODULE-2'),
|
| 571 |
+
('multiprocessing.popen_fork',
|
| 572 |
+
'C:\\Python313\\Lib\\multiprocessing\\popen_fork.py',
|
| 573 |
+
'PYMODULE-2'),
|
| 574 |
+
('multiprocessing.popen_forkserver',
|
| 575 |
+
'C:\\Python313\\Lib\\multiprocessing\\popen_forkserver.py',
|
| 576 |
+
'PYMODULE-2'),
|
| 577 |
+
('multiprocessing.popen_spawn_posix',
|
| 578 |
+
'C:\\Python313\\Lib\\multiprocessing\\popen_spawn_posix.py',
|
| 579 |
+
'PYMODULE-2'),
|
| 580 |
+
('multiprocessing.popen_spawn_win32',
|
| 581 |
+
'C:\\Python313\\Lib\\multiprocessing\\popen_spawn_win32.py',
|
| 582 |
+
'PYMODULE-2'),
|
| 583 |
+
('multiprocessing.process',
|
| 584 |
+
'C:\\Python313\\Lib\\multiprocessing\\process.py',
|
| 585 |
+
'PYMODULE-2'),
|
| 586 |
+
('multiprocessing.queues',
|
| 587 |
+
'C:\\Python313\\Lib\\multiprocessing\\queues.py',
|
| 588 |
+
'PYMODULE-2'),
|
| 589 |
+
('multiprocessing.reduction',
|
| 590 |
+
'C:\\Python313\\Lib\\multiprocessing\\reduction.py',
|
| 591 |
+
'PYMODULE-2'),
|
| 592 |
+
('multiprocessing.resource_sharer',
|
| 593 |
+
'C:\\Python313\\Lib\\multiprocessing\\resource_sharer.py',
|
| 594 |
+
'PYMODULE-2'),
|
| 595 |
+
('multiprocessing.resource_tracker',
|
| 596 |
+
'C:\\Python313\\Lib\\multiprocessing\\resource_tracker.py',
|
| 597 |
+
'PYMODULE-2'),
|
| 598 |
+
('multiprocessing.shared_memory',
|
| 599 |
+
'C:\\Python313\\Lib\\multiprocessing\\shared_memory.py',
|
| 600 |
+
'PYMODULE-2'),
|
| 601 |
+
('multiprocessing.sharedctypes',
|
| 602 |
+
'C:\\Python313\\Lib\\multiprocessing\\sharedctypes.py',
|
| 603 |
+
'PYMODULE-2'),
|
| 604 |
+
('multiprocessing.spawn',
|
| 605 |
+
'C:\\Python313\\Lib\\multiprocessing\\spawn.py',
|
| 606 |
+
'PYMODULE-2'),
|
| 607 |
+
('multiprocessing.synchronize',
|
| 608 |
+
'C:\\Python313\\Lib\\multiprocessing\\synchronize.py',
|
| 609 |
+
'PYMODULE-2'),
|
| 610 |
+
('multiprocessing.util',
|
| 611 |
+
'C:\\Python313\\Lib\\multiprocessing\\util.py',
|
| 612 |
+
'PYMODULE-2'),
|
| 613 |
+
('netrc', 'C:\\Python313\\Lib\\netrc.py', 'PYMODULE-2'),
|
| 614 |
+
('nturl2path', 'C:\\Python313\\Lib\\nturl2path.py', 'PYMODULE-2'),
|
| 615 |
+
('numbers', 'C:\\Python313\\Lib\\numbers.py', 'PYMODULE-2'),
|
| 616 |
+
('numpy',
|
| 617 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\__init__.py',
|
| 618 |
+
'PYMODULE-2'),
|
| 619 |
+
('numpy.__config__',
|
| 620 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\__config__.py',
|
| 621 |
+
'PYMODULE-2'),
|
| 622 |
+
('numpy._array_api_info',
|
| 623 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_array_api_info.py',
|
| 624 |
+
'PYMODULE-2'),
|
| 625 |
+
('numpy._core',
|
| 626 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\__init__.py',
|
| 627 |
+
'PYMODULE-2'),
|
| 628 |
+
('numpy._core._add_newdocs',
|
| 629 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_add_newdocs.py',
|
| 630 |
+
'PYMODULE-2'),
|
| 631 |
+
('numpy._core._add_newdocs_scalars',
|
| 632 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_add_newdocs_scalars.py',
|
| 633 |
+
'PYMODULE-2'),
|
| 634 |
+
('numpy._core._asarray',
|
| 635 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_asarray.py',
|
| 636 |
+
'PYMODULE-2'),
|
| 637 |
+
('numpy._core._dtype',
|
| 638 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_dtype.py',
|
| 639 |
+
'PYMODULE-2'),
|
| 640 |
+
('numpy._core._dtype_ctypes',
|
| 641 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_dtype_ctypes.py',
|
| 642 |
+
'PYMODULE-2'),
|
| 643 |
+
('numpy._core._exceptions',
|
| 644 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_exceptions.py',
|
| 645 |
+
'PYMODULE-2'),
|
| 646 |
+
('numpy._core._internal',
|
| 647 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_internal.py',
|
| 648 |
+
'PYMODULE-2'),
|
| 649 |
+
('numpy._core._machar',
|
| 650 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_machar.py',
|
| 651 |
+
'PYMODULE-2'),
|
| 652 |
+
('numpy._core._methods',
|
| 653 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_methods.py',
|
| 654 |
+
'PYMODULE-2'),
|
| 655 |
+
('numpy._core._string_helpers',
|
| 656 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_string_helpers.py',
|
| 657 |
+
'PYMODULE-2'),
|
| 658 |
+
('numpy._core._type_aliases',
|
| 659 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_type_aliases.py',
|
| 660 |
+
'PYMODULE-2'),
|
| 661 |
+
('numpy._core._ufunc_config',
|
| 662 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\_ufunc_config.py',
|
| 663 |
+
'PYMODULE-2'),
|
| 664 |
+
('numpy._core.arrayprint',
|
| 665 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\arrayprint.py',
|
| 666 |
+
'PYMODULE-2'),
|
| 667 |
+
('numpy._core.defchararray',
|
| 668 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\defchararray.py',
|
| 669 |
+
'PYMODULE-2'),
|
| 670 |
+
('numpy._core.einsumfunc',
|
| 671 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\einsumfunc.py',
|
| 672 |
+
'PYMODULE-2'),
|
| 673 |
+
('numpy._core.fromnumeric',
|
| 674 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\fromnumeric.py',
|
| 675 |
+
'PYMODULE-2'),
|
| 676 |
+
('numpy._core.function_base',
|
| 677 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\function_base.py',
|
| 678 |
+
'PYMODULE-2'),
|
| 679 |
+
('numpy._core.getlimits',
|
| 680 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\getlimits.py',
|
| 681 |
+
'PYMODULE-2'),
|
| 682 |
+
('numpy._core.memmap',
|
| 683 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\memmap.py',
|
| 684 |
+
'PYMODULE-2'),
|
| 685 |
+
('numpy._core.multiarray',
|
| 686 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\multiarray.py',
|
| 687 |
+
'PYMODULE-2'),
|
| 688 |
+
('numpy._core.numeric',
|
| 689 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\numeric.py',
|
| 690 |
+
'PYMODULE-2'),
|
| 691 |
+
('numpy._core.numerictypes',
|
| 692 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\numerictypes.py',
|
| 693 |
+
'PYMODULE-2'),
|
| 694 |
+
('numpy._core.overrides',
|
| 695 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\overrides.py',
|
| 696 |
+
'PYMODULE-2'),
|
| 697 |
+
('numpy._core.printoptions',
|
| 698 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\printoptions.py',
|
| 699 |
+
'PYMODULE-2'),
|
| 700 |
+
('numpy._core.records',
|
| 701 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\records.py',
|
| 702 |
+
'PYMODULE-2'),
|
| 703 |
+
('numpy._core.shape_base',
|
| 704 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\shape_base.py',
|
| 705 |
+
'PYMODULE-2'),
|
| 706 |
+
('numpy._core.strings',
|
| 707 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\strings.py',
|
| 708 |
+
'PYMODULE-2'),
|
| 709 |
+
('numpy._core.tests', '-', 'PYMODULE-2'),
|
| 710 |
+
('numpy._core.tests._natype',
|
| 711 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\tests\\_natype.py',
|
| 712 |
+
'PYMODULE-2'),
|
| 713 |
+
('numpy._core.umath',
|
| 714 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_core\\umath.py',
|
| 715 |
+
'PYMODULE-2'),
|
| 716 |
+
('numpy._distributor_init',
|
| 717 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_distributor_init.py',
|
| 718 |
+
'PYMODULE-2'),
|
| 719 |
+
('numpy._expired_attrs_2_0',
|
| 720 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_expired_attrs_2_0.py',
|
| 721 |
+
'PYMODULE-2'),
|
| 722 |
+
('numpy._globals',
|
| 723 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_globals.py',
|
| 724 |
+
'PYMODULE-2'),
|
| 725 |
+
('numpy._pytesttester',
|
| 726 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_pytesttester.py',
|
| 727 |
+
'PYMODULE-2'),
|
| 728 |
+
('numpy._typing',
|
| 729 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\__init__.py',
|
| 730 |
+
'PYMODULE-2'),
|
| 731 |
+
('numpy._typing._add_docstring',
|
| 732 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_add_docstring.py',
|
| 733 |
+
'PYMODULE-2'),
|
| 734 |
+
('numpy._typing._array_like',
|
| 735 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_array_like.py',
|
| 736 |
+
'PYMODULE-2'),
|
| 737 |
+
('numpy._typing._char_codes',
|
| 738 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_char_codes.py',
|
| 739 |
+
'PYMODULE-2'),
|
| 740 |
+
('numpy._typing._dtype_like',
|
| 741 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_dtype_like.py',
|
| 742 |
+
'PYMODULE-2'),
|
| 743 |
+
('numpy._typing._nbit',
|
| 744 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_nbit.py',
|
| 745 |
+
'PYMODULE-2'),
|
| 746 |
+
('numpy._typing._nbit_base',
|
| 747 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_nbit_base.py',
|
| 748 |
+
'PYMODULE-2'),
|
| 749 |
+
('numpy._typing._nested_sequence',
|
| 750 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_nested_sequence.py',
|
| 751 |
+
'PYMODULE-2'),
|
| 752 |
+
('numpy._typing._scalars',
|
| 753 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_scalars.py',
|
| 754 |
+
'PYMODULE-2'),
|
| 755 |
+
('numpy._typing._shape',
|
| 756 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_shape.py',
|
| 757 |
+
'PYMODULE-2'),
|
| 758 |
+
('numpy._typing._ufunc',
|
| 759 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_typing\\_ufunc.py',
|
| 760 |
+
'PYMODULE-2'),
|
| 761 |
+
('numpy._utils',
|
| 762 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_utils\\__init__.py',
|
| 763 |
+
'PYMODULE-2'),
|
| 764 |
+
('numpy._utils._convertions',
|
| 765 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_utils\\_convertions.py',
|
| 766 |
+
'PYMODULE-2'),
|
| 767 |
+
('numpy._utils._inspect',
|
| 768 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\_utils\\_inspect.py',
|
| 769 |
+
'PYMODULE-2'),
|
| 770 |
+
('numpy.char',
|
| 771 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\char\\__init__.py',
|
| 772 |
+
'PYMODULE-2'),
|
| 773 |
+
('numpy.core',
|
| 774 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\core\\__init__.py',
|
| 775 |
+
'PYMODULE-2'),
|
| 776 |
+
('numpy.core._utils',
|
| 777 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\core\\_utils.py',
|
| 778 |
+
'PYMODULE-2'),
|
| 779 |
+
('numpy.ctypeslib',
|
| 780 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\ctypeslib.py',
|
| 781 |
+
'PYMODULE-2'),
|
| 782 |
+
('numpy.dtypes',
|
| 783 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\dtypes.py',
|
| 784 |
+
'PYMODULE-2'),
|
| 785 |
+
('numpy.exceptions',
|
| 786 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\exceptions.py',
|
| 787 |
+
'PYMODULE-2'),
|
| 788 |
+
('numpy.f2py',
|
| 789 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\__init__.py',
|
| 790 |
+
'PYMODULE-2'),
|
| 791 |
+
('numpy.f2py.__version__',
|
| 792 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\__version__.py',
|
| 793 |
+
'PYMODULE-2'),
|
| 794 |
+
('numpy.f2py._backends',
|
| 795 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\_backends\\__init__.py',
|
| 796 |
+
'PYMODULE-2'),
|
| 797 |
+
('numpy.f2py._backends._backend',
|
| 798 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\_backends\\_backend.py',
|
| 799 |
+
'PYMODULE-2'),
|
| 800 |
+
('numpy.f2py._backends._distutils',
|
| 801 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\_backends\\_distutils.py',
|
| 802 |
+
'PYMODULE-2'),
|
| 803 |
+
('numpy.f2py._backends._meson',
|
| 804 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\_backends\\_meson.py',
|
| 805 |
+
'PYMODULE-2'),
|
| 806 |
+
('numpy.f2py._isocbind',
|
| 807 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\_isocbind.py',
|
| 808 |
+
'PYMODULE-2'),
|
| 809 |
+
('numpy.f2py.auxfuncs',
|
| 810 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\auxfuncs.py',
|
| 811 |
+
'PYMODULE-2'),
|
| 812 |
+
('numpy.f2py.capi_maps',
|
| 813 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\capi_maps.py',
|
| 814 |
+
'PYMODULE-2'),
|
| 815 |
+
('numpy.f2py.cb_rules',
|
| 816 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\cb_rules.py',
|
| 817 |
+
'PYMODULE-2'),
|
| 818 |
+
('numpy.f2py.cfuncs',
|
| 819 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\cfuncs.py',
|
| 820 |
+
'PYMODULE-2'),
|
| 821 |
+
('numpy.f2py.common_rules',
|
| 822 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\common_rules.py',
|
| 823 |
+
'PYMODULE-2'),
|
| 824 |
+
('numpy.f2py.crackfortran',
|
| 825 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\crackfortran.py',
|
| 826 |
+
'PYMODULE-2'),
|
| 827 |
+
('numpy.f2py.diagnose',
|
| 828 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\diagnose.py',
|
| 829 |
+
'PYMODULE-2'),
|
| 830 |
+
('numpy.f2py.f2py2e',
|
| 831 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\f2py2e.py',
|
| 832 |
+
'PYMODULE-2'),
|
| 833 |
+
('numpy.f2py.f90mod_rules',
|
| 834 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\f90mod_rules.py',
|
| 835 |
+
'PYMODULE-2'),
|
| 836 |
+
('numpy.f2py.func2subr',
|
| 837 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\func2subr.py',
|
| 838 |
+
'PYMODULE-2'),
|
| 839 |
+
('numpy.f2py.rules',
|
| 840 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\rules.py',
|
| 841 |
+
'PYMODULE-2'),
|
| 842 |
+
('numpy.f2py.symbolic',
|
| 843 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\symbolic.py',
|
| 844 |
+
'PYMODULE-2'),
|
| 845 |
+
('numpy.f2py.use_rules',
|
| 846 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\f2py\\use_rules.py',
|
| 847 |
+
'PYMODULE-2'),
|
| 848 |
+
('numpy.fft',
|
| 849 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\fft\\__init__.py',
|
| 850 |
+
'PYMODULE-2'),
|
| 851 |
+
('numpy.fft._helper',
|
| 852 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\fft\\_helper.py',
|
| 853 |
+
'PYMODULE-2'),
|
| 854 |
+
('numpy.fft._pocketfft',
|
| 855 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\fft\\_pocketfft.py',
|
| 856 |
+
'PYMODULE-2'),
|
| 857 |
+
('numpy.fft.helper',
|
| 858 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\fft\\helper.py',
|
| 859 |
+
'PYMODULE-2'),
|
| 860 |
+
('numpy.lib',
|
| 861 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\__init__.py',
|
| 862 |
+
'PYMODULE-2'),
|
| 863 |
+
('numpy.lib._array_utils_impl',
|
| 864 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_array_utils_impl.py',
|
| 865 |
+
'PYMODULE-2'),
|
| 866 |
+
('numpy.lib._arraypad_impl',
|
| 867 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_arraypad_impl.py',
|
| 868 |
+
'PYMODULE-2'),
|
| 869 |
+
('numpy.lib._arraysetops_impl',
|
| 870 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_arraysetops_impl.py',
|
| 871 |
+
'PYMODULE-2'),
|
| 872 |
+
('numpy.lib._arrayterator_impl',
|
| 873 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_arrayterator_impl.py',
|
| 874 |
+
'PYMODULE-2'),
|
| 875 |
+
('numpy.lib._datasource',
|
| 876 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_datasource.py',
|
| 877 |
+
'PYMODULE-2'),
|
| 878 |
+
('numpy.lib._function_base_impl',
|
| 879 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_function_base_impl.py',
|
| 880 |
+
'PYMODULE-2'),
|
| 881 |
+
('numpy.lib._histograms_impl',
|
| 882 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_histograms_impl.py',
|
| 883 |
+
'PYMODULE-2'),
|
| 884 |
+
('numpy.lib._index_tricks_impl',
|
| 885 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_index_tricks_impl.py',
|
| 886 |
+
'PYMODULE-2'),
|
| 887 |
+
('numpy.lib._iotools',
|
| 888 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_iotools.py',
|
| 889 |
+
'PYMODULE-2'),
|
| 890 |
+
('numpy.lib._nanfunctions_impl',
|
| 891 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_nanfunctions_impl.py',
|
| 892 |
+
'PYMODULE-2'),
|
| 893 |
+
('numpy.lib._npyio_impl',
|
| 894 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_npyio_impl.py',
|
| 895 |
+
'PYMODULE-2'),
|
| 896 |
+
('numpy.lib._polynomial_impl',
|
| 897 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_polynomial_impl.py',
|
| 898 |
+
'PYMODULE-2'),
|
| 899 |
+
('numpy.lib._scimath_impl',
|
| 900 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_scimath_impl.py',
|
| 901 |
+
'PYMODULE-2'),
|
| 902 |
+
('numpy.lib._shape_base_impl',
|
| 903 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_shape_base_impl.py',
|
| 904 |
+
'PYMODULE-2'),
|
| 905 |
+
('numpy.lib._stride_tricks_impl',
|
| 906 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_stride_tricks_impl.py',
|
| 907 |
+
'PYMODULE-2'),
|
| 908 |
+
('numpy.lib._twodim_base_impl',
|
| 909 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_twodim_base_impl.py',
|
| 910 |
+
'PYMODULE-2'),
|
| 911 |
+
('numpy.lib._type_check_impl',
|
| 912 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_type_check_impl.py',
|
| 913 |
+
'PYMODULE-2'),
|
| 914 |
+
('numpy.lib._ufunclike_impl',
|
| 915 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_ufunclike_impl.py',
|
| 916 |
+
'PYMODULE-2'),
|
| 917 |
+
('numpy.lib._utils_impl',
|
| 918 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_utils_impl.py',
|
| 919 |
+
'PYMODULE-2'),
|
| 920 |
+
('numpy.lib._version',
|
| 921 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\_version.py',
|
| 922 |
+
'PYMODULE-2'),
|
| 923 |
+
('numpy.lib.array_utils',
|
| 924 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\array_utils.py',
|
| 925 |
+
'PYMODULE-2'),
|
| 926 |
+
('numpy.lib.format',
|
| 927 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\format.py',
|
| 928 |
+
'PYMODULE-2'),
|
| 929 |
+
('numpy.lib.introspect',
|
| 930 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\introspect.py',
|
| 931 |
+
'PYMODULE-2'),
|
| 932 |
+
('numpy.lib.mixins',
|
| 933 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\mixins.py',
|
| 934 |
+
'PYMODULE-2'),
|
| 935 |
+
('numpy.lib.npyio',
|
| 936 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\npyio.py',
|
| 937 |
+
'PYMODULE-2'),
|
| 938 |
+
('numpy.lib.recfunctions',
|
| 939 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\recfunctions.py',
|
| 940 |
+
'PYMODULE-2'),
|
| 941 |
+
('numpy.lib.scimath',
|
| 942 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\scimath.py',
|
| 943 |
+
'PYMODULE-2'),
|
| 944 |
+
('numpy.lib.stride_tricks',
|
| 945 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\lib\\stride_tricks.py',
|
| 946 |
+
'PYMODULE-2'),
|
| 947 |
+
('numpy.linalg',
|
| 948 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\linalg\\__init__.py',
|
| 949 |
+
'PYMODULE-2'),
|
| 950 |
+
('numpy.linalg._linalg',
|
| 951 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\linalg\\_linalg.py',
|
| 952 |
+
'PYMODULE-2'),
|
| 953 |
+
('numpy.linalg.linalg',
|
| 954 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\linalg\\linalg.py',
|
| 955 |
+
'PYMODULE-2'),
|
| 956 |
+
('numpy.ma',
|
| 957 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\ma\\__init__.py',
|
| 958 |
+
'PYMODULE-2'),
|
| 959 |
+
('numpy.ma.core',
|
| 960 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\ma\\core.py',
|
| 961 |
+
'PYMODULE-2'),
|
| 962 |
+
('numpy.ma.extras',
|
| 963 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\ma\\extras.py',
|
| 964 |
+
'PYMODULE-2'),
|
| 965 |
+
('numpy.ma.mrecords',
|
| 966 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\ma\\mrecords.py',
|
| 967 |
+
'PYMODULE-2'),
|
| 968 |
+
('numpy.matlib',
|
| 969 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\matlib.py',
|
| 970 |
+
'PYMODULE-2'),
|
| 971 |
+
('numpy.matrixlib',
|
| 972 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\matrixlib\\__init__.py',
|
| 973 |
+
'PYMODULE-2'),
|
| 974 |
+
('numpy.matrixlib.defmatrix',
|
| 975 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\matrixlib\\defmatrix.py',
|
| 976 |
+
'PYMODULE-2'),
|
| 977 |
+
('numpy.polynomial',
|
| 978 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\__init__.py',
|
| 979 |
+
'PYMODULE-2'),
|
| 980 |
+
('numpy.polynomial._polybase',
|
| 981 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\_polybase.py',
|
| 982 |
+
'PYMODULE-2'),
|
| 983 |
+
('numpy.polynomial.chebyshev',
|
| 984 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\chebyshev.py',
|
| 985 |
+
'PYMODULE-2'),
|
| 986 |
+
('numpy.polynomial.hermite',
|
| 987 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\hermite.py',
|
| 988 |
+
'PYMODULE-2'),
|
| 989 |
+
('numpy.polynomial.hermite_e',
|
| 990 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\hermite_e.py',
|
| 991 |
+
'PYMODULE-2'),
|
| 992 |
+
('numpy.polynomial.laguerre',
|
| 993 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\laguerre.py',
|
| 994 |
+
'PYMODULE-2'),
|
| 995 |
+
('numpy.polynomial.legendre',
|
| 996 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\legendre.py',
|
| 997 |
+
'PYMODULE-2'),
|
| 998 |
+
('numpy.polynomial.polynomial',
|
| 999 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\polynomial.py',
|
| 1000 |
+
'PYMODULE-2'),
|
| 1001 |
+
('numpy.polynomial.polyutils',
|
| 1002 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\polynomial\\polyutils.py',
|
| 1003 |
+
'PYMODULE-2'),
|
| 1004 |
+
('numpy.random',
|
| 1005 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\random\\__init__.py',
|
| 1006 |
+
'PYMODULE-2'),
|
| 1007 |
+
('numpy.random._pickle',
|
| 1008 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\random\\_pickle.py',
|
| 1009 |
+
'PYMODULE-2'),
|
| 1010 |
+
('numpy.rec',
|
| 1011 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\rec\\__init__.py',
|
| 1012 |
+
'PYMODULE-2'),
|
| 1013 |
+
('numpy.strings',
|
| 1014 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\strings\\__init__.py',
|
| 1015 |
+
'PYMODULE-2'),
|
| 1016 |
+
('numpy.testing',
|
| 1017 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\testing\\__init__.py',
|
| 1018 |
+
'PYMODULE-2'),
|
| 1019 |
+
('numpy.testing._private',
|
| 1020 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\testing\\_private\\__init__.py',
|
| 1021 |
+
'PYMODULE-2'),
|
| 1022 |
+
('numpy.testing._private.extbuild',
|
| 1023 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\testing\\_private\\extbuild.py',
|
| 1024 |
+
'PYMODULE-2'),
|
| 1025 |
+
('numpy.testing._private.utils',
|
| 1026 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\testing\\_private\\utils.py',
|
| 1027 |
+
'PYMODULE-2'),
|
| 1028 |
+
('numpy.testing.overrides',
|
| 1029 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\testing\\overrides.py',
|
| 1030 |
+
'PYMODULE-2'),
|
| 1031 |
+
('numpy.typing',
|
| 1032 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\typing\\__init__.py',
|
| 1033 |
+
'PYMODULE-2'),
|
| 1034 |
+
('numpy.version',
|
| 1035 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\numpy\\version.py',
|
| 1036 |
+
'PYMODULE-2'),
|
| 1037 |
+
('opcode', 'C:\\Python313\\Lib\\opcode.py', 'PYMODULE-2'),
|
| 1038 |
+
('pathlib', 'C:\\Python313\\Lib\\pathlib\\__init__.py', 'PYMODULE-2'),
|
| 1039 |
+
('pathlib._abc', 'C:\\Python313\\Lib\\pathlib\\_abc.py', 'PYMODULE-2'),
|
| 1040 |
+
('pathlib._local', 'C:\\Python313\\Lib\\pathlib\\_local.py', 'PYMODULE-2'),
|
| 1041 |
+
('pdb', 'C:\\Python313\\Lib\\pdb.py', 'PYMODULE-2'),
|
| 1042 |
+
('pickle', 'C:\\Python313\\Lib\\pickle.py', 'PYMODULE-2'),
|
| 1043 |
+
('pkgutil', 'C:\\Python313\\Lib\\pkgutil.py', 'PYMODULE-2'),
|
| 1044 |
+
('platform', 'C:\\Python313\\Lib\\platform.py', 'PYMODULE-2'),
|
| 1045 |
+
('pprint', 'C:\\Python313\\Lib\\pprint.py', 'PYMODULE-2'),
|
| 1046 |
+
('psutil',
|
| 1047 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\psutil\\__init__.py',
|
| 1048 |
+
'PYMODULE-2'),
|
| 1049 |
+
('psutil._common',
|
| 1050 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\psutil\\_common.py',
|
| 1051 |
+
'PYMODULE-2'),
|
| 1052 |
+
('psutil._compat',
|
| 1053 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\psutil\\_compat.py',
|
| 1054 |
+
'PYMODULE-2'),
|
| 1055 |
+
('psutil._pswindows',
|
| 1056 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\psutil\\_pswindows.py',
|
| 1057 |
+
'PYMODULE-2'),
|
| 1058 |
+
('py_compile', 'C:\\Python313\\Lib\\py_compile.py', 'PYMODULE-2'),
|
| 1059 |
+
('pydoc', 'C:\\Python313\\Lib\\pydoc.py', 'PYMODULE-2'),
|
| 1060 |
+
('pydoc_data', 'C:\\Python313\\Lib\\pydoc_data\\__init__.py', 'PYMODULE-2'),
|
| 1061 |
+
('pydoc_data.topics',
|
| 1062 |
+
'C:\\Python313\\Lib\\pydoc_data\\topics.py',
|
| 1063 |
+
'PYMODULE-2'),
|
| 1064 |
+
('queue', 'C:\\Python313\\Lib\\queue.py', 'PYMODULE-2'),
|
| 1065 |
+
('quopri', 'C:\\Python313\\Lib\\quopri.py', 'PYMODULE-2'),
|
| 1066 |
+
('random', 'C:\\Python313\\Lib\\random.py', 'PYMODULE-2'),
|
| 1067 |
+
('rlcompleter', 'C:\\Python313\\Lib\\rlcompleter.py', 'PYMODULE-2'),
|
| 1068 |
+
('runpy', 'C:\\Python313\\Lib\\runpy.py', 'PYMODULE-2'),
|
| 1069 |
+
('secrets', 'C:\\Python313\\Lib\\secrets.py', 'PYMODULE-2'),
|
| 1070 |
+
('selectors', 'C:\\Python313\\Lib\\selectors.py', 'PYMODULE-2'),
|
| 1071 |
+
('shlex', 'C:\\Python313\\Lib\\shlex.py', 'PYMODULE-2'),
|
| 1072 |
+
('shutil', 'C:\\Python313\\Lib\\shutil.py', 'PYMODULE-2'),
|
| 1073 |
+
('signal', 'C:\\Python313\\Lib\\signal.py', 'PYMODULE-2'),
|
| 1074 |
+
('socket', 'C:\\Python313\\Lib\\socket.py', 'PYMODULE-2'),
|
| 1075 |
+
('socketserver', 'C:\\Python313\\Lib\\socketserver.py', 'PYMODULE-2'),
|
| 1076 |
+
('ssl', 'C:\\Python313\\Lib\\ssl.py', 'PYMODULE-2'),
|
| 1077 |
+
('statistics', 'C:\\Python313\\Lib\\statistics.py', 'PYMODULE-2'),
|
| 1078 |
+
('string', 'C:\\Python313\\Lib\\string.py', 'PYMODULE-2'),
|
| 1079 |
+
('stringprep', 'C:\\Python313\\Lib\\stringprep.py', 'PYMODULE-2'),
|
| 1080 |
+
('subprocess', 'C:\\Python313\\Lib\\subprocess.py', 'PYMODULE-2'),
|
| 1081 |
+
('sysconfig', 'C:\\Python313\\Lib\\sysconfig\\__init__.py', 'PYMODULE-2'),
|
| 1082 |
+
('tarfile', 'C:\\Python313\\Lib\\tarfile.py', 'PYMODULE-2'),
|
| 1083 |
+
('tempfile', 'C:\\Python313\\Lib\\tempfile.py', 'PYMODULE-2'),
|
| 1084 |
+
('textwrap', 'C:\\Python313\\Lib\\textwrap.py', 'PYMODULE-2'),
|
| 1085 |
+
('threading', 'C:\\Python313\\Lib\\threading.py', 'PYMODULE-2'),
|
| 1086 |
+
('threadpoolctl',
|
| 1087 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\threadpoolctl.py',
|
| 1088 |
+
'PYMODULE-2'),
|
| 1089 |
+
('tkinter', 'C:\\Python313\\Lib\\tkinter\\__init__.py', 'PYMODULE-2'),
|
| 1090 |
+
('tkinter.commondialog',
|
| 1091 |
+
'C:\\Python313\\Lib\\tkinter\\commondialog.py',
|
| 1092 |
+
'PYMODULE-2'),
|
| 1093 |
+
('tkinter.constants',
|
| 1094 |
+
'C:\\Python313\\Lib\\tkinter\\constants.py',
|
| 1095 |
+
'PYMODULE-2'),
|
| 1096 |
+
('tkinter.dialog', 'C:\\Python313\\Lib\\tkinter\\dialog.py', 'PYMODULE-2'),
|
| 1097 |
+
('tkinter.filedialog',
|
| 1098 |
+
'C:\\Python313\\Lib\\tkinter\\filedialog.py',
|
| 1099 |
+
'PYMODULE-2'),
|
| 1100 |
+
('tkinter.messagebox',
|
| 1101 |
+
'C:\\Python313\\Lib\\tkinter\\messagebox.py',
|
| 1102 |
+
'PYMODULE-2'),
|
| 1103 |
+
('tkinter.simpledialog',
|
| 1104 |
+
'C:\\Python313\\Lib\\tkinter\\simpledialog.py',
|
| 1105 |
+
'PYMODULE-2'),
|
| 1106 |
+
('tkinter.ttk', 'C:\\Python313\\Lib\\tkinter\\ttk.py', 'PYMODULE-2'),
|
| 1107 |
+
('token', 'C:\\Python313\\Lib\\token.py', 'PYMODULE-2'),
|
| 1108 |
+
('tokenize', 'C:\\Python313\\Lib\\tokenize.py', 'PYMODULE-2'),
|
| 1109 |
+
('tracemalloc', 'C:\\Python313\\Lib\\tracemalloc.py', 'PYMODULE-2'),
|
| 1110 |
+
('tty', 'C:\\Python313\\Lib\\tty.py', 'PYMODULE-2'),
|
| 1111 |
+
('typing', 'C:\\Python313\\Lib\\typing.py', 'PYMODULE-2'),
|
| 1112 |
+
('typing_extensions',
|
| 1113 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\typing_extensions.py',
|
| 1114 |
+
'PYMODULE-2'),
|
| 1115 |
+
('unittest', 'C:\\Python313\\Lib\\unittest\\__init__.py', 'PYMODULE-2'),
|
| 1116 |
+
('unittest._log', 'C:\\Python313\\Lib\\unittest\\_log.py', 'PYMODULE-2'),
|
| 1117 |
+
('unittest.async_case',
|
| 1118 |
+
'C:\\Python313\\Lib\\unittest\\async_case.py',
|
| 1119 |
+
'PYMODULE-2'),
|
| 1120 |
+
('unittest.case', 'C:\\Python313\\Lib\\unittest\\case.py', 'PYMODULE-2'),
|
| 1121 |
+
('unittest.loader', 'C:\\Python313\\Lib\\unittest\\loader.py', 'PYMODULE-2'),
|
| 1122 |
+
('unittest.main', 'C:\\Python313\\Lib\\unittest\\main.py', 'PYMODULE-2'),
|
| 1123 |
+
('unittest.result', 'C:\\Python313\\Lib\\unittest\\result.py', 'PYMODULE-2'),
|
| 1124 |
+
('unittest.runner', 'C:\\Python313\\Lib\\unittest\\runner.py', 'PYMODULE-2'),
|
| 1125 |
+
('unittest.signals',
|
| 1126 |
+
'C:\\Python313\\Lib\\unittest\\signals.py',
|
| 1127 |
+
'PYMODULE-2'),
|
| 1128 |
+
('unittest.suite', 'C:\\Python313\\Lib\\unittest\\suite.py', 'PYMODULE-2'),
|
| 1129 |
+
('unittest.util', 'C:\\Python313\\Lib\\unittest\\util.py', 'PYMODULE-2'),
|
| 1130 |
+
('urllib', 'C:\\Python313\\Lib\\urllib\\__init__.py', 'PYMODULE-2'),
|
| 1131 |
+
('urllib.error', 'C:\\Python313\\Lib\\urllib\\error.py', 'PYMODULE-2'),
|
| 1132 |
+
('urllib.parse', 'C:\\Python313\\Lib\\urllib\\parse.py', 'PYMODULE-2'),
|
| 1133 |
+
('urllib.request', 'C:\\Python313\\Lib\\urllib\\request.py', 'PYMODULE-2'),
|
| 1134 |
+
('urllib.response', 'C:\\Python313\\Lib\\urllib\\response.py', 'PYMODULE-2'),
|
| 1135 |
+
('webbrowser', 'C:\\Python313\\Lib\\webbrowser.py', 'PYMODULE-2'),
|
| 1136 |
+
('xml', 'C:\\Python313\\Lib\\xml\\__init__.py', 'PYMODULE-2'),
|
| 1137 |
+
('xml.dom', 'C:\\Python313\\Lib\\xml\\dom\\__init__.py', 'PYMODULE-2'),
|
| 1138 |
+
('xml.dom.NodeFilter',
|
| 1139 |
+
'C:\\Python313\\Lib\\xml\\dom\\NodeFilter.py',
|
| 1140 |
+
'PYMODULE-2'),
|
| 1141 |
+
('xml.dom.domreg', 'C:\\Python313\\Lib\\xml\\dom\\domreg.py', 'PYMODULE-2'),
|
| 1142 |
+
('xml.dom.expatbuilder',
|
| 1143 |
+
'C:\\Python313\\Lib\\xml\\dom\\expatbuilder.py',
|
| 1144 |
+
'PYMODULE-2'),
|
| 1145 |
+
('xml.dom.minicompat',
|
| 1146 |
+
'C:\\Python313\\Lib\\xml\\dom\\minicompat.py',
|
| 1147 |
+
'PYMODULE-2'),
|
| 1148 |
+
('xml.dom.minidom', 'C:\\Python313\\Lib\\xml\\dom\\minidom.py', 'PYMODULE-2'),
|
| 1149 |
+
('xml.dom.pulldom', 'C:\\Python313\\Lib\\xml\\dom\\pulldom.py', 'PYMODULE-2'),
|
| 1150 |
+
('xml.dom.xmlbuilder',
|
| 1151 |
+
'C:\\Python313\\Lib\\xml\\dom\\xmlbuilder.py',
|
| 1152 |
+
'PYMODULE-2'),
|
| 1153 |
+
('xml.etree', 'C:\\Python313\\Lib\\xml\\etree\\__init__.py', 'PYMODULE-2'),
|
| 1154 |
+
('xml.etree.ElementInclude',
|
| 1155 |
+
'C:\\Python313\\Lib\\xml\\etree\\ElementInclude.py',
|
| 1156 |
+
'PYMODULE-2'),
|
| 1157 |
+
('xml.etree.ElementPath',
|
| 1158 |
+
'C:\\Python313\\Lib\\xml\\etree\\ElementPath.py',
|
| 1159 |
+
'PYMODULE-2'),
|
| 1160 |
+
('xml.etree.ElementTree',
|
| 1161 |
+
'C:\\Python313\\Lib\\xml\\etree\\ElementTree.py',
|
| 1162 |
+
'PYMODULE-2'),
|
| 1163 |
+
('xml.etree.cElementTree',
|
| 1164 |
+
'C:\\Python313\\Lib\\xml\\etree\\cElementTree.py',
|
| 1165 |
+
'PYMODULE-2'),
|
| 1166 |
+
('xml.parsers',
|
| 1167 |
+
'C:\\Python313\\Lib\\xml\\parsers\\__init__.py',
|
| 1168 |
+
'PYMODULE-2'),
|
| 1169 |
+
('xml.parsers.expat',
|
| 1170 |
+
'C:\\Python313\\Lib\\xml\\parsers\\expat.py',
|
| 1171 |
+
'PYMODULE-2'),
|
| 1172 |
+
('xml.sax', 'C:\\Python313\\Lib\\xml\\sax\\__init__.py', 'PYMODULE-2'),
|
| 1173 |
+
('xml.sax._exceptions',
|
| 1174 |
+
'C:\\Python313\\Lib\\xml\\sax\\_exceptions.py',
|
| 1175 |
+
'PYMODULE-2'),
|
| 1176 |
+
('xml.sax.expatreader',
|
| 1177 |
+
'C:\\Python313\\Lib\\xml\\sax\\expatreader.py',
|
| 1178 |
+
'PYMODULE-2'),
|
| 1179 |
+
('xml.sax.handler', 'C:\\Python313\\Lib\\xml\\sax\\handler.py', 'PYMODULE-2'),
|
| 1180 |
+
('xml.sax.saxutils',
|
| 1181 |
+
'C:\\Python313\\Lib\\xml\\sax\\saxutils.py',
|
| 1182 |
+
'PYMODULE-2'),
|
| 1183 |
+
('xml.sax.xmlreader',
|
| 1184 |
+
'C:\\Python313\\Lib\\xml\\sax\\xmlreader.py',
|
| 1185 |
+
'PYMODULE-2'),
|
| 1186 |
+
('xmlrpc', 'C:\\Python313\\Lib\\xmlrpc\\__init__.py', 'PYMODULE-2'),
|
| 1187 |
+
('xmlrpc.client', 'C:\\Python313\\Lib\\xmlrpc\\client.py', 'PYMODULE-2'),
|
| 1188 |
+
('xmlrpc.server', 'C:\\Python313\\Lib\\xmlrpc\\server.py', 'PYMODULE-2'),
|
| 1189 |
+
('yaml',
|
| 1190 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\__init__.py',
|
| 1191 |
+
'PYMODULE-2'),
|
| 1192 |
+
('yaml.composer',
|
| 1193 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\composer.py',
|
| 1194 |
+
'PYMODULE-2'),
|
| 1195 |
+
('yaml.constructor',
|
| 1196 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\constructor.py',
|
| 1197 |
+
'PYMODULE-2'),
|
| 1198 |
+
('yaml.cyaml',
|
| 1199 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\cyaml.py',
|
| 1200 |
+
'PYMODULE-2'),
|
| 1201 |
+
('yaml.dumper',
|
| 1202 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\dumper.py',
|
| 1203 |
+
'PYMODULE-2'),
|
| 1204 |
+
('yaml.emitter',
|
| 1205 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\emitter.py',
|
| 1206 |
+
'PYMODULE-2'),
|
| 1207 |
+
('yaml.error',
|
| 1208 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\error.py',
|
| 1209 |
+
'PYMODULE-2'),
|
| 1210 |
+
('yaml.events',
|
| 1211 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\events.py',
|
| 1212 |
+
'PYMODULE-2'),
|
| 1213 |
+
('yaml.loader',
|
| 1214 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\loader.py',
|
| 1215 |
+
'PYMODULE-2'),
|
| 1216 |
+
('yaml.nodes',
|
| 1217 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\nodes.py',
|
| 1218 |
+
'PYMODULE-2'),
|
| 1219 |
+
('yaml.parser',
|
| 1220 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\parser.py',
|
| 1221 |
+
'PYMODULE-2'),
|
| 1222 |
+
('yaml.reader',
|
| 1223 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\reader.py',
|
| 1224 |
+
'PYMODULE-2'),
|
| 1225 |
+
('yaml.representer',
|
| 1226 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\representer.py',
|
| 1227 |
+
'PYMODULE-2'),
|
| 1228 |
+
('yaml.resolver',
|
| 1229 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\resolver.py',
|
| 1230 |
+
'PYMODULE-2'),
|
| 1231 |
+
('yaml.scanner',
|
| 1232 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\scanner.py',
|
| 1233 |
+
'PYMODULE-2'),
|
| 1234 |
+
('yaml.serializer',
|
| 1235 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\serializer.py',
|
| 1236 |
+
'PYMODULE-2'),
|
| 1237 |
+
('yaml.tokens',
|
| 1238 |
+
'C:\\Users\\rampy\\AppData\\Roaming\\Python\\Python313\\site-packages\\yaml\\tokens.py',
|
| 1239 |
+
'PYMODULE-2'),
|
| 1240 |
+
('zipfile', 'C:\\Python313\\Lib\\zipfile\\__init__.py', 'PYMODULE-2'),
|
| 1241 |
+
('zipfile._path',
|
| 1242 |
+
'C:\\Python313\\Lib\\zipfile\\_path\\__init__.py',
|
| 1243 |
+
'PYMODULE-2'),
|
| 1244 |
+
('zipfile._path.glob',
|
| 1245 |
+
'C:\\Python313\\Lib\\zipfile\\_path\\glob.py',
|
| 1246 |
+
'PYMODULE-2'),
|
| 1247 |
+
('zipimport', 'C:\\Python313\\Lib\\zipimport.py', 'PYMODULE-2')])
|
build/MAC-Installer/base_library.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bf33efbca47dfe975d6c982298111cbb5ad6dbb3bc886bafd83605c022992458
|
| 3 |
+
size 1278257
|
build/MAC-Installer/warn-MAC-Installer.txt
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
This file lists modules PyInstaller was not able to find. This does not
|
| 3 |
+
necessarily mean these modules are required for running your program. Both
|
| 4 |
+
Python's standard library and 3rd-party Python packages often conditionally
|
| 5 |
+
import optional modules, some of which may be available only on certain
|
| 6 |
+
platforms.
|
| 7 |
+
|
| 8 |
+
Types of import:
|
| 9 |
+
* top-level: imported at the top-level - look at these first
|
| 10 |
+
* conditional: imported within an if-statement
|
| 11 |
+
* delayed: imported within a function
|
| 12 |
+
* optional: imported within a try-except-statement
|
| 13 |
+
|
| 14 |
+
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
| 15 |
+
tracking down the missing module yourself. Thanks!
|
| 16 |
+
|
| 17 |
+
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
|
| 18 |
+
missing module named fcntl - imported by subprocess (optional), psutil._compat (delayed, optional), xmlrpc.server (optional)
|
| 19 |
+
missing module named termios - imported by tty (top-level), _pyrepl.pager (delayed, optional), getpass (optional), psutil._compat (delayed, optional)
|
| 20 |
+
missing module named 'collections.abc' - imported by traceback (top-level), typing (top-level), inspect (top-level), logging (top-level), importlib.resources.readers (top-level), selectors (top-level), tracemalloc (top-level), PIL.Image (top-level), PIL._typing (top-level), numpy.lib._npyio_impl (top-level), http.client (top-level), numpy.lib._function_base_impl (top-level), numpy._typing._nested_sequence (conditional), numpy._typing._shape (top-level), numpy._typing._dtype_like (top-level), numpy._typing._array_like (top-level), asyncio.base_events (top-level), asyncio.coroutines (top-level), yaml.constructor (top-level), numpy.random.bit_generator (top-level), typing_extensions (top-level), numpy.random.mtrand (top-level), numpy.random._generator (top-level), xml.etree.ElementTree (top-level), PIL.TiffImagePlugin (top-level), PIL.ImageOps (top-level), PIL.ImagePalette (top-level), PIL.ImageFilter (top-level), PIL.PngImagePlugin (top-level), PIL.Jpeg2KImagePlugin (top-level), PIL.IptcImagePlugin (top-level)
|
| 21 |
+
missing module named vms_lib - imported by platform (delayed, optional)
|
| 22 |
+
missing module named 'java.lang' - imported by platform (delayed, optional)
|
| 23 |
+
missing module named java - imported by platform (delayed)
|
| 24 |
+
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
| 25 |
+
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
| 26 |
+
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
|
| 27 |
+
missing module named resource - imported by posix (top-level)
|
| 28 |
+
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional), http.server (delayed, optional), netrc (delayed, optional), getpass (delayed, optional), psutil (optional)
|
| 29 |
+
missing module named _scproxy - imported by urllib.request (conditional)
|
| 30 |
+
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
| 31 |
+
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
|
| 32 |
+
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
|
| 33 |
+
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional)
|
| 34 |
+
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
|
| 35 |
+
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
|
| 36 |
+
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
| 37 |
+
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
|
| 38 |
+
missing module named pyimod02_importers - imported by C:\Users\rampy\AppData\Roaming\Python\Python313\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
| 39 |
+
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
|
| 40 |
+
missing module named annotationlib - imported by typing_extensions (conditional)
|
| 41 |
+
missing module named _dummy_thread - imported by numpy._core.arrayprint (optional)
|
| 42 |
+
missing module named 'numpy_distutils.cpuinfo' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
| 43 |
+
missing module named 'numpy_distutils.fcompiler' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
| 44 |
+
missing module named 'numpy_distutils.command' - imported by numpy.f2py.diagnose (delayed, conditional, optional)
|
| 45 |
+
missing module named numpy_distutils - imported by numpy.f2py.diagnose (delayed, optional)
|
| 46 |
+
missing module named dummy_threading - imported by psutil._compat (optional)
|
| 47 |
+
missing module named readline - imported by cmd (delayed, conditional, optional), code (delayed, conditional, optional), rlcompleter (optional), pdb (delayed, optional)
|
| 48 |
+
missing module named _typeshed - imported by numpy.random.bit_generator (top-level)
|
| 49 |
+
missing module named numpy.random.RandomState - imported by numpy.random (top-level), numpy.random._generator (top-level)
|
| 50 |
+
missing module named pyodide_js - imported by threadpoolctl (delayed, optional)
|
| 51 |
+
missing module named numpy._core.zeros - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 52 |
+
missing module named numpy._core.vstack - imported by numpy._core (top-level), numpy.lib._shape_base_impl (top-level), numpy (conditional)
|
| 53 |
+
missing module named numpy._core.void - imported by numpy._core (conditional), numpy (conditional)
|
| 54 |
+
missing module named numpy._core.vecmat - imported by numpy._core (conditional), numpy (conditional)
|
| 55 |
+
missing module named numpy._core.vecdot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 56 |
+
missing module named numpy._core.ushort - imported by numpy._core (conditional), numpy (conditional)
|
| 57 |
+
missing module named numpy._core.unsignedinteger - imported by numpy._core (conditional), numpy (conditional)
|
| 58 |
+
missing module named numpy._core.ulonglong - imported by numpy._core (conditional), numpy (conditional)
|
| 59 |
+
missing module named numpy._core.ulong - imported by numpy._core (conditional), numpy (conditional)
|
| 60 |
+
missing module named numpy._core.uintp - imported by numpy._core (conditional), numpy (conditional)
|
| 61 |
+
missing module named numpy._core.uintc - imported by numpy._core (conditional), numpy (conditional)
|
| 62 |
+
missing module named numpy._core.uint64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 63 |
+
missing module named numpy._core.uint32 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 64 |
+
missing module named numpy._core.uint16 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 65 |
+
missing module named numpy._core.uint - imported by numpy._core (conditional), numpy (conditional)
|
| 66 |
+
missing module named numpy._core.ubyte - imported by numpy._core (conditional), numpy (conditional)
|
| 67 |
+
missing module named numpy._core.trunc - imported by numpy._core (conditional), numpy (conditional)
|
| 68 |
+
missing module named numpy._core.true_divide - imported by numpy._core (conditional), numpy (conditional)
|
| 69 |
+
missing module named numpy._core.transpose - imported by numpy._core (top-level), numpy.lib._function_base_impl (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 70 |
+
missing module named numpy._core.trace - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 71 |
+
missing module named numpy._core.timedelta64 - imported by numpy._core (conditional), numpy (conditional)
|
| 72 |
+
missing module named numpy._core.tensordot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 73 |
+
missing module named numpy._core.tanh - imported by numpy._core (conditional), numpy (conditional)
|
| 74 |
+
missing module named numpy._core.tan - imported by numpy._core (conditional), numpy (conditional)
|
| 75 |
+
missing module named numpy._core.swapaxes - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 76 |
+
missing module named numpy._core.sum - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 77 |
+
missing module named numpy._core.subtract - imported by numpy._core (conditional), numpy (conditional)
|
| 78 |
+
missing module named numpy._core.str_ - imported by numpy._core (conditional), numpy (conditional)
|
| 79 |
+
missing module named numpy._core.square - imported by numpy._core (conditional), numpy (conditional)
|
| 80 |
+
missing module named numpy._core.sqrt - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
| 81 |
+
missing module named numpy._core.spacing - imported by numpy._core (conditional), numpy (conditional)
|
| 82 |
+
missing module named numpy._core.sort - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 83 |
+
missing module named numpy._core.sinh - imported by numpy._core (conditional), numpy (conditional)
|
| 84 |
+
missing module named numpy._core.single - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 85 |
+
missing module named numpy._core.signedinteger - imported by numpy._core (conditional), numpy (conditional)
|
| 86 |
+
missing module named numpy._core.signbit - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 87 |
+
missing module named numpy._core.sign - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 88 |
+
missing module named numpy._core.short - imported by numpy._core (conditional), numpy (conditional)
|
| 89 |
+
missing module named numpy._core.rint - imported by numpy._core (conditional), numpy (conditional)
|
| 90 |
+
missing module named numpy._core.right_shift - imported by numpy._core (conditional), numpy (conditional)
|
| 91 |
+
missing module named numpy._core.result_type - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional), numpy.fft._pocketfft (top-level)
|
| 92 |
+
missing module named numpy._core.remainder - imported by numpy._core (conditional), numpy (conditional)
|
| 93 |
+
missing module named numpy._core.reciprocal - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
| 94 |
+
missing module named numpy._core.radians - imported by numpy._core (conditional), numpy (conditional)
|
| 95 |
+
missing module named numpy._core.rad2deg - imported by numpy._core (conditional), numpy (conditional)
|
| 96 |
+
missing module named numpy._core.prod - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 97 |
+
missing module named numpy._core.power - imported by numpy._core (conditional), numpy (conditional)
|
| 98 |
+
missing module named numpy._core.positive - imported by numpy._core (conditional), numpy (conditional)
|
| 99 |
+
missing module named numpy._core.pi - imported by numpy._core (conditional), numpy (conditional)
|
| 100 |
+
missing module named numpy._core.outer - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 101 |
+
missing module named numpy._core.ones - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 102 |
+
missing module named numpy._core.object_ - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 103 |
+
missing module named numpy._core.number - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 104 |
+
missing module named numpy._core.not_equal - imported by numpy._core (conditional), numpy (conditional)
|
| 105 |
+
missing module named numpy._core.newaxis - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 106 |
+
missing module named numpy._core.negative - imported by numpy._core (conditional), numpy (conditional)
|
| 107 |
+
missing module named numpy._core.ndarray - imported by numpy._core (top-level), numpy.lib._utils_impl (top-level), numpy.testing._private.utils (top-level), numpy (conditional)
|
| 108 |
+
missing module named numpy._core.multiply - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 109 |
+
missing module named numpy._core.moveaxis - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 110 |
+
missing module named numpy._core.modf - imported by numpy._core (conditional), numpy (conditional)
|
| 111 |
+
missing module named numpy._core.mod - imported by numpy._core (conditional), numpy (conditional)
|
| 112 |
+
missing module named numpy._core.minimum - imported by numpy._core (conditional), numpy (conditional)
|
| 113 |
+
missing module named numpy._core.maximum - imported by numpy._core (conditional), numpy (conditional)
|
| 114 |
+
missing module named numpy._core.max - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 115 |
+
missing module named numpy._core.matrix_transpose - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 116 |
+
missing module named numpy._core.matvec - imported by numpy._core (conditional), numpy (conditional)
|
| 117 |
+
missing module named numpy._core.matmul - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 118 |
+
missing module named numpy._core.longdouble - imported by numpy._core (conditional), numpy (conditional)
|
| 119 |
+
missing module named numpy._core.long - imported by numpy._core (conditional), numpy (conditional)
|
| 120 |
+
missing module named numpy._core.logical_xor - imported by numpy._core (conditional), numpy (conditional)
|
| 121 |
+
missing module named numpy._core.logical_or - imported by numpy._core (conditional), numpy (conditional)
|
| 122 |
+
missing module named numpy._core.logical_not - imported by numpy._core (conditional), numpy (conditional)
|
| 123 |
+
missing module named numpy._core.logical_and - imported by numpy._core (conditional), numpy (conditional)
|
| 124 |
+
missing module named numpy._core.logaddexp2 - imported by numpy._core (conditional), numpy (conditional)
|
| 125 |
+
missing module named numpy._core.logaddexp - imported by numpy._core (conditional), numpy (conditional)
|
| 126 |
+
missing module named numpy._core.log2 - imported by numpy._core (conditional), numpy (conditional)
|
| 127 |
+
missing module named numpy._core.log1p - imported by numpy._core (conditional), numpy (conditional)
|
| 128 |
+
missing module named numpy._core.log - imported by numpy._core (conditional), numpy (conditional)
|
| 129 |
+
missing module named numpy._core.linspace - imported by numpy._core (top-level), numpy.lib._index_tricks_impl (top-level), numpy (conditional)
|
| 130 |
+
missing module named numpy._core.less_equal - imported by numpy._core (conditional), numpy (conditional)
|
| 131 |
+
missing module named numpy._core.less - imported by numpy._core (conditional), numpy (conditional)
|
| 132 |
+
missing module named numpy._core.left_shift - imported by numpy._core (conditional), numpy (conditional)
|
| 133 |
+
missing module named numpy._core.ldexp - imported by numpy._core (conditional), numpy (conditional)
|
| 134 |
+
missing module named numpy._core.lcm - imported by numpy._core (conditional), numpy (conditional)
|
| 135 |
+
missing module named numpy._core.isscalar - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 136 |
+
missing module named numpy._core.isnat - imported by numpy._core (top-level), numpy.testing._private.utils (top-level), numpy (conditional)
|
| 137 |
+
missing module named numpy._core.isnan - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 138 |
+
missing module named numpy._core.isfinite - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 139 |
+
missing module named numpy._core.intp - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (top-level), numpy (conditional), numpy._array_api_info (top-level)
|
| 140 |
+
missing module named numpy._core.integer - imported by numpy._core (conditional), numpy (conditional), numpy.fft._helper (top-level)
|
| 141 |
+
missing module named numpy._core.intc - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 142 |
+
missing module named numpy._core.int8 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 143 |
+
missing module named numpy._core.int64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 144 |
+
missing module named numpy._core.int32 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 145 |
+
missing module named numpy._core.int16 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 146 |
+
missing module named numpy._core.inf - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 147 |
+
missing module named numpy._core.inexact - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 148 |
+
missing module named numpy._core.iinfo - imported by numpy._core (top-level), numpy.lib._twodim_base_impl (top-level), numpy (conditional)
|
| 149 |
+
missing module named numpy._core.hypot - imported by numpy._core (conditional), numpy (conditional)
|
| 150 |
+
missing module named numpy._core.hstack - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 151 |
+
missing module named numpy._core.heaviside - imported by numpy._core (conditional), numpy (conditional)
|
| 152 |
+
missing module named numpy._core.half - imported by numpy._core (conditional), numpy (conditional)
|
| 153 |
+
missing module named numpy._core.greater_equal - imported by numpy._core (conditional), numpy (conditional)
|
| 154 |
+
missing module named numpy._core.greater - imported by numpy._core (conditional), numpy (conditional)
|
| 155 |
+
missing module named numpy._core.gcd - imported by numpy._core (conditional), numpy (conditional)
|
| 156 |
+
missing module named numpy._core.frompyfunc - imported by numpy._core (conditional), numpy (conditional)
|
| 157 |
+
missing module named numpy._core.frexp - imported by numpy._core (conditional), numpy (conditional)
|
| 158 |
+
missing module named numpy._core.fmod - imported by numpy._core (conditional), numpy (conditional)
|
| 159 |
+
missing module named numpy._core.fmin - imported by numpy._core (conditional), numpy (conditional)
|
| 160 |
+
missing module named numpy._core.fmax - imported by numpy._core (conditional), numpy (conditional)
|
| 161 |
+
missing module named numpy._core.floor_divide - imported by numpy._core (conditional), numpy (conditional)
|
| 162 |
+
missing module named numpy._core.floor - imported by numpy._core (conditional), numpy (conditional)
|
| 163 |
+
missing module named numpy._core.floating - imported by numpy._core (conditional), numpy (conditional)
|
| 164 |
+
missing module named numpy._core.float_power - imported by numpy._core (conditional), numpy (conditional)
|
| 165 |
+
missing module named numpy._core.float32 - imported by numpy._core (top-level), numpy.testing._private.utils (top-level), numpy (conditional), numpy._array_api_info (top-level)
|
| 166 |
+
missing module named numpy._core.float16 - imported by numpy._core (conditional), numpy (conditional)
|
| 167 |
+
missing module named numpy._core.finfo - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 168 |
+
missing module named numpy._core.fabs - imported by numpy._core (conditional), numpy (conditional)
|
| 169 |
+
missing module named numpy._core.expm1 - imported by numpy._core (conditional), numpy (conditional)
|
| 170 |
+
missing module named numpy._core.exp - imported by numpy._core (conditional), numpy (conditional)
|
| 171 |
+
missing module named numpy._core.euler_gamma - imported by numpy._core (conditional), numpy (conditional)
|
| 172 |
+
missing module named numpy._core.errstate - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 173 |
+
missing module named numpy._core.equal - imported by numpy._core (conditional), numpy (conditional)
|
| 174 |
+
missing module named numpy._core.empty_like - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level)
|
| 175 |
+
missing module named numpy._core.empty - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (top-level), numpy (conditional), numpy.fft._helper (top-level)
|
| 176 |
+
missing module named numpy._core.e - imported by numpy._core (conditional), numpy (conditional)
|
| 177 |
+
missing module named numpy._core.double - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 178 |
+
missing module named numpy._core.dot - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 179 |
+
missing module named numpy._core.divmod - imported by numpy._core (conditional), numpy (conditional)
|
| 180 |
+
missing module named numpy._core.divide - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 181 |
+
missing module named numpy._core.diagonal - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 182 |
+
missing module named numpy._core.degrees - imported by numpy._core (conditional), numpy (conditional)
|
| 183 |
+
missing module named numpy._core.deg2rad - imported by numpy._core (conditional), numpy (conditional)
|
| 184 |
+
missing module named numpy._core.datetime64 - imported by numpy._core (conditional), numpy (conditional)
|
| 185 |
+
missing module named numpy._core.csingle - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 186 |
+
missing module named numpy._core.cross - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 187 |
+
missing module named numpy._core.count_nonzero - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 188 |
+
missing module named numpy._core.cosh - imported by numpy._core (conditional), numpy (conditional)
|
| 189 |
+
missing module named numpy._core.cos - imported by numpy._core (conditional), numpy (conditional)
|
| 190 |
+
missing module named numpy._core.copysign - imported by numpy._core (conditional), numpy (conditional)
|
| 191 |
+
missing module named numpy._core.conjugate - imported by numpy._core (conditional), numpy (conditional), numpy.fft._pocketfft (top-level)
|
| 192 |
+
missing module named numpy._core.conj - imported by numpy._core (conditional), numpy (conditional)
|
| 193 |
+
missing module named numpy._core.complexfloating - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 194 |
+
missing module named numpy._core.complex64 - imported by numpy._core (conditional), numpy (conditional), numpy._array_api_info (top-level)
|
| 195 |
+
missing module named numpy._core.clongdouble - imported by numpy._core (conditional), numpy (conditional)
|
| 196 |
+
missing module named numpy._core.character - imported by numpy._core (conditional), numpy (conditional)
|
| 197 |
+
missing module named numpy._core.ceil - imported by numpy._core (conditional), numpy (conditional)
|
| 198 |
+
missing module named numpy._core.cdouble - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 199 |
+
missing module named numpy._core.cbrt - imported by numpy._core (conditional), numpy (conditional)
|
| 200 |
+
missing module named numpy._core.bytes_ - imported by numpy._core (conditional), numpy (conditional)
|
| 201 |
+
missing module named numpy._core.byte - imported by numpy._core (conditional), numpy (conditional)
|
| 202 |
+
missing module named numpy._core.bool_ - imported by numpy._core (conditional), numpy (conditional)
|
| 203 |
+
missing module named numpy._core.bitwise_xor - imported by numpy._core (conditional), numpy (conditional)
|
| 204 |
+
missing module named numpy._core.bitwise_or - imported by numpy._core (conditional), numpy (conditional)
|
| 205 |
+
missing module named numpy._core.bitwise_count - imported by numpy._core (conditional), numpy (conditional)
|
| 206 |
+
missing module named numpy._core.bitwise_and - imported by numpy._core (conditional), numpy (conditional)
|
| 207 |
+
missing module named numpy._core.atleast_3d - imported by numpy._core (top-level), numpy.lib._shape_base_impl (top-level), numpy (conditional)
|
| 208 |
+
missing module named numpy._core.atleast_2d - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 209 |
+
missing module named numpy._core.atleast_1d - imported by numpy._core (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 210 |
+
missing module named numpy._core.asarray - imported by numpy._core (top-level), numpy.lib._array_utils_impl (top-level), numpy.linalg._linalg (top-level), numpy (conditional), numpy.fft._pocketfft (top-level), numpy.fft._helper (top-level)
|
| 211 |
+
missing module named numpy._core.asanyarray - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 212 |
+
missing module named numpy._core.array_repr - imported by numpy._core (top-level), numpy.testing._private.utils (top-level), numpy (conditional)
|
| 213 |
+
missing module named numpy._core.array2string - imported by numpy._core (delayed), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 214 |
+
missing module named numpy._core.array - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (top-level), numpy.lib._polynomial_impl (top-level), numpy (conditional)
|
| 215 |
+
missing module named numpy._core.argsort - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 216 |
+
missing module named numpy._core.arctanh - imported by numpy._core (conditional), numpy (conditional)
|
| 217 |
+
missing module named numpy._core.arctan2 - imported by numpy._core (conditional), numpy (conditional)
|
| 218 |
+
missing module named numpy._core.arctan - imported by numpy._core (conditional), numpy (conditional)
|
| 219 |
+
missing module named numpy._core.arcsinh - imported by numpy._core (conditional), numpy (conditional)
|
| 220 |
+
missing module named numpy._core.arcsin - imported by numpy._core (conditional), numpy (conditional)
|
| 221 |
+
missing module named numpy._core.arccosh - imported by numpy._core (conditional), numpy (conditional)
|
| 222 |
+
missing module named numpy._core.arccos - imported by numpy._core (conditional), numpy (conditional)
|
| 223 |
+
missing module named numpy._core.arange - imported by numpy._core (top-level), numpy.testing._private.utils (top-level), numpy (conditional), numpy.fft._helper (top-level)
|
| 224 |
+
missing module named numpy._core.amin - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 225 |
+
missing module named numpy._core.amax - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 226 |
+
missing module named numpy._core.all - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy.testing._private.utils (delayed), numpy (conditional)
|
| 227 |
+
missing module named numpy._core.add - imported by numpy._core (top-level), numpy.linalg._linalg (top-level), numpy (conditional)
|
| 228 |
+
missing module named numpy._distributor_init_local - imported by numpy (optional), numpy._distributor_init (optional)
|
| 229 |
+
missing module named olefile - imported by PIL.FpxImagePlugin (top-level), PIL.MicImagePlugin (top-level)
|
| 230 |
+
missing module named xmlrpclib - imported by defusedxml.xmlrpc (conditional)
|
build/MAC-Installer/xref-MAC-Installer.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/spec/MAC-Installer.spec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- mode: python ; coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
a = Analysis(
|
| 5 |
+
['..\\..\\installer\\mac_installer.py'],
|
| 6 |
+
pathex=['.'],
|
| 7 |
+
binaries=[],
|
| 8 |
+
datas=[],
|
| 9 |
+
hiddenimports=[],
|
| 10 |
+
hookspath=[],
|
| 11 |
+
hooksconfig={},
|
| 12 |
+
runtime_hooks=[],
|
| 13 |
+
excludes=['pygame', 'matplotlib', 'IPython'],
|
| 14 |
+
noarchive=False,
|
| 15 |
+
optimize=2,
|
| 16 |
+
)
|
| 17 |
+
pyz = PYZ(a.pure)
|
| 18 |
+
|
| 19 |
+
exe = EXE(
|
| 20 |
+
pyz,
|
| 21 |
+
a.scripts,
|
| 22 |
+
a.binaries,
|
| 23 |
+
a.datas,
|
| 24 |
+
[('O', None, 'OPTION'), ('O', None, 'OPTION')],
|
| 25 |
+
name='MAC-Installer',
|
| 26 |
+
debug=False,
|
| 27 |
+
bootloader_ignore_signals=False,
|
| 28 |
+
strip=False,
|
| 29 |
+
upx=True,
|
| 30 |
+
upx_exclude=[],
|
| 31 |
+
runtime_tmpdir=None,
|
| 32 |
+
console=False,
|
| 33 |
+
disable_windowed_traceback=False,
|
| 34 |
+
argv_emulation=False,
|
| 35 |
+
target_arch=None,
|
| 36 |
+
codesign_identity=None,
|
| 37 |
+
entitlements_file=None,
|
| 38 |
+
icon=['..\\..\\installer\\build\\mac_icon.ico'],
|
| 39 |
+
)
|
delete later/Loader.svelte
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- MAC Loader Component
|
| 2 |
+
Usage: <Loader size={140} color="#DF9076" />
|
| 3 |
+
-->
|
| 4 |
+
<script>
|
| 5 |
+
export let size = 140;
|
| 6 |
+
export let color = '#DF9076';
|
| 7 |
+
|
| 8 |
+
$: cx = size / 2;
|
| 9 |
+
$: cy = size / 2;
|
| 10 |
+
$: outerR = size * 0.36;
|
| 11 |
+
$: nodeR = size * 0.058;
|
| 12 |
+
$: ringR = size * 0.13;
|
| 13 |
+
$: holeR = size * 0.07;
|
| 14 |
+
$: sw = size * 0.026;
|
| 15 |
+
|
| 16 |
+
$: nodes = Array.from({ length: 6 }, (_, i) => {
|
| 17 |
+
const angle = (Math.PI / 3) * i - Math.PI / 6;
|
| 18 |
+
return {
|
| 19 |
+
x: cx + outerR * Math.cos(angle),
|
| 20 |
+
y: cy + outerR * Math.sin(angle),
|
| 21 |
+
delay: `${i * 0.14}s`,
|
| 22 |
+
};
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
$: hexPoints = nodes.map(n => `${n.x.toFixed(2)},${n.y.toFixed(2)}`).join(' ');
|
| 26 |
+
|
| 27 |
+
// Hex perimeter ≈ 6 × outerR (side = circumradius for regular hex)
|
| 28 |
+
$: hexPerim = 6 * outerR;
|
| 29 |
+
$: dashLen = hexPerim * 0.22;
|
| 30 |
+
$: dashGap = hexPerim - dashLen;
|
| 31 |
+
|
| 32 |
+
// Spoke length (center → node)
|
| 33 |
+
$: spokeLen = outerR;
|
| 34 |
+
|
| 35 |
+
// Center ring circumference
|
| 36 |
+
$: ringCirc = 2 * Math.PI * ringR;
|
| 37 |
+
</script>
|
| 38 |
+
|
| 39 |
+
<div class="mac-loader" style="width:{size}px; height:{size}px;">
|
| 40 |
+
<svg
|
| 41 |
+
width={size}
|
| 42 |
+
height={size}
|
| 43 |
+
viewBox="0 0 {size} {size}"
|
| 44 |
+
fill="none"
|
| 45 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 46 |
+
>
|
| 47 |
+
<!-- ── Hex outline (dimmed skeleton) ── -->
|
| 48 |
+
<polygon
|
| 49 |
+
points={hexPoints}
|
| 50 |
+
stroke={color}
|
| 51 |
+
stroke-width={sw * 0.6}
|
| 52 |
+
fill="none"
|
| 53 |
+
stroke-opacity="0.15"
|
| 54 |
+
/>
|
| 55 |
+
|
| 56 |
+
<!-- ── Hex sweep (rotating bright segment) ── -->
|
| 57 |
+
<polygon
|
| 58 |
+
class="hex-sweep"
|
| 59 |
+
points={hexPoints}
|
| 60 |
+
stroke={color}
|
| 61 |
+
stroke-width={sw * 0.9}
|
| 62 |
+
fill="none"
|
| 63 |
+
stroke-dasharray="{dashLen} {dashGap}"
|
| 64 |
+
style="--perim: {hexPerim}; animation-duration: 2.4s;"
|
| 65 |
+
/>
|
| 66 |
+
|
| 67 |
+
<!-- ── Spokes ── -->
|
| 68 |
+
{#each nodes as n, i}
|
| 69 |
+
<line
|
| 70 |
+
class="spoke"
|
| 71 |
+
x1={cx} y1={cy}
|
| 72 |
+
x2={n.x} y2={n.y}
|
| 73 |
+
stroke={color}
|
| 74 |
+
stroke-width={sw * 0.5}
|
| 75 |
+
stroke-opacity="0.2"
|
| 76 |
+
stroke-linecap="round"
|
| 77 |
+
/>
|
| 78 |
+
<!-- Traveling dot along spoke -->
|
| 79 |
+
<line
|
| 80 |
+
class="spoke-pulse"
|
| 81 |
+
x1={cx} y1={cy}
|
| 82 |
+
x2={n.x} y2={n.y}
|
| 83 |
+
stroke={color}
|
| 84 |
+
stroke-width={sw * 0.7}
|
| 85 |
+
stroke-linecap="round"
|
| 86 |
+
stroke-dasharray="{spokeLen * 0.18} {spokeLen}"
|
| 87 |
+
style="animation-delay: {n.delay}; --spoke-len: {spokeLen};"
|
| 88 |
+
/>
|
| 89 |
+
{/each}
|
| 90 |
+
|
| 91 |
+
<!-- ── Outer nodes ── -->
|
| 92 |
+
{#each nodes as n, i}
|
| 93 |
+
<!-- Glow ring -->
|
| 94 |
+
<circle
|
| 95 |
+
class="node-glow"
|
| 96 |
+
cx={n.x} cy={n.y}
|
| 97 |
+
r={nodeR * 1.9}
|
| 98 |
+
fill={color}
|
| 99 |
+
fill-opacity="0"
|
| 100 |
+
style="animation-delay: {n.delay};"
|
| 101 |
+
/>
|
| 102 |
+
<!-- Node dot -->
|
| 103 |
+
<circle
|
| 104 |
+
class="node-dot"
|
| 105 |
+
cx={n.x} cy={n.y}
|
| 106 |
+
r={nodeR}
|
| 107 |
+
fill={color}
|
| 108 |
+
fill-opacity="0.4"
|
| 109 |
+
style="animation-delay: {n.delay};"
|
| 110 |
+
/>
|
| 111 |
+
{/each}
|
| 112 |
+
|
| 113 |
+
<!-- ── Center ring (spinning arc) ── -->
|
| 114 |
+
<circle
|
| 115 |
+
cx={cx} cy={cy}
|
| 116 |
+
r={ringR}
|
| 117 |
+
stroke={color}
|
| 118 |
+
stroke-width={sw * 0.7}
|
| 119 |
+
fill="none"
|
| 120 |
+
stroke-opacity="0.15"
|
| 121 |
+
/>
|
| 122 |
+
<circle
|
| 123 |
+
class="center-arc"
|
| 124 |
+
cx={cx} cy={cy}
|
| 125 |
+
r={ringR}
|
| 126 |
+
stroke={color}
|
| 127 |
+
stroke-width={sw * 1.1}
|
| 128 |
+
fill="none"
|
| 129 |
+
stroke-dasharray="{ringCirc * 0.3} {ringCirc * 0.7}"
|
| 130 |
+
style="--circ: {ringCirc};"
|
| 131 |
+
/>
|
| 132 |
+
|
| 133 |
+
<!-- ── Center fill ── -->
|
| 134 |
+
<circle
|
| 135 |
+
class="center-core"
|
| 136 |
+
cx={cx} cy={cy}
|
| 137 |
+
r={holeR}
|
| 138 |
+
fill={color}
|
| 139 |
+
/>
|
| 140 |
+
</svg>
|
| 141 |
+
</div>
|
| 142 |
+
|
| 143 |
+
<style>
|
| 144 |
+
.mac-loader {
|
| 145 |
+
display: inline-flex;
|
| 146 |
+
align-items: center;
|
| 147 |
+
justify-content: center;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
/* Hex sweep: dash travels around the hexagon */
|
| 151 |
+
.hex-sweep {
|
| 152 |
+
transform-origin: 50% 50%;
|
| 153 |
+
animation: hexSweep 2.4s linear infinite;
|
| 154 |
+
}
|
| 155 |
+
@keyframes hexSweep {
|
| 156 |
+
from { stroke-dashoffset: 0; }
|
| 157 |
+
to { stroke-dashoffset: calc(var(--perim) * -1px); }
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/* Traveling dot along each spoke */
|
| 161 |
+
.spoke-pulse {
|
| 162 |
+
animation: spokePulse 1.8s ease-in-out infinite;
|
| 163 |
+
}
|
| 164 |
+
@keyframes spokePulse {
|
| 165 |
+
0% { stroke-dashoffset: 0; stroke-opacity: 0; }
|
| 166 |
+
10% { stroke-opacity: 0.9; }
|
| 167 |
+
80% { stroke-dashoffset: calc(var(--spoke-len) * -1px); stroke-opacity: 0; }
|
| 168 |
+
100% { stroke-dashoffset: calc(var(--spoke-len) * -1px); stroke-opacity: 0; }
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
/* Node glow pulse */
|
| 172 |
+
.node-glow {
|
| 173 |
+
animation: nodeGlow 1.8s ease-in-out infinite;
|
| 174 |
+
}
|
| 175 |
+
@keyframes nodeGlow {
|
| 176 |
+
0%, 100% { fill-opacity: 0; r: 0; }
|
| 177 |
+
40% { fill-opacity: 0.18; }
|
| 178 |
+
60% { fill-opacity: 0.08; }
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/* Node dot brightness */
|
| 182 |
+
.node-dot {
|
| 183 |
+
animation: nodeDot 1.8s ease-in-out infinite;
|
| 184 |
+
}
|
| 185 |
+
@keyframes nodeDot {
|
| 186 |
+
0%, 100% { fill-opacity: 0.25; }
|
| 187 |
+
50% { fill-opacity: 1; }
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/* Center arc rotation */
|
| 191 |
+
.center-arc {
|
| 192 |
+
transform-origin: 50% 50%;
|
| 193 |
+
animation: centerSpin 1.2s linear infinite;
|
| 194 |
+
}
|
| 195 |
+
@keyframes centerSpin {
|
| 196 |
+
from { transform: rotate(-90deg); }
|
| 197 |
+
to { transform: rotate(270deg); }
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/* Center core pulse */
|
| 201 |
+
.center-core {
|
| 202 |
+
animation: corePulse 1.8s ease-in-out infinite;
|
| 203 |
+
}
|
| 204 |
+
@keyframes corePulse {
|
| 205 |
+
0%, 100% { fill-opacity: 0.7; transform: scale(1); }
|
| 206 |
+
50% { fill-opacity: 1; transform: scale(1.15); }
|
| 207 |
+
}
|
| 208 |
+
</style>
|
delete later/MAC Loader.html
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>MAC Loader</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 9 |
+
body {
|
| 10 |
+
background: #0a0e17;
|
| 11 |
+
display: flex;
|
| 12 |
+
flex-direction: column;
|
| 13 |
+
align-items: center;
|
| 14 |
+
justify-content: center;
|
| 15 |
+
min-height: 100vh;
|
| 16 |
+
gap: 64px;
|
| 17 |
+
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
/* ── Loader wrapper ───────────────────── */
|
| 21 |
+
.loader { display: inline-flex; position: relative; }
|
| 22 |
+
|
| 23 |
+
/* ── Hex sweep ────────────────────────── */
|
| 24 |
+
.hex-sweep {
|
| 25 |
+
animation: hexSweep 2.4s linear infinite;
|
| 26 |
+
transform-origin: 50% 50%;
|
| 27 |
+
}
|
| 28 |
+
@keyframes hexSweep {
|
| 29 |
+
from { stroke-dashoffset: 0; }
|
| 30 |
+
to { stroke-dashoffset: var(--perim-neg); }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/* ── Spoke traveling dot ─────────────── */
|
| 34 |
+
.spoke-pulse {
|
| 35 |
+
animation: spokePulse 1.8s ease-in-out infinite;
|
| 36 |
+
}
|
| 37 |
+
@keyframes spokePulse {
|
| 38 |
+
0% { stroke-dashoffset: 0; stroke-opacity: 0; }
|
| 39 |
+
8% { stroke-opacity: 1; }
|
| 40 |
+
82%, 100% { stroke-dashoffset: var(--spoke-neg); stroke-opacity: 0; }
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
/* ── Node glow ───────────────────────── */
|
| 44 |
+
.node-glow {
|
| 45 |
+
animation: nodeGlow 1.8s ease-in-out infinite;
|
| 46 |
+
}
|
| 47 |
+
@keyframes nodeGlow {
|
| 48 |
+
0%, 100% { opacity: 0; }
|
| 49 |
+
45% { opacity: 1; }
|
| 50 |
+
70% { opacity: 0.3; }
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/* ── Node dot ────────────────────────── */
|
| 54 |
+
.node-dot {
|
| 55 |
+
animation: nodeDot 1.8s ease-in-out infinite;
|
| 56 |
+
}
|
| 57 |
+
@keyframes nodeDot {
|
| 58 |
+
0%, 100% { opacity: 0.3; }
|
| 59 |
+
50% { opacity: 1; }
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
/* ── Center arc spin ─────────────────── */
|
| 63 |
+
.center-arc {
|
| 64 |
+
transform-origin: 50% 50%;
|
| 65 |
+
animation: centerSpin 1.1s linear infinite;
|
| 66 |
+
}
|
| 67 |
+
@keyframes centerSpin {
|
| 68 |
+
from { transform: rotate(-90deg); }
|
| 69 |
+
to { transform: rotate(270deg); }
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/* ── Center core pulse ───────────────── */
|
| 73 |
+
.center-core {
|
| 74 |
+
transform-origin: 50% 50%;
|
| 75 |
+
animation: corePulse 1.8s ease-in-out infinite;
|
| 76 |
+
}
|
| 77 |
+
@keyframes corePulse {
|
| 78 |
+
0%, 100% { opacity: 0.65; transform: scale(1); }
|
| 79 |
+
50% { opacity: 1; transform: scale(1.18); }
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
/* ── Size showcase ───────────────────── */
|
| 83 |
+
.sizes {
|
| 84 |
+
display: flex;
|
| 85 |
+
align-items: center;
|
| 86 |
+
gap: 40px;
|
| 87 |
+
}
|
| 88 |
+
.size-wrap {
|
| 89 |
+
display: flex;
|
| 90 |
+
flex-direction: column;
|
| 91 |
+
align-items: center;
|
| 92 |
+
gap: 12px;
|
| 93 |
+
}
|
| 94 |
+
.size-label {
|
| 95 |
+
font-size: 10px;
|
| 96 |
+
letter-spacing: 0.18em;
|
| 97 |
+
text-transform: uppercase;
|
| 98 |
+
color: #3a5070;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
/* ── Bg variants ─────────────────────── */
|
| 102 |
+
.variants {
|
| 103 |
+
display: flex;
|
| 104 |
+
gap: 20px;
|
| 105 |
+
}
|
| 106 |
+
.variant {
|
| 107 |
+
border-radius: 16px;
|
| 108 |
+
padding: 32px 40px;
|
| 109 |
+
display: flex;
|
| 110 |
+
align-items: center;
|
| 111 |
+
justify-content: center;
|
| 112 |
+
}
|
| 113 |
+
.variant.dark { background: #0d1520; border: 1px solid rgba(255,255,255,0.06); }
|
| 114 |
+
.variant.mid { background: #1a1a2e; border: 1px solid rgba(255,255,255,0.06); }
|
| 115 |
+
.variant.light { background: #f5f0ed; }
|
| 116 |
+
.variant.white { background: #ffffff; }
|
| 117 |
+
</style>
|
| 118 |
+
</head>
|
| 119 |
+
<body>
|
| 120 |
+
|
| 121 |
+
<!-- Hero size -->
|
| 122 |
+
<div class="loader" id="hero"></div>
|
| 123 |
+
|
| 124 |
+
<!-- Size showcase -->
|
| 125 |
+
<div class="sizes">
|
| 126 |
+
<div class="size-wrap">
|
| 127 |
+
<div class="loader" id="sz64"></div>
|
| 128 |
+
<span class="size-label">64px</span>
|
| 129 |
+
</div>
|
| 130 |
+
<div class="size-wrap">
|
| 131 |
+
<div class="loader" id="sz48"></div>
|
| 132 |
+
<span class="size-label">48px</span>
|
| 133 |
+
</div>
|
| 134 |
+
<div class="size-wrap">
|
| 135 |
+
<div class="loader" id="sz32"></div>
|
| 136 |
+
<span class="size-label">32px</span>
|
| 137 |
+
</div>
|
| 138 |
+
<div class="size-wrap">
|
| 139 |
+
<div class="loader" id="sz24"></div>
|
| 140 |
+
<span class="size-label">24px</span>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<!-- Background variants -->
|
| 145 |
+
<div class="variants">
|
| 146 |
+
<div class="variant dark"> <div class="loader" id="v1"></div></div>
|
| 147 |
+
<div class="variant mid"> <div class="loader" id="v2"></div></div>
|
| 148 |
+
<div class="variant light"> <div class="loader" id="v3"></div></div>
|
| 149 |
+
<div class="variant white"> <div class="loader" id="v4"></div></div>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
<script>
|
| 153 |
+
const COLOR = '#DF9076';
|
| 154 |
+
|
| 155 |
+
function makeLoader(container, size) {
|
| 156 |
+
const cx = size / 2, cy = size / 2;
|
| 157 |
+
const outerR = size * 0.36;
|
| 158 |
+
const nodeR = size * 0.058;
|
| 159 |
+
const ringR = size * 0.13;
|
| 160 |
+
const holeR = size * 0.068;
|
| 161 |
+
const sw = size * 0.026;
|
| 162 |
+
const hexPerim = 6 * outerR;
|
| 163 |
+
const dashLen = hexPerim * 0.2;
|
| 164 |
+
const dashGap = hexPerim - dashLen;
|
| 165 |
+
const ringCirc = 2 * Math.PI * ringR;
|
| 166 |
+
const sweepDash = ringCirc * 0.28;
|
| 167 |
+
const sweepGap = ringCirc - sweepDash;
|
| 168 |
+
|
| 169 |
+
const nodes = Array.from({ length: 6 }, (_, i) => {
|
| 170 |
+
const a = Math.PI / 3 * i - Math.PI / 6;
|
| 171 |
+
return {
|
| 172 |
+
x: cx + outerR * Math.cos(a),
|
| 173 |
+
y: cy + outerR * Math.sin(a),
|
| 174 |
+
delay: `${(i * 0.14).toFixed(2)}s`,
|
| 175 |
+
};
|
| 176 |
+
});
|
| 177 |
+
|
| 178 |
+
const hexPts = nodes.map(n => `${n.x.toFixed(2)},${n.y.toFixed(2)}`).join(' ');
|
| 179 |
+
const spokeLen = outerR;
|
| 180 |
+
const spokeDash = spokeLen * 0.18;
|
| 181 |
+
|
| 182 |
+
const svgNS = 'http://www.w3.org/2000/svg';
|
| 183 |
+
const svg = document.createElementNS(svgNS, 'svg');
|
| 184 |
+
svg.setAttribute('width', size);
|
| 185 |
+
svg.setAttribute('height', size);
|
| 186 |
+
svg.setAttribute('viewBox', `0 0 ${size} ${size}`);
|
| 187 |
+
svg.setAttribute('fill', 'none');
|
| 188 |
+
|
| 189 |
+
function el(tag, attrs) {
|
| 190 |
+
const e = document.createElementNS(svgNS, tag);
|
| 191 |
+
Object.entries(attrs).forEach(([k, v]) => e.setAttribute(k, v));
|
| 192 |
+
return e;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
// Hex skeleton
|
| 196 |
+
svg.appendChild(el('polygon', {
|
| 197 |
+
points: hexPts, stroke: COLOR,
|
| 198 |
+
'stroke-width': sw * 0.55, fill: 'none', 'stroke-opacity': '0.15',
|
| 199 |
+
}));
|
| 200 |
+
|
| 201 |
+
// Hex sweep
|
| 202 |
+
const sweep = el('polygon', {
|
| 203 |
+
points: hexPts, stroke: COLOR,
|
| 204 |
+
'stroke-width': sw * 0.85, fill: 'none',
|
| 205 |
+
'stroke-dasharray': `${dashLen} ${dashGap}`,
|
| 206 |
+
class: 'hex-sweep',
|
| 207 |
+
});
|
| 208 |
+
sweep.style.setProperty('--perim-neg', `${-hexPerim}px`);
|
| 209 |
+
svg.appendChild(sweep);
|
| 210 |
+
|
| 211 |
+
// Spokes + traveling dots
|
| 212 |
+
nodes.forEach(n => {
|
| 213 |
+
svg.appendChild(el('line', {
|
| 214 |
+
x1: cx, y1: cy, x2: n.x, y2: n.y,
|
| 215 |
+
stroke: COLOR, 'stroke-width': sw * 0.45,
|
| 216 |
+
'stroke-opacity': '0.18', 'stroke-linecap': 'round',
|
| 217 |
+
}));
|
| 218 |
+
|
| 219 |
+
const sp = el('line', {
|
| 220 |
+
x1: cx, y1: cy, x2: n.x, y2: n.y,
|
| 221 |
+
stroke: COLOR, 'stroke-width': sw * 0.75,
|
| 222 |
+
'stroke-linecap': 'round',
|
| 223 |
+
'stroke-dasharray': `${spokeDash} ${spokeLen + spokeDash}`,
|
| 224 |
+
class: 'spoke-pulse',
|
| 225 |
+
});
|
| 226 |
+
sp.style.animationDelay = n.delay;
|
| 227 |
+
sp.style.setProperty('--spoke-neg', `${-(spokeLen + spokeDash)}px`);
|
| 228 |
+
svg.appendChild(sp);
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
// Node glows + dots
|
| 232 |
+
nodes.forEach(n => {
|
| 233 |
+
const glow = el('circle', {
|
| 234 |
+
cx: n.x, cy: n.y, r: nodeR * 2.2,
|
| 235 |
+
fill: COLOR, class: 'node-glow',
|
| 236 |
+
});
|
| 237 |
+
glow.style.animationDelay = n.delay;
|
| 238 |
+
svg.appendChild(glow);
|
| 239 |
+
|
| 240 |
+
const dot = el('circle', {
|
| 241 |
+
cx: n.x, cy: n.y, r: nodeR,
|
| 242 |
+
fill: COLOR, class: 'node-dot',
|
| 243 |
+
});
|
| 244 |
+
dot.style.animationDelay = n.delay;
|
| 245 |
+
svg.appendChild(dot);
|
| 246 |
+
});
|
| 247 |
+
|
| 248 |
+
// Center ring background
|
| 249 |
+
svg.appendChild(el('circle', {
|
| 250 |
+
cx, cy, r: ringR,
|
| 251 |
+
stroke: COLOR, 'stroke-width': sw * 0.65,
|
| 252 |
+
fill: 'none', 'stroke-opacity': '0.14',
|
| 253 |
+
}));
|
| 254 |
+
|
| 255 |
+
// Center spinning arc
|
| 256 |
+
const arc = el('circle', {
|
| 257 |
+
cx, cy, r: ringR,
|
| 258 |
+
stroke: COLOR, 'stroke-width': sw * 1.1,
|
| 259 |
+
fill: 'none',
|
| 260 |
+
'stroke-dasharray': `${sweepDash} ${sweepGap}`,
|
| 261 |
+
'stroke-linecap': 'round',
|
| 262 |
+
class: 'center-arc',
|
| 263 |
+
});
|
| 264 |
+
svg.appendChild(arc);
|
| 265 |
+
|
| 266 |
+
// Center core
|
| 267 |
+
const core = el('circle', {
|
| 268 |
+
cx, cy, r: holeR,
|
| 269 |
+
fill: COLOR, class: 'center-core',
|
| 270 |
+
});
|
| 271 |
+
svg.appendChild(core);
|
| 272 |
+
|
| 273 |
+
container.appendChild(svg);
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
makeLoader(document.getElementById('hero'), 180);
|
| 277 |
+
makeLoader(document.getElementById('sz64'), 64);
|
| 278 |
+
makeLoader(document.getElementById('sz48'), 48);
|
| 279 |
+
makeLoader(document.getElementById('sz32'), 32);
|
| 280 |
+
makeLoader(document.getElementById('sz24'), 24);
|
| 281 |
+
makeLoader(document.getElementById('v1'), 80);
|
| 282 |
+
makeLoader(document.getElementById('v2'), 80);
|
| 283 |
+
makeLoader(document.getElementById('v3'), 80);
|
| 284 |
+
makeLoader(document.getElementById('v4'), 80);
|
| 285 |
+
</script>
|
| 286 |
+
</body>
|
| 287 |
+
</html>
|
delete later/MBM-MAC Globe.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/MAC-Installer.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c01d907d83babf33d5cf35d00b9359724b5edb688bd169bb5771df39eec313b4
|
| 3 |
+
size 29829754
|
docker-compose.worker.yml
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ═══════════════════════════════════════════════════════════
|
| 2 |
+
# MAC Worker Node — run this on each worker PC
|
| 3 |
+
# Worker PCs run: vLLM (GPU inference) + optional Jupyter
|
| 4 |
+
# PostgreSQL/Redis/Nginx stay on the master node only.
|
| 5 |
+
#
|
| 6 |
+
# Steps:
|
| 7 |
+
# 1. Copy this file + worker_agent.py to the worker PC
|
| 8 |
+
# 2. Create .env.worker with MAC_ENROLL_TOKEN and MAC_MASTER_URL
|
| 9 |
+
# 3. docker compose -f docker-compose.worker.yml up -d
|
| 10 |
+
# 4. Admin approves the node in the MAC cluster panel
|
| 11 |
+
# ═══════════════════════════════════════════════════════════
|
| 12 |
+
|
| 13 |
+
services:
|
| 14 |
+
|
| 15 |
+
# ── vLLM GPU Inference ─────────────────────────────────────
|
| 16 |
+
vllm:
|
| 17 |
+
image: vllm/vllm-openai:latest
|
| 18 |
+
container_name: mac-worker-vllm
|
| 19 |
+
ports:
|
| 20 |
+
- "${VLLM_PORT:-8001}:8001"
|
| 21 |
+
environment:
|
| 22 |
+
- HF_HOME=/root/.cache/huggingface
|
| 23 |
+
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN:-}
|
| 24 |
+
volumes:
|
| 25 |
+
- hf-cache:/root/.cache/huggingface
|
| 26 |
+
command: >
|
| 27 |
+
--model ${VLLM_MODEL:-Qwen/Qwen2.5-7B-Instruct-AWQ}
|
| 28 |
+
--port ${VLLM_PORT:-8001}
|
| 29 |
+
--gpu-memory-utilization ${VLLM_GPU_MEM:-0.85}
|
| 30 |
+
--max-model-len ${VLLM_MAX_LEN:-8192}
|
| 31 |
+
--trust-remote-code
|
| 32 |
+
--enforce-eager
|
| 33 |
+
--served-model-name ${VLLM_SERVED_NAME:-Qwen/Qwen2.5-7B-Instruct-AWQ}
|
| 34 |
+
deploy:
|
| 35 |
+
resources:
|
| 36 |
+
reservations:
|
| 37 |
+
devices:
|
| 38 |
+
- driver: nvidia
|
| 39 |
+
count: 1
|
| 40 |
+
capabilities: [gpu]
|
| 41 |
+
restart: unless-stopped
|
| 42 |
+
networks:
|
| 43 |
+
- worker-net
|
| 44 |
+
healthcheck:
|
| 45 |
+
test: ["CMD-SHELL", "curl -sf http://localhost:${VLLM_PORT:-8001}/health || exit 1"]
|
| 46 |
+
interval: 30s
|
| 47 |
+
timeout: 10s
|
| 48 |
+
retries: 3
|
| 49 |
+
start_period: 120s
|
| 50 |
+
|
| 51 |
+
# ── Jupyter Kernel Gateway (optional — for notebook offload) ──
|
| 52 |
+
# Enable by setting ENABLE_NOTEBOOK=1 in .env.worker
|
| 53 |
+
jupyter:
|
| 54 |
+
image: jupyter/scipy-notebook:latest
|
| 55 |
+
container_name: mac-worker-jupyter
|
| 56 |
+
ports:
|
| 57 |
+
- "${NOTEBOOK_PORT:-8888}:8888"
|
| 58 |
+
environment:
|
| 59 |
+
- JUPYTER_ENABLE_LAB=no
|
| 60 |
+
command: >
|
| 61 |
+
jupyter kernelgateway
|
| 62 |
+
--KernelGatewayApp.ip=0.0.0.0
|
| 63 |
+
--KernelGatewayApp.port=8888
|
| 64 |
+
--KernelGatewayApp.allow_origin=*
|
| 65 |
+
--KernelGatewayApp.auth_token=${JUPYTER_TOKEN:-mac-notebook-token}
|
| 66 |
+
volumes:
|
| 67 |
+
- notebooks:/home/jovyan/work
|
| 68 |
+
restart: unless-stopped
|
| 69 |
+
networks:
|
| 70 |
+
- worker-net
|
| 71 |
+
profiles:
|
| 72 |
+
- notebook # only starts with: docker compose --profile notebook up
|
| 73 |
+
|
| 74 |
+
# ── Worker Agent ───────────────────────────────────────────
|
| 75 |
+
worker-agent:
|
| 76 |
+
image: python:3.11-slim
|
| 77 |
+
container_name: mac-worker-agent
|
| 78 |
+
working_dir: /app
|
| 79 |
+
volumes:
|
| 80 |
+
- ./worker_agent.py:/app/worker_agent.py:ro
|
| 81 |
+
command: >
|
| 82 |
+
sh -c "pip install --quiet httpx psutil pynvml && python worker_agent.py"
|
| 83 |
+
environment:
|
| 84 |
+
- MAC_MASTER_URL=${MAC_MASTER_URL}
|
| 85 |
+
- MAC_ENROLL_TOKEN=${MAC_ENROLL_TOKEN:-}
|
| 86 |
+
- MAC_NODE_TOKEN=${MAC_NODE_TOKEN:-}
|
| 87 |
+
- MAC_WORKER_NAME=${MAC_WORKER_NAME:-Worker}
|
| 88 |
+
- MAC_VLLM_PORT=${VLLM_PORT:-8001}
|
| 89 |
+
- MAC_NOTEBOOK_PORT=${NOTEBOOK_PORT:-}
|
| 90 |
+
- MAC_TAGS=${MAC_TAGS:-llm}
|
| 91 |
+
- MAC_HEARTBEAT_SEC=${HEARTBEAT_SEC:-10}
|
| 92 |
+
network_mode: host # needs to see vLLM on localhost AND reach master
|
| 93 |
+
restart: unless-stopped
|
| 94 |
+
depends_on:
|
| 95 |
+
vllm:
|
| 96 |
+
condition: service_healthy
|
| 97 |
+
|
| 98 |
+
volumes:
|
| 99 |
+
hf-cache:
|
| 100 |
+
notebooks:
|
| 101 |
+
|
| 102 |
+
networks:
|
| 103 |
+
worker-net:
|
| 104 |
+
driver: bridge
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ═══════════════════════════════════════════════════════════
|
| 2 |
+
# MAC — MBM AI Cloud | Local Server Setup (12GB GPU)
|
| 3 |
+
# ═══════════════════════════════════════════════════════════
|
| 4 |
+
# RTX 3060 12GB VRAM — single model at a time strategy.
|
| 5 |
+
# GPU: Qwen2.5-7B chat/code ~ 5GB (gpu_memory_utilization=0.45)
|
| 6 |
+
# CPU: Whisper STT + Piper TTS ~ 1.5GB RAM (no VRAM)
|
| 7 |
+
# Infra: PostgreSQL + Redis + Nginx + Qdrant + SearXNG
|
| 8 |
+
# ═══════════════════════════════════════════════════════════
|
| 9 |
+
|
| 10 |
+
services:
|
| 11 |
+
|
| 12 |
+
# ── MAC API Server ──────────────────────────────────────
|
| 13 |
+
mac:
|
| 14 |
+
build: .
|
| 15 |
+
container_name: mac-api
|
| 16 |
+
ports:
|
| 17 |
+
- "${APP_HOST:-0.0.0.0}:8001:8000"
|
| 18 |
+
env_file: .env
|
| 19 |
+
environment:
|
| 20 |
+
- DATABASE_URL=postgresql+asyncpg://mac:mac_password@postgres:5432/mac_db
|
| 21 |
+
- REDIS_URL=redis://redis:6379/0
|
| 22 |
+
- VLLM_BASE_URL=http://vllm-speed:8001
|
| 23 |
+
- VLLM_SPEED_URL=http://vllm-speed:8001
|
| 24 |
+
- VLLM_CODE_URL=http://vllm-speed:8001
|
| 25 |
+
- VLLM_REASONING_URL=http://vllm-speed:8001
|
| 26 |
+
- VLLM_INTELLIGENCE_URL=http://vllm-speed:8001
|
| 27 |
+
- WHISPER_URL=http://whisper:8000
|
| 28 |
+
- TTS_URL=http://tts:8000
|
| 29 |
+
- EMBEDDING_URL=http://vllm-speed:8001
|
| 30 |
+
- QDRANT_URL=http://qdrant:6333
|
| 31 |
+
- SEARXNG_URL=http://searxng:8080
|
| 32 |
+
- MAC_ENABLED_MODELS=qwen2.5:7b,whisper-small,tts-piper
|
| 33 |
+
depends_on:
|
| 34 |
+
postgres:
|
| 35 |
+
condition: service_healthy
|
| 36 |
+
redis:
|
| 37 |
+
condition: service_healthy
|
| 38 |
+
restart: unless-stopped
|
| 39 |
+
networks:
|
| 40 |
+
- mac-net
|
| 41 |
+
|
| 42 |
+
# ═══════════════════════════════════════════════════════
|
| 43 |
+
# vLLM GPU INFERENCE — Single model for 12GB GPU
|
| 44 |
+
# ═══════════════════════════════════════════════════════
|
| 45 |
+
|
| 46 |
+
# ── Speed Model: Qwen2.5-7B (handles ALL chat/code/general) ──
|
| 47 |
+
vllm-speed:
|
| 48 |
+
image: vllm/vllm-openai:latest
|
| 49 |
+
container_name: mac-vllm-speed
|
| 50 |
+
ports:
|
| 51 |
+
- "${VLLM_SPEED_PORT:-8001}:${VLLM_SPEED_PORT:-8001}"
|
| 52 |
+
environment:
|
| 53 |
+
- HF_HOME=/root/.cache/huggingface
|
| 54 |
+
volumes:
|
| 55 |
+
- hf-cache:/root/.cache/huggingface
|
| 56 |
+
command: >
|
| 57 |
+
--model ${VLLM_SPEED_MODEL:-Qwen/Qwen2.5-7B-Instruct-AWQ}
|
| 58 |
+
--port ${VLLM_SPEED_PORT:-8001}
|
| 59 |
+
--gpu-memory-utilization 0.85
|
| 60 |
+
--max-model-len 8192
|
| 61 |
+
--trust-remote-code
|
| 62 |
+
--enforce-eager
|
| 63 |
+
deploy:
|
| 64 |
+
resources:
|
| 65 |
+
reservations:
|
| 66 |
+
devices:
|
| 67 |
+
- driver: nvidia
|
| 68 |
+
count: 1
|
| 69 |
+
capabilities: [gpu]
|
| 70 |
+
restart: unless-stopped
|
| 71 |
+
networks:
|
| 72 |
+
- mac-net
|
| 73 |
+
|
| 74 |
+
# ── Code/Reasoning/Intelligence models DISABLED (12GB GPU) ──
|
| 75 |
+
# Uncomment when upgrading to 24GB+ GPU
|
| 76 |
+
# vllm-code:
|
| 77 |
+
# ...
|
| 78 |
+
# vllm-reason:
|
| 79 |
+
# ...
|
| 80 |
+
# vllm-intel:
|
| 81 |
+
# ...
|
| 82 |
+
|
| 83 |
+
# ═══════════════════════════════════════════════════════
|
| 84 |
+
# SPEECH & AUDIO SERVICES (CPU — saves GPU for LLM)
|
| 85 |
+
# ═══════════════════════════════════════════════════════
|
| 86 |
+
|
| 87 |
+
# ── Whisper — Speech-to-Text (CPU mode) ────────────────
|
| 88 |
+
whisper:
|
| 89 |
+
image: fedirz/faster-whisper-server:latest-cpu
|
| 90 |
+
container_name: mac-whisper
|
| 91 |
+
ports:
|
| 92 |
+
- "${WHISPER_PORT:-8005}:8000"
|
| 93 |
+
environment:
|
| 94 |
+
- WHISPER__MODEL=${WHISPER_MODEL:-Systran/faster-whisper-small}
|
| 95 |
+
- WHISPER__DEVICE=cpu
|
| 96 |
+
restart: unless-stopped
|
| 97 |
+
networks:
|
| 98 |
+
- mac-net
|
| 99 |
+
|
| 100 |
+
# ── Piper TTS — Text-to-Speech (CPU, lightweight) ─────
|
| 101 |
+
# TEMPORARILY DISABLED — image still downloading on slow WiFi
|
| 102 |
+
# tts:
|
| 103 |
+
# image: ghcr.io/matatonic/openedai-speech:latest
|
| 104 |
+
# container_name: mac-tts
|
| 105 |
+
# ports:
|
| 106 |
+
# - "${TTS_PORT:-8006}:8000"
|
| 107 |
+
# volumes:
|
| 108 |
+
# - tts-voices:/app/voices
|
| 109 |
+
# restart: unless-stopped
|
| 110 |
+
# networks:
|
| 111 |
+
# - mac-net
|
| 112 |
+
|
| 113 |
+
# ═══════════════════════════════════════════════════════
|
| 114 |
+
# INFRASTRUCTURE SERVICES
|
| 115 |
+
# ═══════════════════════════════════════════════════════
|
| 116 |
+
|
| 117 |
+
# ── PostgreSQL — Persistent data store ─────────────────
|
| 118 |
+
postgres:
|
| 119 |
+
image: postgres:16-alpine
|
| 120 |
+
container_name: mac-postgres
|
| 121 |
+
environment:
|
| 122 |
+
POSTGRES_USER: mac
|
| 123 |
+
POSTGRES_PASSWORD: mac_password
|
| 124 |
+
POSTGRES_DB: mac_db
|
| 125 |
+
ports:
|
| 126 |
+
- "5433:5432"
|
| 127 |
+
volumes:
|
| 128 |
+
- pgdata:/var/lib/postgresql/data
|
| 129 |
+
healthcheck:
|
| 130 |
+
test: ["CMD-SHELL", "pg_isready -U mac -d mac_db"]
|
| 131 |
+
interval: 5s
|
| 132 |
+
timeout: 5s
|
| 133 |
+
retries: 5
|
| 134 |
+
restart: unless-stopped
|
| 135 |
+
networks:
|
| 136 |
+
- mac-net
|
| 137 |
+
|
| 138 |
+
# ── pgAdmin — PostgreSQL admin UI (local-only by default) ──
|
| 139 |
+
pgadmin:
|
| 140 |
+
image: dpage/pgadmin4:8
|
| 141 |
+
container_name: mac-pgadmin
|
| 142 |
+
ports:
|
| 143 |
+
- "127.0.0.1:${PGADMIN_PORT:-5051}:80"
|
| 144 |
+
environment:
|
| 145 |
+
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@mbm.ac.in}
|
| 146 |
+
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-ChangeThisStrongPassword!}
|
| 147 |
+
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "True"
|
| 148 |
+
depends_on:
|
| 149 |
+
postgres:
|
| 150 |
+
condition: service_healthy
|
| 151 |
+
volumes:
|
| 152 |
+
- pgadmin-data:/var/lib/pgadmin
|
| 153 |
+
restart: unless-stopped
|
| 154 |
+
networks:
|
| 155 |
+
- mac-net
|
| 156 |
+
|
| 157 |
+
# ── Redis — Rate limiting & caching ────────────────────
|
| 158 |
+
redis:
|
| 159 |
+
image: redis:7-alpine
|
| 160 |
+
container_name: mac-redis
|
| 161 |
+
ports:
|
| 162 |
+
- "6380:6379"
|
| 163 |
+
volumes:
|
| 164 |
+
- redisdata:/data
|
| 165 |
+
healthcheck:
|
| 166 |
+
test: ["CMD", "redis-cli", "ping"]
|
| 167 |
+
interval: 5s
|
| 168 |
+
timeout: 5s
|
| 169 |
+
retries: 5
|
| 170 |
+
restart: unless-stopped
|
| 171 |
+
networks:
|
| 172 |
+
- mac-net
|
| 173 |
+
|
| 174 |
+
# ── Nginx — Reverse proxy + SvelteKit frontend ─────────
|
| 175 |
+
nginx:
|
| 176 |
+
image: nginx:alpine
|
| 177 |
+
container_name: mac-nginx
|
| 178 |
+
ports:
|
| 179 |
+
- "${APP_HOST:-0.0.0.0}:${APP_PORT:-80}:80"
|
| 180 |
+
volumes:
|
| 181 |
+
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
| 182 |
+
- ./frontend/build:/app:ro # SvelteKit static build output
|
| 183 |
+
depends_on:
|
| 184 |
+
- mac
|
| 185 |
+
restart: unless-stopped
|
| 186 |
+
networks:
|
| 187 |
+
- mac-net
|
| 188 |
+
|
| 189 |
+
# ── Qdrant — Vector DB for RAG ─────────────────────────
|
| 190 |
+
qdrant:
|
| 191 |
+
image: qdrant/qdrant:latest
|
| 192 |
+
container_name: mac-qdrant
|
| 193 |
+
ports:
|
| 194 |
+
- "6333:6333"
|
| 195 |
+
volumes:
|
| 196 |
+
- qdrantdata:/qdrant/storage
|
| 197 |
+
restart: unless-stopped
|
| 198 |
+
networks:
|
| 199 |
+
- mac-net
|
| 200 |
+
|
| 201 |
+
# ── SearXNG — Self-hosted web search ───────────────────
|
| 202 |
+
searxng:
|
| 203 |
+
image: searxng/searxng:latest
|
| 204 |
+
container_name: mac-searxng
|
| 205 |
+
ports:
|
| 206 |
+
- "8888:8080"
|
| 207 |
+
environment:
|
| 208 |
+
- SEARXNG_BASE_URL=http://localhost:8888/
|
| 209 |
+
volumes:
|
| 210 |
+
- searxngdata:/etc/searxng
|
| 211 |
+
restart: unless-stopped
|
| 212 |
+
networks:
|
| 213 |
+
- mac-net
|
| 214 |
+
|
| 215 |
+
volumes:
|
| 216 |
+
pgdata:
|
| 217 |
+
pgadmin-data:
|
| 218 |
+
redisdata:
|
| 219 |
+
qdrantdata:
|
| 220 |
+
searxngdata:
|
| 221 |
+
hf-cache: # Shared HuggingFace model cache across all vLLM instances
|
| 222 |
+
tts-voices: # Persisted TTS voice models
|
| 223 |
+
|
| 224 |
+
networks:
|
| 225 |
+
mac-net:
|
| 226 |
+
driver: bridge
|
docs/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MAC — Architecture Reference
|
| 2 |
+
|
| 3 |
+
> **Audience:** an AI coding agent (or new engineer) dropped into this repo with no prior context.
|
| 4 |
+
> **Goal:** understand the system end-to-end — every subsystem, the data flow, where state lives, and how the pieces secure and observe each other.
|
| 5 |
+
> Read [README.md](README.md) for the elevator pitch and [MAC-PROGRESS.md](MAC-PROGRESS.md) for the build log. This file is the *map*.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 0. Identity in one paragraph
|
| 10 |
+
|
| 11 |
+
MAC (MBM AI Cloud) is a **self-hosted, on-prem AI platform** for MBM University Jodhpur. It gives students/faculty a private ChatGPT-style chat, a notebook IDE, RAG over college docs, an attendance system using face capture, an exam copy-check workflow with AI vision + plagiarism detection, and an admin/cluster console — all powered by **open-source LLMs** running on the college's own GPUs. There are no external API calls; vLLM serves models locally, and worker GPUs are added by enrolling them into the cluster.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 1. Top-level topology
|
| 16 |
+
|
| 17 |
+
```
|
| 18 |
+
┌────────────────────────────────────────────────────────────────┐
|
| 19 |
+
│ CLIENTS │
|
| 20 |
+
│ • Web (SvelteKit PWA, served by Nginx in prod) │
|
| 21 |
+
│ • API consumers (curl / Python SDK / scripts) │
|
| 22 |
+
└──────────────────────────┬─────────────────────────────────────┘
|
| 23 |
+
│ HTTPS
|
| 24 |
+
▼
|
| 25 |
+
┌──────────────────────┐
|
| 26 |
+
│ NGINX │ ← TLS, gzip, /api → mac, / → static
|
| 27 |
+
└──────────┬───────────┘
|
| 28 |
+
│
|
| 29 |
+
┌──────────────────┴──────────────────┐
|
| 30 |
+
▼ ▼
|
| 31 |
+
┌─────────────────┐ ┌──────────────────────┐
|
| 32 |
+
│ SvelteKit │ │ FastAPI (mac.main) │
|
| 33 |
+
│ static build │ │ /api/v1/* │
|
| 34 |
+
└─────────────────┘ └──────────┬───────────┘
|
| 35 |
+
│
|
| 36 |
+
┌───────────────────────┬───────────────────┼─────────────────────────┐
|
| 37 |
+
▼ ▼ ▼ ▼
|
| 38 |
+
┌────────────┐ ┌────────────┐ ┌──────────────┐ ┌────────────────┐
|
| 39 |
+
│ PostgreSQL │ │ Redis │ │ Qdrant │ │ SearXNG │
|
| 40 |
+
│ (primary) │ │ cache / │ │ (RAG vec) │ │ (web search) │
|
| 41 |
+
│ Alembic │ │ bl / rl │ └──────────────┘ └────────────────┘
|
| 42 |
+
└────────────┘ └────────────┘
|
| 43 |
+
|
| 44 |
+
▲ load_balancer.get_best_worker()
|
| 45 |
+
│
|
| 46 |
+
┌───────────────────────┴───────────────────────────────────────────────┐
|
| 47 |
+
│ MAC CLUSTER (GPU workers, any LAN PC) │
|
| 48 |
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
|
| 49 |
+
│ │ vLLM (OpenAI │ │ Jupyter kernel │ │ worker_agent.py│ │
|
| 50 |
+
│ │ compatible) │ │ gateway (opt.) │ │ (heartbeat) │ │
|
| 51 |
+
│ └─────────────────┘ └─────────────────┘ └────────────────┘ │
|
| 52 |
+
└────────────────────────────────────────────────────────────────────────┘
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
- **Master node** runs FastAPI + Postgres + Redis + Nginx + Qdrant + SearXNG.
|
| 56 |
+
- **Worker nodes** run vLLM + an optional Jupyter kernel gateway, plus [worker_agent.py](worker_agent.py) which self-registers via an enrollment token and sends a heartbeat every 10s (GPU util, VRAM, RAM, CPU).
|
| 57 |
+
- **Routing** is master-side: every user request hits the master API, which uses [mac/services/load_balancer.py](mac/services/load_balancer.py) to score-pick the best worker for an LLM call or notebook kernel.
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## 2. Repository map (what lives where)
|
| 62 |
+
|
| 63 |
+
```
|
| 64 |
+
mac/
|
| 65 |
+
main.py FastAPI app, lifespan (DB init, dev seeds, bg tasks),
|
| 66 |
+
router mounts under /api/v1, root SPA fallback.
|
| 67 |
+
config.py Pydantic Settings — every env var + .env loader.
|
| 68 |
+
database.py Async SQLAlchemy engine + session factory; `Base`.
|
| 69 |
+
utils/security.py JWT encode/decode + jti generation; password hash.
|
| 70 |
+
middleware/
|
| 71 |
+
auth_middleware.py Bearer extractor → JWT | legacy-key | scoped-key → User.
|
| 72 |
+
rate_limit.py Per-user req/hour + token/day; injects X-RateLimit-*.
|
| 73 |
+
feature_gate.py feature_required("ai_chat") dependency.
|
| 74 |
+
models/ SQLAlchemy ORM models (one file per domain).
|
| 75 |
+
schemas/ Pydantic request/response schemas.
|
| 76 |
+
services/ Pure business logic, no HTTP — called by routers.
|
| 77 |
+
routers/ FastAPI routers, thin: validate → call service → return.
|
| 78 |
+
|
| 79 |
+
frontend/ SvelteKit 2 + Svelte 5 PWA.
|
| 80 |
+
src/routes/ File-system routing: login, setup, chat, dashboard,
|
| 81 |
+
admin, cluster, keys, settings, notifications, rag.
|
| 82 |
+
src/lib/api.js Single fetch wrapper; one export per backend domain.
|
| 83 |
+
src/lib/stores.js Svelte stores (auth, setup, features, chat, toast).
|
| 84 |
+
src/lib/i18n.js 19 Indian languages, lazy-loaded strings, RTL support.
|
| 85 |
+
static/manifest.json PWA manifest; static/sw.js is a no-cache worker.
|
| 86 |
+
|
| 87 |
+
alembic/ Migration env + versioned revisions.
|
| 88 |
+
nginx/ nginx.conf (HTTP) + nginx.https.conf (TLS).
|
| 89 |
+
docker-compose.yml Master stack.
|
| 90 |
+
docker-compose.worker.yml Worker stack (vLLM + worker_agent).
|
| 91 |
+
worker_agent.py Enrollment + heartbeat agent for a GPU node.
|
| 92 |
+
installer/ Windows installer (PyInstaller) + branding assets.
|
| 93 |
+
tests/ pytest suite.
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## 3. Request lifecycle (the universal path)
|
| 99 |
+
|
| 100 |
+
Every authenticated `/api/v1/*` request goes through these layers in order. Knowing this map means you can audit any new endpoint quickly.
|
| 101 |
+
|
| 102 |
+
```
|
| 103 |
+
HTTP request
|
| 104 |
+
│
|
| 105 |
+
▼
|
| 106 |
+
[1] CORS middleware (mac/main.py — allow_origins from settings)
|
| 107 |
+
│
|
| 108 |
+
▼
|
| 109 |
+
[2] Route handler (FastAPI) (mac/routers/*.py)
|
| 110 |
+
│ Depends(get_current_user)
|
| 111 |
+
▼
|
| 112 |
+
[3] Auth resolver (mac/middleware/auth_middleware.py)
|
| 113 |
+
│ Bearer token → branch:
|
| 114 |
+
│ • mac_sk_live_* → legacy API key (User.api_key)
|
| 115 |
+
│ • mac_sk_* → scoped API key (hashed, scopes, expiry, revocable)
|
| 116 |
+
│ • else → JWT (verify sig, check exp, check jti blacklist)
|
| 117 |
+
│ → returns User or raises 401
|
| 118 |
+
│
|
| 119 |
+
▼
|
| 120 |
+
[4] Role guard (optional) require_admin / require_faculty_or_admin
|
| 121 |
+
│
|
| 122 |
+
▼
|
| 123 |
+
[5] Feature gate (optional) feature_required("ai_chat")
|
| 124 |
+
│ → reads system_config / feature_flags table → 403 if disabled for role
|
| 125 |
+
│
|
| 126 |
+
▼
|
| 127 |
+
[6] Rate limit (optional) check_rate_limit
|
| 128 |
+
│ • requests/hour from usage_log (per-user)
|
| 129 |
+
│ • tokens/day from usage_log (per-user)
|
| 130 |
+
│ • injects X-RateLimit-* into request.state
|
| 131 |
+
│
|
| 132 |
+
▼
|
| 133 |
+
[7] Service layer mac/services/*.py
|
| 134 |
+
│ Business logic — never imports FastAPI; takes db: AsyncSession.
|
| 135 |
+
│
|
| 136 |
+
▼
|
| 137 |
+
[8] Response → HTTP middleware inject_rate_limit_headers reads request.state
|
| 138 |
+
and stamps headers onto the response
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
This separation is the single most important design rule:
|
| 142 |
+
**routers do parsing + auth + I/O orchestration; services do business logic; models do persistence.** Anything calling FastAPI types from a service is a smell.
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## 4. Identity & access — auth, sessions, keys
|
| 147 |
+
|
| 148 |
+
There are **three** ways a request authenticates, all collapsed to a `User` by `get_current_user`:
|
| 149 |
+
|
| 150 |
+
### 4.1 JWT (interactive users)
|
| 151 |
+
- Login: `POST /api/v1/auth/login` with `{roll_number, password}` → `{access_token, refresh_token, user}`.
|
| 152 |
+
- Access token lifetime: `JWT_ACCESS_TOKEN_EXPIRE_MINUTES` (default 1440 = 24h).
|
| 153 |
+
- Every access token carries a `jti` (random UUID) baked into the JWT claims by [mac/utils/security.py](mac/utils/security.py).
|
| 154 |
+
- `POST /api/v1/auth/logout` blacklists the current `jti` in Redis with a TTL equal to remaining token life ([token_blacklist_service.py](mac/services/token_blacklist_service.py)). Refresh tokens are also revoked. Falls back to an in-process set if Redis is unreachable (dev only).
|
| 155 |
+
- The JWT signing secret is **not** read from env in production — it's stored in `system_config` and seeded on first boot by [setup_service.get_or_generate_jwt_secret](mac/services/setup_service.py). This means restarting the app does not invalidate everyone's sessions.
|
| 156 |
+
|
| 157 |
+
### 4.2 Legacy API keys
|
| 158 |
+
- Format: `mac_sk_live_<48 hex chars>`. Stored on `users.api_key`. One per user.
|
| 159 |
+
- Use case: scripts that need a stable long-lived credential.
|
| 160 |
+
- Resolved before JWT in `auth_middleware` because of the prefix check.
|
| 161 |
+
|
| 162 |
+
### 4.3 Scoped API keys
|
| 163 |
+
- Format: `mac_sk_<random>`, hashed at rest. Created via `/api/v1/scoped-keys`.
|
| 164 |
+
- Carry: scopes (list of allowed endpoints), optional expiry, label, revoke flag.
|
| 165 |
+
- Resolved by [scoped_key_service.get_key_by_hash](mac/services/scoped_key_service.py).
|
| 166 |
+
- Attached to `user._scoped_key` for downstream scope enforcement.
|
| 167 |
+
|
| 168 |
+
### 4.4 Roles
|
| 169 |
+
- `admin` | `faculty` | `student`. Enforced at the router layer via `require_admin` / `require_faculty_or_admin` dependencies.
|
| 170 |
+
- Feature flags layer on top: a feature can be enabled globally but restricted to specific roles (see `feature_flags.roles`).
|
| 171 |
+
|
| 172 |
+
### 4.5 First-run onboarding
|
| 173 |
+
- `GET /api/v1/setup/status` → `{is_first_run, has_jwt_secret, version}`. Frontend uses this to decide whether to show the setup wizard or login.
|
| 174 |
+
- `POST /api/v1/setup/create-admin` provisions the first admin and seals the system.
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## 5. LLM serving & cluster routing
|
| 179 |
+
|
| 180 |
+
### 5.1 Model registry — three layers of override
|
| 181 |
+
`mac/services/llm_service.py::_BUILTIN_MODELS` holds the defaults (Qwen2.5 7B, Qwen2.5-Coder 7B/AWQ, DeepSeek-R1, etc.). Each entry knows its `served_name` (HF repo), `category` (`speed | code | reasoning | intelligence`), `capabilities`, and `url_key` pointing at one of `vllm_speed_url | vllm_code_url | …` in `Settings`.
|
| 182 |
+
|
| 183 |
+
Override priority:
|
| 184 |
+
1. `MAC_MODELS_JSON` env var (a full JSON array) — replaces the registry entirely.
|
| 185 |
+
2. `MAC_ENABLED_MODELS` env var (comma-separated IDs) — filters which built-ins are exposed.
|
| 186 |
+
3. `MAC_AUTO_FALLBACK` — what `model="auto"` resolves to.
|
| 187 |
+
|
| 188 |
+
### 5.2 The system prompt is forced
|
| 189 |
+
`_inject_system_prompt` in `llm_service` prepends a hard-coded MAC identity prompt to **every** chat completion. This prevents the underlying Qwen/DeepSeek model from claiming to be "Qwen made by Alibaba" — it always says it is MAC, built by MBM University. If the user supplied a system message, MAC's identity is concatenated in front of theirs.
|
| 190 |
+
|
| 191 |
+
### 5.3 Routing decision (where does this call go?)
|
| 192 |
+
```
|
| 193 |
+
chat request
|
| 194 |
+
│
|
| 195 |
+
▼
|
| 196 |
+
llm_service._resolve_model_cluster(model_id)
|
| 197 |
+
│
|
| 198 |
+
▼
|
| 199 |
+
load_balancer.get_best_worker(db, model_id)
|
| 200 |
+
│ SELECT WorkerNode JOIN NodeModelDeployment
|
| 201 |
+
│ WHERE node.status='active' AND deployment.status='ready'
|
| 202 |
+
│ AND last_heartbeat within 30s
|
| 203 |
+
│ ORDER BY gpu_util*0.5 + (vram_used/total)*0.3
|
| 204 |
+
│
|
| 205 |
+
├── candidate found → POST http://{node.ip}:{deployment.port}/v1/chat/completions
|
| 206 |
+
│
|
| 207 |
+
└── none → fall back to local config (settings.vllm_<category>_url)
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
vLLM speaks the **OpenAI-compatible** API, so the proxy is a near-pass-through with SSE streaming preserved end-to-end.
|
| 211 |
+
|
| 212 |
+
### 5.4 Cluster lifecycle
|
| 213 |
+
| Event | Endpoint | Auth | Effect |
|
| 214 |
+
|---|---|---|---|
|
| 215 |
+
| Admin mints token | `POST /cluster/enroll-token` | admin JWT | Single-use, expiring `EnrollmentToken` row |
|
| 216 |
+
| Worker registers | `POST /cluster/register` | enroll token | Creates `WorkerNode` (status `pending`) + reports IP, GPU specs |
|
| 217 |
+
| Admin approves | `POST /cluster/nodes/{id}/action {action:"approve"}` | admin | `status → active` |
|
| 218 |
+
| Worker heartbeats | `POST /cluster/heartbeat` | node token | Updates `last_heartbeat`, GPU util, VRAM, CPU, RAM, queue depth — also append-only into `cluster_heartbeats` (time-series for charts) |
|
| 219 |
+
| Worker reports models | (in heartbeat payload) | — | Upserts `NodeModelDeployment` rows |
|
| 220 |
+
| Drain / remove | `POST /cluster/nodes/{id}/action` | admin | Stops new traffic; allows in-flight to finish |
|
| 221 |
+
|
| 222 |
+
Workers older than 30s without a heartbeat are silently skipped by the balancer — no manual intervention needed if a worker dies.
|
| 223 |
+
|
| 224 |
+
---
|
| 225 |
+
|
| 226 |
+
## 6. Notebooks — multi-language code execution
|
| 227 |
+
|
| 228 |
+
This is the most operationally complex subsystem. The design supports **two backends** and **distributed execution**.
|
| 229 |
+
|
| 230 |
+
### 6.1 Architecture
|
| 231 |
+
```
|
| 232 |
+
Client (browser)
|
| 233 |
+
│ WebSocket /ws/notebook/{notebook_id}?token=JWT
|
| 234 |
+
▼
|
| 235 |
+
mac/routers/notebook_ws.py
|
| 236 |
+
│ • verifies JWT (decode_access_token, no DB hit on hot path)
|
| 237 |
+
│ • registers connection in _connections[notebook_id]
|
| 238 |
+
▼
|
| 239 |
+
kernel_manager (mac/services/kernel_manager.py)
|
| 240 |
+
│ Backend selection at startup:
|
| 241 |
+
│ _docker_available() → Docker mode
|
| 242 |
+
│ else → subprocess mode (dev)
|
| 243 |
+
│
|
| 244 |
+
├── DOCKER MODE (production)
|
| 245 |
+
│ • spawns mac-kernel-{lang} container (image_prefix in config)
|
| 246 |
+
│ • applies memory + CPU limits from settings
|
| 247 |
+
│ • optionally attaches GPU (--gpus all) for ML kernels
|
| 248 |
+
│ • streams stdout/stderr back as JSONL events
|
| 249 |
+
│
|
| 250 |
+
├── SUBPROCESS MODE (dev)
|
| 251 |
+
│ • runs the language interpreter directly on the host
|
| 252 |
+
│ • no isolation; only safe for trusted local dev
|
| 253 |
+
│
|
| 254 |
+
└── REMOTE WORKER MODE
|
| 255 |
+
• load_balancer.get_notebook_worker(db) picks a worker with notebook_port
|
| 256 |
+
• forwards the execute via the worker's Jupyter kernel gateway
|
| 257 |
+
• output streams back to the master, then to the client
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
### 6.2 WebSocket protocol
|
| 261 |
+
Defined at the top of [notebook_ws.py](mac/routers/notebook_ws.py):
|
| 262 |
+
|
| 263 |
+
| Direction | Type | Payload |
|
| 264 |
+
|---|---|---|
|
| 265 |
+
| C→S | `execute` | `{cell_id, code, language}` |
|
| 266 |
+
| C→S | `interrupt` | `{kernel_id}` |
|
| 267 |
+
| C→S | `ping` | — |
|
| 268 |
+
| S→C | `status` | `{cell_id, execution_state: busy\|idle}` |
|
| 269 |
+
| S→C | `stream` | `{cell_id, name: stdout\|stderr, text}` |
|
| 270 |
+
| S→C | `error` | `{cell_id, ename, evalue, traceback[]}` |
|
| 271 |
+
| S→C | `pong` | — |
|
| 272 |
+
|
| 273 |
+
### 6.3 State & limits
|
| 274 |
+
- `KernelInstance` per session: `id`, `language`, `node_id`, `container_id`, `status`, `last_activity`, `execution_count`.
|
| 275 |
+
- Idle kernels are reaped after `kernel_timeout` seconds (default 120).
|
| 276 |
+
- Max concurrent kernels per node: `kernel_max_per_node` (default 10).
|
| 277 |
+
- Persistent notebook content: `notebooks` table; cells stored as JSON, ordered.
|
| 278 |
+
|
| 279 |
+
### 6.4 Why a custom protocol and not raw Jupyter?
|
| 280 |
+
Three reasons: (a) we need user-scoped auth via our JWT; (b) we need to fan-out execution across the cluster, not just one local kernel; (c) we want the option to swap kernels for sandboxed runners later without changing the wire format.
|
| 281 |
+
|
| 282 |
+
---
|
| 283 |
+
|
| 284 |
+
## 7. RAG — private document search
|
| 285 |
+
|
| 286 |
+
Pipeline: **upload → chunk → embed → store → retrieve → augment**.
|
| 287 |
+
|
| 288 |
+
```
|
| 289 |
+
PDF/MD/TXT upload (POST /rag/upload)
|
| 290 |
+
│
|
| 291 |
+
▼
|
| 292 |
+
rag_service.ingest_document
|
| 293 |
+
│ • text extraction (pypdf for PDF, plain read otherwise)
|
| 294 |
+
│ • chunk_text(words=512, overlap=50) ← simple word-window
|
| 295 |
+
│ • for each chunk:
|
| 296 |
+
│ emb = await llm_service.embed(text) ← uses EMBEDDING_URL or vLLM
|
| 297 |
+
│ qdrant.upsert(point=(uuid, emb, payload))
|
| 298 |
+
│ • RAGDocument row in Postgres with chunk count & status
|
| 299 |
+
▼
|
| 300 |
+
QUERY TIME (chat with rag context)
|
| 301 |
+
│
|
| 302 |
+
▼
|
| 303 |
+
rag_service.query(question, top_k=5)
|
| 304 |
+
│ • emb_q = embed(question)
|
| 305 |
+
│ • qdrant.search(collection, emb_q, top_k)
|
| 306 |
+
│ • returns chunks + source metadata
|
| 307 |
+
▼
|
| 308 |
+
llm_service.chat with messages = [
|
| 309 |
+
{role:"system", content: MAC_PROMPT + "\n\nContext:\n" + chunks},
|
| 310 |
+
*user_messages,
|
| 311 |
+
]
|
| 312 |
+
```
|
| 313 |
+
|
| 314 |
+
Collections (`RAGCollection`) namespace documents — e.g. one per subject. Documents (`RAGDocument`) track ownership and indexing status so the UI can show "Indexing 42/120 chunks…".
|
| 315 |
+
|
| 316 |
+
---
|
| 317 |
+
|
| 318 |
+
## 8. Attendance — face-based check-in
|
| 319 |
+
|
| 320 |
+
### 8.1 Models
|
| 321 |
+
- `FaceTemplate` — one per user, holds a face encoding (64-byte hash in dev; pluggable to `face_recognition`/`dlib` for production).
|
| 322 |
+
- `AttendanceSession` — created by faculty: `{branch, section, subject, date, window_minutes}`.
|
| 323 |
+
- `AttendanceRecord` — one per (session, student): `present | absent | late`, captured selfie hash, confidence, timestamp.
|
| 324 |
+
|
| 325 |
+
### 8.2 Flow
|
| 326 |
+
```
|
| 327 |
+
1. Faculty: POST /attendance/sessions → creates session, returns join token + QR
|
| 328 |
+
2. Student: GET /attendance/active → returns currently open sessions for them
|
| 329 |
+
3. Student: POST /attendance/check-in → uploads base64 selfie
|
| 330 |
+
server:
|
| 331 |
+
• decodes image
|
| 332 |
+
• hashes (sha256) — dedupe replay
|
| 333 |
+
• computes encoding
|
| 334 |
+
• compares to stored FaceTemplate
|
| 335 |
+
• if (match && within window) → AttendanceRecord(present)
|
| 336 |
+
• else → 401 with reason
|
| 337 |
+
4. Faculty: GET /attendance/sessions/{id}/report → CSV / PDF roster
|
| 338 |
+
```
|
| 339 |
+
|
| 340 |
+
### 8.3 Anti-cheat heuristics
|
| 341 |
+
- Session has a strict `window_minutes` — late arrivals are recorded as `late`, not `present`.
|
| 342 |
+
- Same selfie hash twice in a session → rejected (replay block).
|
| 343 |
+
- One record per (session, student) — UPSERT prevents stuffing.
|
| 344 |
+
- Production: swap `_compute_face_encoding` for the real `face_recognition.face_encodings()` (the call sites already accept it; only the function body changes).
|
| 345 |
+
|
| 346 |
+
---
|
| 347 |
+
|
| 348 |
+
## 9. Copy Check — exam paper evaluation
|
| 349 |
+
|
| 350 |
+
A faculty workflow that grades scanned answer sheets using vision-capable LLMs and runs cross-paper plagiarism detection. Models in `mac/models/copy_check.py`:
|
| 351 |
+
|
| 352 |
+
| Model | Role |
|
| 353 |
+
|---|---|
|
| 354 |
+
| `CopyCheckSession` | One exam: subject, class, total_marks, syllabus_text |
|
| 355 |
+
| `CopyCheckSheet` | One student's submission: roll, scanned pages, AI score, feedback |
|
| 356 |
+
| `CopyCheckPlagiarism` | Pairwise similarity between two sheets in the same session |
|
| 357 |
+
|
| 358 |
+
### 9.1 Flow
|
| 359 |
+
```
|
| 360 |
+
Faculty creates session → uploads syllabus / answer key
|
| 361 |
+
│
|
| 362 |
+
▼
|
| 363 |
+
For each student answer sheet (PDF or image bundle):
|
| 364 |
+
• file saved under uploads/copy_check/{session_id}/{roll}/
|
| 365 |
+
• AI vision model reads each page (multimodal LLM)
|
| 366 |
+
• Service builds a structured prompt: syllabus + answer key + student answer
|
| 367 |
+
• LLM returns { per_question_marks, total, weakness_summary, suggestions }
|
| 368 |
+
• CopyCheckSheet upserted with score + JSON feedback
|
| 369 |
+
│
|
| 370 |
+
▼
|
| 371 |
+
Plagiarism pass:
|
| 372 |
+
• difflib.SequenceMatcher on extracted text per pair within session
|
| 373 |
+
• CopyCheckPlagiarism row written for (sheet_a, sheet_b, similarity, flagged_passages)
|
| 374 |
+
│
|
| 375 |
+
▼
|
| 376 |
+
Faculty reviews:
|
| 377 |
+
• per-student PDF report (fpdf2)
|
| 378 |
+
• plagiarism heatmap
|
| 379 |
+
• can override AI marks before "publish"
|
| 380 |
+
```
|
| 381 |
+
|
| 382 |
+
### 9.2 Why the AI doesn't have final authority
|
| 383 |
+
The faculty UI explicitly requires a **"Reviewed & Approved"** flag before any score becomes visible to students. The AI is graded as a *recommendation* — the audit trail records both the AI suggestion and the faculty's override. This is the legal/academic-integrity boundary.
|
| 384 |
+
|
| 385 |
+
---
|
| 386 |
+
|
| 387 |
+
## 10. Other domain modules (one-paragraph each)
|
| 388 |
+
|
| 389 |
+
- **Doubts forum** ([doubts.py](mac/routers/doubts.py)): students post questions; faculty/peers answer; AI generates a draft answer that the asker can accept or replace. Threaded, taggable.
|
| 390 |
+
- **File sharing** ([file_share.py](mac/routers/file_share.py)): admin/faculty upload class materials; per-file access scoping; per-download analytics in `file_downloads`.
|
| 391 |
+
- **Notifications** ([notifications.py](mac/routers/notifications.py)): in-app + Web Push (`pywebpush`); endpoints registered via VAPID; one row per user-notification with read/unread state.
|
| 392 |
+
- **Academic** ([academic.py](mac/routers/academic.py)): branches & sections — used to scope attendance, file sharing, and admin lists.
|
| 393 |
+
- **Doubt copy-check submissions** ([model_submission_service.py](mac/services/model_submission_service.py)): community-trained adapter / LoRA submissions queued for admin review before being published as model registry entries.
|
| 394 |
+
- **Search** ([search.py](mac/routers/search.py) + SearXNG): private metasearch, no Google, no telemetry, returned to the chat as a tool result.
|
| 395 |
+
- **Hardware / Network / System** ([hardware.py](mac/routers/hardware.py), [network.py](mac/routers/network.py), [system.py](mac/routers/system.py)): admin diagnostics — local CPU/GPU/RAM, recommended models for the detected GPU, LAN discovery (`mac/services/discovery.py` UDP broadcast on port 7700), version & update status (`mac/services/updater.py` polls GitHub releases).
|
| 396 |
+
- **Quota** ([quota.py](mac/routers/quota.py)): per-user requests/hour and tokens/day; admin can override per user; default from `RATE_LIMIT_*` env.
|
| 397 |
+
- **Guardrails** ([guardrails.py](mac/routers/guardrails.py) + `guardrail_service`): admin-editable ruleset (banned terms, forbidden topics) applied as a pre-check on chat input and a post-check on model output.
|
| 398 |
+
|
| 399 |
+
---
|
| 400 |
+
|
| 401 |
+
## 11. Cross-cutting concerns
|
| 402 |
+
|
| 403 |
+
### 11.1 Configuration
|
| 404 |
+
**One source of truth:** [mac/config.py](mac/config.py) `Settings(BaseSettings)`. Every value reads from env or `.env`. `_fix_database_url` auto-promotes `postgres://` and `postgresql://` to `postgresql+asyncpg://` and strips `sslmode=` (it's handled in `connect_args` separately for Neon/Supabase). Adding a new tunable means: add a field to `Settings`, document it in `.env.example`, use `settings.your_field` everywhere — never read `os.environ` directly.
|
| 405 |
+
|
| 406 |
+
### 11.2 Migrations
|
| 407 |
+
Alembic-managed. Two revisions today:
|
| 408 |
+
- `20260426_0001_initial_schema.py` — full original schema.
|
| 409 |
+
- `20260427_0002_session1_tables.py` — feature flags, system_config, branches, sections, cluster_heartbeats, shared_files, file_downloads, video_projects, video_jobs.
|
| 410 |
+
|
| 411 |
+
In dev (`MAC_ENV=development`), `init_db()` in `lifespan` creates tables idempotently from `Base.metadata`. In prod, you **must** run `alembic upgrade head` before serving traffic; tables are not auto-created. Whenever you add a column to a model, write a new revision.
|
| 412 |
+
|
| 413 |
+
### 11.3 Background tasks
|
| 414 |
+
Started in `lifespan` and cancelled on shutdown:
|
| 415 |
+
- [updater.background_check_loop](mac/services/updater.py) — polls GitHub for new releases every `MAC_UPDATE_CHECK_INTERVAL_HOURS`.
|
| 416 |
+
- [discovery.start_discovery_server](mac/services/discovery.py) — UDP broadcast listener so worker PCs on the LAN can find the master without manual IP entry.
|
| 417 |
+
|
| 418 |
+
### 11.4 Caching, blacklisting, rate limits
|
| 419 |
+
All Redis-backed with **graceful in-process fallback**:
|
| 420 |
+
- JWT blacklist → `mac:bl:{jti}` keys with TTL = remaining token life.
|
| 421 |
+
- Rate-limit counters → derived from `usage_log` rows (no Redis needed for counts).
|
| 422 |
+
- Session/feature caches → not implemented yet; designed to live under `mac:cache:*`.
|
| 423 |
+
|
| 424 |
+
### 11.5 Observability
|
| 425 |
+
Every chat call is logged to `usage_log`: user_id, model_id, tokens_in, tokens_out, latency_ms, status, request_id (`generate_request_id` in `utils/security`). The dashboard route reads these for per-user charts. Cluster heartbeats are append-only into `cluster_heartbeats` so node history charts are just `SELECT … ORDER BY ts`.
|
| 426 |
+
|
| 427 |
+
---
|
| 428 |
+
|
| 429 |
+
## 12. Frontend — SvelteKit PWA
|
| 430 |
+
|
| 431 |
+
### 12.1 Stack
|
| 432 |
+
SvelteKit 2 + Svelte 5 + Tailwind 3 + Vite 6. Built as a static site (`@sveltejs/adapter-static` with `fallback: 'index.html'`) and served by Nginx in production, by Vite dev server with `/api` proxy to the FastAPI port in development.
|
| 433 |
+
|
| 434 |
+
### 12.2 SPA mode
|
| 435 |
+
The root has `+layout.js` with `export const ssr = false; export const prerender = false;` so the entire app is rendered client-side. This is intentional — it sidesteps hydration issues, and there is no SEO need for an internal college tool.
|
| 436 |
+
|
| 437 |
+
### 12.3 State
|
| 438 |
+
[src/lib/stores.js](frontend/src/lib/stores.js) holds Svelte stores:
|
| 439 |
+
- `authStore` — `{user, token, refreshToken}`, with `init()` that re-hydrates from `localStorage` and re-fetches `/auth/me`, plus `login`/`logout`.
|
| 440 |
+
- `setupStore` — `is_first_run` flag.
|
| 441 |
+
- `featureStore` — feature flag map for conditional UI.
|
| 442 |
+
- `chatStore` — local conversation history (per-session, not yet server-persisted).
|
| 443 |
+
- `toast` — single-message notifier.
|
| 444 |
+
|
| 445 |
+
### 12.4 API client
|
| 446 |
+
[src/lib/api.js](frontend/src/lib/api.js) is the *only* place that talks HTTP. One `headers()` helper attaches the bearer token from `localStorage`. Each backend domain (`auth`, `query`, `models`, `cluster`, `rag`, `files`, …) is its own export with named methods. Adding a new endpoint = add a method here, never `fetch()` from a component directly.
|
| 447 |
+
|
| 448 |
+
### 12.5 Auth/setup gate
|
| 449 |
+
[+layout.svelte](frontend/src/routes/+layout.svelte) boots the app on first paint:
|
| 450 |
+
1. `initLocale()` — detect language from `localStorage` / browser.
|
| 451 |
+
2. `authStore.init()` — restore session.
|
| 452 |
+
3. `checkSetup()` — first-run check.
|
| 453 |
+
4. `loadFeatures()` — fetch flags.
|
| 454 |
+
5. Redirect: first-run → `/setup`, no user on protected route → `/login`, root → `/chat` or `/login`.
|
| 455 |
+
6. Render either `Sidebar + slot` (logged in) or bare `slot` (login/setup).
|
| 456 |
+
|
| 457 |
+
### 12.6 Internationalisation
|
| 458 |
+
[src/lib/i18n.js](frontend/src/lib/i18n.js) ships **19 Indian languages** with lazy-loaded string maps and an `RTL_LOCALES` set (Urdu) that flips the layout direction. Adding a new locale = add to `SUPPORTED_LOCALES`, drop a translation map, no other file changes.
|
| 459 |
+
|
| 460 |
+
### 12.7 PWA + service worker
|
| 461 |
+
[static/manifest.json](frontend/static/manifest.json) declares the installable app + shortcuts. [static/sw.js](frontend/static/sw.js) is intentionally **caching-disabled** — every install/activate wipes all caches and there is no `fetch` handler. This was a deliberate decision: caching the SPA shell caused stale-build problems during rapid dev. Re-introduce caching only behind a versioned cache name with a clear invalidation strategy.
|
| 462 |
+
|
| 463 |
+
---
|
| 464 |
+
|
| 465 |
+
## 13. Deployment
|
| 466 |
+
|
| 467 |
+
### 13.1 Master node (single command)
|
| 468 |
+
```bash
|
| 469 |
+
cd frontend && npm install && npm run build && cd ..
|
| 470 |
+
cp .env.example .env # edit secrets
|
| 471 |
+
docker compose up postgres -d
|
| 472 |
+
docker compose run --rm mac alembic upgrade head
|
| 473 |
+
docker compose up -d
|
| 474 |
+
```
|
| 475 |
+
Compose brings up: `mac` (FastAPI), `postgres`, `redis`, `qdrant`, `searxng`, `vllm-speed`, `nginx`. (Whisper/TTS commented out by default.)
|
| 476 |
+
|
| 477 |
+
### 13.2 Adding a worker
|
| 478 |
+
On master:
|
| 479 |
+
```bash
|
| 480 |
+
curl -X POST http://MASTER:8000/api/v1/cluster/enroll-token \
|
| 481 |
+
-H "Authorization: Bearer ADMIN_JWT" -d '{"label":"Lab PC 1","expires_hours":24}'
|
| 482 |
+
```
|
| 483 |
+
On the worker PC:
|
| 484 |
+
```bash
|
| 485 |
+
MAC_MASTER_URL=http://MASTER:8000 \
|
| 486 |
+
MAC_ENROLL_TOKEN=<token> \
|
| 487 |
+
MAC_VLLM_PORT=8001 \
|
| 488 |
+
docker compose -f docker-compose.worker.yml up -d
|
| 489 |
+
```
|
| 490 |
+
Then approve in admin → Cluster.
|
| 491 |
+
|
| 492 |
+
### 13.3 HTTPS
|
| 493 |
+
Drop certs into `nginx/ssl/`, swap the bind-mounted config to `nginx/nginx.https.conf` in `docker-compose.yml`, restart Nginx.
|
| 494 |
+
|
| 495 |
+
### 13.4 Windows installer
|
| 496 |
+
[installer/build_installer.ps1](installer/build_installer.ps1) builds a one-shot `dist/MAC-Installer.exe` (PyInstaller) that bootstraps Docker Desktop checks, clones/updates the repo, writes a sane `.env` with detected host IP, and starts the master stack. Branding assets are embedded base64 in [installer/embedded_assets.py](installer/embedded_assets.py) so the binary works even if image files are missing at runtime.
|
| 497 |
+
|
| 498 |
+
---
|
| 499 |
+
|
| 500 |
+
## 14. Security checklist (what every reviewer should verify)
|
| 501 |
+
|
| 502 |
+
1. **No external API calls.** `grep -r "openai.com\|api.anthropic\|googleapis" mac/` should be empty. All inference is local.
|
| 503 |
+
2. **JWT secret is not in env in production.** It's seeded in `system_config` on first boot and re-used across restarts.
|
| 504 |
+
3. **JWT carries `jti`** and the auth middleware checks blacklist on every request.
|
| 505 |
+
4. **Every router** requiring auth uses `Depends(get_current_user)` — search for any `@router.*` that doesn't and justify it.
|
| 506 |
+
5. **Role guards** on admin-only operations: `Depends(require_admin)` on token mints, user list, cluster mutations, feature toggles, system restart.
|
| 507 |
+
6. **Rate limits** on user-facing inference endpoints (`/query/*`, `/rag/query`).
|
| 508 |
+
7. **Scoped keys** never logged in full; only the prefix is shown after creation.
|
| 509 |
+
8. **Worker enrollment tokens** are single-use and time-limited (`expires_at` checked on register).
|
| 510 |
+
9. **Heartbeats authenticate by `node_token`**, not by JWT — rotated on every approve/reactivate.
|
| 511 |
+
10. **CORS:** `MAC_CORS_ORIGINS` defaults to `["*"]` for ease of dev; **set explicit origins in prod**.
|
| 512 |
+
11. **Uploads:** `uploads/` is outside the static mount; copy-check sheets and RAG docs are served via authenticated endpoints, never directly.
|
| 513 |
+
12. **WebSocket auth:** `notebook_ws` validates the JWT in the query string before `accept()`. Don't move the accept above the validation.
|
| 514 |
+
|
| 515 |
+
---
|
| 516 |
+
|
| 517 |
+
## 15. How to add a new feature (the recipe)
|
| 518 |
+
|
| 519 |
+
1. **Model:** add a SQLAlchemy class in `mac/models/<domain>.py`, import it in `mac/main.py::lifespan` so `Base.metadata` knows.
|
| 520 |
+
2. **Migration:** `alembic revision --autogenerate -m "add <thing>"` → review → commit.
|
| 521 |
+
3. **Schema:** Pydantic request/response in `mac/schemas/<domain>.py`.
|
| 522 |
+
4. **Service:** pure logic in `mac/services/<domain>_service.py`. Takes `db: AsyncSession` and primitive args. No FastAPI types.
|
| 523 |
+
5. **Router:** thin handler in `mac/routers/<domain>.py`. Order of `Depends`: `get_db` → `get_current_user` → `require_*` → `feature_required("…")` → `check_rate_limit` (only if user-driven inference). Mount in `mac/main.py`.
|
| 524 |
+
6. **Feature flag:** add a default to `feature_seeder.DEFAULT_FLAGS` so it can be toggled per role from admin.
|
| 525 |
+
7. **API client:** add a method to `frontend/src/lib/api.js` under the matching export.
|
| 526 |
+
8. **Store (if it has UI state):** add to `frontend/src/lib/stores.js`.
|
| 527 |
+
9. **Route:** new directory under `frontend/src/routes/<feature>/+page.svelte`.
|
| 528 |
+
10. **Sidebar entry:** edit `frontend/src/lib/components/Sidebar.svelte`.
|
| 529 |
+
11. **i18n:** add new strings to `BASE` in `frontend/src/lib/i18n.js`.
|
| 530 |
+
12. **Test:** at least one happy-path + one auth-failure pytest in `tests/`.
|
| 531 |
+
|
| 532 |
+
Follow this and the system stays consistent. Skip steps and you'll end up with a feature that's invisible to the admin, untranslated, untested, or worse — bypassing the auth chain.
|
| 533 |
+
|
| 534 |
+
---
|
| 535 |
+
|
| 536 |
+
*Last updated: 2026-04-27. If you change a subsystem and this file no longer matches reality, update it in the same PR.*
|
docs/MAC-CONTEXT.md
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MAC — Full Agent Context File
|
| 2 |
+
> Generated: 2026-04-28
|
| 3 |
+
> Sources: Claude (session knowledge), GitHub Copilot / Antigravity (VS Code agent), VS Code workspace
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. VS Code / Copilot Agent Session Info (Antigravity)
|
| 8 |
+
|
| 9 |
+
| Variable | Value |
|
| 10 |
+
|---|---|
|
| 11 |
+
| `ANTIGRAVITY_AGENT` | `github.copilot-chat` |
|
| 12 |
+
| `ANTIGRAVITY_EDITOR_APP_ROOT` | VS Code (Windows) |
|
| 13 |
+
| `ANTIGRAVITY_TRAJECTORY_ID` | `e36c8c56-d0d8-4913-8da2-90176f0c34d3` |
|
| 14 |
+
| `VSCODE_TARGET_SESSION_LOG` | `c:\Users\rampy\AppData\Roaming\Code\User\workspaceStorage\26393181f28fefe9ec94c456e08b07ec\GitHub.copilot-chat\debug-logs\e36c8c56-d0d8-4913-8da2-90176f0c34d3` |
|
| 15 |
+
| `VSCODE_USER_PROMPTS_FOLDER` | `c:\Users\rampy\AppData\Roaming\Code\User\prompts` |
|
| 16 |
+
| Workspace root | `D:\MAC` |
|
| 17 |
+
| OS | Windows |
|
| 18 |
+
| Date | 2026-04-28 |
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 2. Project Identity
|
| 23 |
+
|
| 24 |
+
**MAC** = MBM AI Cloud
|
| 25 |
+
**Owner:** MBM University Jodhpur (internal/institutional)
|
| 26 |
+
**Purpose:** Self-hosted, on-prem AI platform — private ChatGPT-style chat, notebook IDE, RAG over college docs, attendance with face capture, exam copy-check with AI vision + plagiarism detection, and admin/cluster console — all running on the college's own GPUs via vLLM. **No external API calls.**
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## 3. Stack
|
| 31 |
+
|
| 32 |
+
| Layer | Technology |
|
| 33 |
+
|---|---|
|
| 34 |
+
| Backend API | FastAPI 0.115 (Python 3.11+) |
|
| 35 |
+
| Database | PostgreSQL 16 + Alembic migrations |
|
| 36 |
+
| Cache / Blacklist / RL | Redis |
|
| 37 |
+
| Vector DB (RAG) | Qdrant |
|
| 38 |
+
| Web search | SearXNG |
|
| 39 |
+
| LLM inference | vLLM (OpenAI-compatible) |
|
| 40 |
+
| Frontend | SvelteKit 2 + Svelte 5 + Tailwind 3 + Vite 6 (PWA) |
|
| 41 |
+
| Reverse proxy | Nginx |
|
| 42 |
+
| Containerisation | Docker Compose |
|
| 43 |
+
| Installer | PyInstaller (Windows) |
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## 4. Top-Level Topology
|
| 48 |
+
|
| 49 |
+
```
|
| 50 |
+
CLIENTS (Web PWA / API consumers)
|
| 51 |
+
│ HTTPS
|
| 52 |
+
▼
|
| 53 |
+
NGINX ← TLS, gzip, /api → mac, / → static SPA
|
| 54 |
+
│
|
| 55 |
+
├── SvelteKit static build
|
| 56 |
+
│
|
| 57 |
+
└── FastAPI /api/v1/*
|
| 58 |
+
│
|
| 59 |
+
┌──────────┼──────────┬─────────────────┐
|
| 60 |
+
▼ ▼ ▼ ▼
|
| 61 |
+
PostgreSQL Redis Qdrant SearXNG
|
| 62 |
+
(primary) (cache/bl/rl) (RAG vectors) (web search)
|
| 63 |
+
│
|
| 64 |
+
│ load_balancer.get_best_worker()
|
| 65 |
+
▼
|
| 66 |
+
MAC CLUSTER (GPU worker nodes on LAN)
|
| 67 |
+
├── vLLM (OpenAI-compatible inference)
|
| 68 |
+
├── Jupyter kernel gateway (optional)
|
| 69 |
+
└── worker_agent.py (heartbeat every 10s)
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
- **Master node:** FastAPI + Postgres + Redis + Nginx + Qdrant + SearXNG
|
| 73 |
+
- **Worker nodes:** vLLM + optional Jupyter gateway + `worker_agent.py`
|
| 74 |
+
- **Routing:** master-side; `load_balancer.py` scores workers by `gpu_util×0.5 + vram_ratio×0.3`; stale threshold = 30 s
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## 5. Repository Map
|
| 79 |
+
|
| 80 |
+
```
|
| 81 |
+
mac/
|
| 82 |
+
main.py FastAPI app, lifespan (DB init, dev seeds, bg tasks),
|
| 83 |
+
router mounts under /api/v1, root SPA fallback
|
| 84 |
+
config.py Pydantic Settings — every env var + .env loader
|
| 85 |
+
database.py Async SQLAlchemy engine + session factory; Base
|
| 86 |
+
utils/security.py JWT encode/decode + jti generation; password hash
|
| 87 |
+
middleware/
|
| 88 |
+
auth_middleware.py Bearer → JWT | legacy-key | scoped-key → User
|
| 89 |
+
rate_limit.py Per-user req/hour + token/day; X-RateLimit-* headers
|
| 90 |
+
feature_gate.py feature_required("ai_chat") dependency
|
| 91 |
+
models/ SQLAlchemy ORM models (one file per domain)
|
| 92 |
+
schemas/ Pydantic request/response schemas
|
| 93 |
+
services/ Pure business logic, no HTTP — called by routers
|
| 94 |
+
routers/ FastAPI routers: validate → call service → return
|
| 95 |
+
|
| 96 |
+
frontend/
|
| 97 |
+
src/routes/ File-system routing: login, setup, chat, dashboard,
|
| 98 |
+
admin, cluster, keys, settings, notifications, rag
|
| 99 |
+
src/lib/api.js Single fetch wrapper; one export per backend domain
|
| 100 |
+
src/lib/stores.js Svelte stores (auth, setup, features, chat, toast)
|
| 101 |
+
src/lib/i18n.js 19 Indian languages, lazy-loaded strings, RTL support
|
| 102 |
+
static/manifest.json PWA manifest
|
| 103 |
+
static/sw.js No-cache service worker (intentional)
|
| 104 |
+
|
| 105 |
+
alembic/ Migration env + versioned revisions
|
| 106 |
+
nginx/ nginx.conf (HTTP) + nginx.https.conf (TLS)
|
| 107 |
+
docker-compose.yml Master stack
|
| 108 |
+
docker-compose.worker.yml Worker stack (vLLM + worker_agent)
|
| 109 |
+
worker_agent.py Enrollment + heartbeat agent for GPU nodes
|
| 110 |
+
installer/ Windows installer (PyInstaller) + branding
|
| 111 |
+
tests/ pytest suite
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## 6. Request Lifecycle (every /api/v1/* call)
|
| 117 |
+
|
| 118 |
+
```
|
| 119 |
+
HTTP request
|
| 120 |
+
[1] CORS middleware
|
| 121 |
+
[2] FastAPI route handler
|
| 122 |
+
[3] Auth resolver (auth_middleware.py)
|
| 123 |
+
mac_sk_live_* → legacy API key
|
| 124 |
+
mac_sk_* → scoped API key (hashed, scopes, expiry)
|
| 125 |
+
else → JWT (verify sig, exp, jti blacklist)
|
| 126 |
+
[4] Role guard require_admin / require_faculty_or_admin
|
| 127 |
+
[5] Feature gate feature_required("ai_chat") — 403 if disabled
|
| 128 |
+
[6] Rate limit req/hour + tokens/day from usage_log
|
| 129 |
+
[7] Service layer business logic (no FastAPI types)
|
| 130 |
+
[8] Response inject_rate_limit_headers stamps X-RateLimit-*
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
**Design rule:** routers = parsing + auth + I/O orchestration; services = business logic; models = persistence.
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## 7. Auth & Identity
|
| 138 |
+
|
| 139 |
+
### Three auth paths (all collapse to a `User`):
|
| 140 |
+
1. **JWT** — login → `{access_token (jti claim), refresh_token}`. Secret stored in `system_config` (not env). Logout blacklists `jti` in Redis with TTL = remaining life.
|
| 141 |
+
2. **Legacy API key** — `mac_sk_live_<48 hex>`. One per user. Checked first by prefix.
|
| 142 |
+
3. **Scoped API key** — `mac_sk_<random>`, hashed at rest. Has scopes, optional expiry, label.
|
| 143 |
+
|
| 144 |
+
### Roles: `admin | faculty | student`
|
| 145 |
+
|
| 146 |
+
### First-run onboarding:
|
| 147 |
+
- `GET /api/v1/setup/status` → `{is_first_run, has_jwt_secret, version}`
|
| 148 |
+
- `POST /api/v1/setup/create-admin` → provisions first admin, seals system
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## 8. LLM Serving & Cluster Routing
|
| 153 |
+
|
| 154 |
+
### Model registry (three override layers):
|
| 155 |
+
1. `MAC_MODELS_JSON` env → replaces entire registry
|
| 156 |
+
2. `MAC_ENABLED_MODELS` env → filters built-ins
|
| 157 |
+
3. `MAC_AUTO_FALLBACK` → what `model="auto"` resolves to
|
| 158 |
+
|
| 159 |
+
Built-in models: Qwen2.5 7B, Qwen2.5-Coder 7B/AWQ, DeepSeek-R1, etc.
|
| 160 |
+
Categories: `speed | code | reasoning | intelligence`
|
| 161 |
+
|
| 162 |
+
### System prompt is forced:
|
| 163 |
+
`_inject_system_prompt` prepends a hard-coded MAC identity to **every** completion. Model always presents itself as MAC by MBM University, never as Qwen/DeepSeek.
|
| 164 |
+
|
| 165 |
+
### Routing flow:
|
| 166 |
+
```
|
| 167 |
+
chat request
|
| 168 |
+
→ llm_service._resolve_model_cluster(model_id)
|
| 169 |
+
→ load_balancer.get_best_worker(db, model_id)
|
| 170 |
+
SELECT WorkerNode JOIN NodeModelDeployment
|
| 171 |
+
WHERE status='active' AND last_heartbeat within 30s
|
| 172 |
+
ORDER BY gpu_util*0.5 + vram_ratio*0.3
|
| 173 |
+
→ POST http://{node.ip}:{port}/v1/chat/completions (SSE passthrough)
|
| 174 |
+
→ fallback to local vLLM if no workers
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
### Cluster lifecycle:
|
| 178 |
+
| Event | Endpoint | Auth |
|
| 179 |
+
|---|---|---|
|
| 180 |
+
| Admin mints token | `POST /cluster/enroll-token` | admin JWT |
|
| 181 |
+
| Worker registers | `POST /cluster/register` | enroll token |
|
| 182 |
+
| Admin approves | `POST /cluster/nodes/{id}/action` | admin JWT |
|
| 183 |
+
| Worker heartbeats | `POST /cluster/heartbeat` | node token |
|
| 184 |
+
| Drain/remove | `POST /cluster/nodes/{id}/action` | admin JWT |
|
| 185 |
+
|
| 186 |
+
Workers > 30 s without heartbeat are silently skipped by balancer.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## 9. Notebooks — Multi-language Code Execution
|
| 191 |
+
|
| 192 |
+
### Architecture:
|
| 193 |
+
```
|
| 194 |
+
Browser WebSocket /ws/notebook/{id}?token=JWT
|
| 195 |
+
→ notebook_ws.py (JWT verified before accept())
|
| 196 |
+
→ kernel_manager.py
|
| 197 |
+
Docker mode (prod): mac-kernel-{lang} container, memory+CPU limits, optional GPU
|
| 198 |
+
Subprocess mode (dev): direct interpreter on host
|
| 199 |
+
Remote worker mode: forwards to Jupyter kernel gateway on worker node
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### WebSocket protocol:
|
| 203 |
+
| Direction | Type | Payload |
|
| 204 |
+
|---|---|---|
|
| 205 |
+
| C→S | `execute` | `{cell_id, code, language}` |
|
| 206 |
+
| C→S | `interrupt` | `{kernel_id}` |
|
| 207 |
+
| S→C | `stream` | `{cell_id, name: stdout\|stderr, text}` |
|
| 208 |
+
| S→C | `error` | `{cell_id, ename, evalue, traceback[]}` |
|
| 209 |
+
| S→C | `status` | `{cell_id, execution_state: busy\|idle}` |
|
| 210 |
+
|
| 211 |
+
Idle kernels reaped after `kernel_timeout` s (default 120). Max 10 kernels/node.
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## 10. RAG — Private Document Search
|
| 216 |
+
|
| 217 |
+
```
|
| 218 |
+
Upload (PDF/MD/TXT)
|
| 219 |
+
→ text extraction (pypdf / plain read)
|
| 220 |
+
→ chunk_text(words=512, overlap=50)
|
| 221 |
+
→ embed each chunk via llm_service.embed()
|
| 222 |
+
→ qdrant.upsert(point=(uuid, embedding, payload))
|
| 223 |
+
→ RAGDocument row in Postgres
|
| 224 |
+
|
| 225 |
+
Query time:
|
| 226 |
+
→ embed(question) → qdrant.search(top_k=5)
|
| 227 |
+
→ inject chunks as context into system message
|
| 228 |
+
→ LLM responds with augmented answer
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
`RAGCollection` namespaces documents (e.g., one per subject). `RAGDocument` tracks chunk count + indexing status.
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## 11. Attendance — Face-based Check-in
|
| 236 |
+
|
| 237 |
+
1. Faculty creates `AttendanceSession` → `{branch, section, subject, date, window_minutes}`
|
| 238 |
+
2. Student POSTs base64 selfie to `/attendance/check-in`
|
| 239 |
+
3. Server: decodes → sha256 (replay block) → encoding → compare vs `FaceTemplate` → record `present/late`
|
| 240 |
+
4. Faculty exports CSV/PDF roster
|
| 241 |
+
|
| 242 |
+
Anti-cheat: strict window, replay hash block, one record per (session, student).
|
| 243 |
+
|
| 244 |
+
---
|
| 245 |
+
|
| 246 |
+
## 12. Copy Check — Exam Grading
|
| 247 |
+
|
| 248 |
+
```
|
| 249 |
+
Faculty creates session (syllabus + answer key)
|
| 250 |
+
→ uploads student answer sheets (PDF / image)
|
| 251 |
+
→ AI vision LLM reads each page
|
| 252 |
+
→ returns {per_question_marks, total, feedback}
|
| 253 |
+
→ difflib plagiarism pass across sheets in same session
|
| 254 |
+
→ Faculty reviews, overrides if needed, approves before publish
|
| 255 |
+
```
|
| 256 |
+
|
| 257 |
+
Models: `CopyCheckSession`, `CopyCheckSheet`, `CopyCheckPlagiarism`
|
| 258 |
+
AI score is a **recommendation** — requires faculty "Reviewed & Approved" flag before students see it.
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
## 13. Other Domain Modules
|
| 263 |
+
|
| 264 |
+
| Module | Description |
|
| 265 |
+
|---|---|
|
| 266 |
+
| **Doubts forum** | Students post questions; AI drafts answer; faculty/peers reply |
|
| 267 |
+
| **File sharing** | Admin/faculty upload class materials; per-download analytics |
|
| 268 |
+
| **Notifications** | In-app + Web Push (VAPID/pywebpush); read/unread state |
|
| 269 |
+
| **Academic** | Branches & sections — scopes attendance, file sharing, admin lists |
|
| 270 |
+
| **Search** | SearXNG private metasearch; returned as tool result to chat |
|
| 271 |
+
| **Hardware/Network/System** | Admin diagnostics, LAN discovery (UDP port 7700), version/update polling |
|
| 272 |
+
| **Quota** | Per-user req/hour + tokens/day; admin override per user |
|
| 273 |
+
| **Guardrails** | Admin-editable banned terms / forbidden topics; pre + post chat check |
|
| 274 |
+
| **Video** | `VideoProject`/`VideoJob` models exist; router not yet built |
|
| 275 |
+
|
| 276 |
+
---
|
| 277 |
+
|
| 278 |
+
## 14. Frontend (SvelteKit PWA)
|
| 279 |
+
|
| 280 |
+
- **Build:** `@sveltejs/adapter-static` → `fallback: 'index.html'` → pure CSR, no SSR
|
| 281 |
+
- **State:** `authStore`, `chatStore`, `setupStore`, `featureStore`, `toast` in `stores.js`
|
| 282 |
+
- **API client:** `src/lib/api.js` — only place that calls `fetch()`; one export per backend domain
|
| 283 |
+
- **Auth gate:** `+layout.svelte` boots: `initLocale → authStore.init → checkSetup → loadFeatures → redirect`
|
| 284 |
+
- **i18n:** 19 Indian languages, lazy-loaded, RTL support (Urdu)
|
| 285 |
+
- **Service worker:** intentionally no caching — avoids stale-build problems during rapid dev
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
## 15. Alembic Migrations
|
| 290 |
+
|
| 291 |
+
| Revision | Contents |
|
| 292 |
+
|---|---|
|
| 293 |
+
| `20260426_0001_initial_schema.py` | Full original schema |
|
| 294 |
+
| `20260427_0002_session1_tables.py` | feature_flags, system_config, branches, sections, cluster_heartbeats, shared_files, file_downloads, video_projects, video_jobs |
|
| 295 |
+
| `20260427_0003_file_share_node_columns.py` | node notebook_port and tags columns |
|
| 296 |
+
|
| 297 |
+
Dev: `MAC_ENV=development` → `init_db()` auto-creates tables.
|
| 298 |
+
Prod: **must** run `alembic upgrade head` before starting; no auto-create.
|
| 299 |
+
|
| 300 |
+
---
|
| 301 |
+
|
| 302 |
+
## 16. Configuration (mac/config.py)
|
| 303 |
+
|
| 304 |
+
All env vars via Pydantic `Settings(BaseSettings)`. Never read `os.environ` directly.
|
| 305 |
+
`_fix_database_url` auto-promotes `postgres://` → `postgresql+asyncpg://`.
|
| 306 |
+
JWT secret: NOT from env in prod — generated once on first boot, stored in `system_config`.
|
| 307 |
+
|
| 308 |
+
Key env vars:
|
| 309 |
+
```
|
| 310 |
+
DATABASE_URL, REDIS_URL, QDRANT_URL, SEARXNG_URL
|
| 311 |
+
MAC_CORS_ORIGINS (default ["*"] — set explicit origins in prod!)
|
| 312 |
+
MAC_MODELS_JSON, MAC_ENABLED_MODELS, MAC_AUTO_FALLBACK
|
| 313 |
+
JWT_ACCESS_TOKEN_EXPIRE_MINUTES (default 1440 = 24h)
|
| 314 |
+
RATE_LIMIT_REQUESTS_PER_HOUR, RATE_LIMIT_TOKENS_PER_DAY
|
| 315 |
+
MAC_ENV (development | production)
|
| 316 |
+
MAC_UPDATE_CHECK_INTERVAL_HOURS
|
| 317 |
+
```
|
| 318 |
+
|
| 319 |
+
---
|
| 320 |
+
|
| 321 |
+
## 17. Security Checklist
|
| 322 |
+
|
| 323 |
+
1. No external API calls — all inference is local vLLM
|
| 324 |
+
2. JWT secret in `system_config`, not env in production
|
| 325 |
+
3. Every JWT carries `jti`; middleware checks Redis blacklist on every request
|
| 326 |
+
4. Every auth-required router has `Depends(get_current_user)`
|
| 327 |
+
5. Role guards (`require_admin`) on token mints, cluster mutations, feature toggles, system restart
|
| 328 |
+
6. Rate limits on `/query/*` and `/rag/query`
|
| 329 |
+
7. Scoped keys never logged in full — only prefix shown post-creation
|
| 330 |
+
8. Worker enrollment tokens: single-use + time-limited
|
| 331 |
+
9. Heartbeats authenticate via `node_token` (not JWT), rotated on approve/reactivate
|
| 332 |
+
10. CORS: set explicit origins in prod (`MAC_CORS_ORIGINS`)
|
| 333 |
+
11. `uploads/` outside static mount; served via authenticated endpoints only
|
| 334 |
+
12. WebSocket: JWT validated **before** `accept()` in `notebook_ws`
|
| 335 |
+
|
| 336 |
+
---
|
| 337 |
+
|
| 338 |
+
## 18. Cross-Cutting Concerns
|
| 339 |
+
|
| 340 |
+
### Background tasks (started in lifespan):
|
| 341 |
+
- `updater.background_check_loop` — polls GitHub for new releases every N hours
|
| 342 |
+
- `discovery.start_discovery_server` — UDP broadcast on port 7700 for LAN worker discovery
|
| 343 |
+
|
| 344 |
+
### Redis usage:
|
| 345 |
+
- JWT blacklist: `mac:bl:{jti}` keys with TTL
|
| 346 |
+
- Rate-limit counters: derived from `usage_log` rows
|
| 347 |
+
- Graceful in-process fallback when Redis unreachable (dev only)
|
| 348 |
+
|
| 349 |
+
### Observability:
|
| 350 |
+
- Every chat call logged to `usage_log`: user_id, model_id, tokens_in, tokens_out, latency_ms, status, request_id
|
| 351 |
+
- Cluster heartbeats append-only in `cluster_heartbeats` → used for node history charts
|
| 352 |
+
|
| 353 |
+
---
|
| 354 |
+
|
| 355 |
+
## 19. Deployment Quick-Start
|
| 356 |
+
|
| 357 |
+
### Master node:
|
| 358 |
+
```bash
|
| 359 |
+
cd frontend && npm install && npm run build && cd ..
|
| 360 |
+
cp .env.example .env # edit DB, Redis, model settings
|
| 361 |
+
docker compose up postgres -d
|
| 362 |
+
docker compose run --rm mac alembic upgrade head
|
| 363 |
+
docker compose up -d
|
| 364 |
+
```
|
| 365 |
+
|
| 366 |
+
### Worker node:
|
| 367 |
+
```bash
|
| 368 |
+
# On master — mint enrollment token
|
| 369 |
+
curl -X POST http://MASTER:8000/api/v1/cluster/enroll-token \
|
| 370 |
+
-H "Authorization: Bearer ADMIN_JWT" \
|
| 371 |
+
-d '{"label":"Lab PC 1","expires_hours":24}'
|
| 372 |
+
|
| 373 |
+
# On worker PC
|
| 374 |
+
MAC_MASTER_URL=http://MASTER:8000 \
|
| 375 |
+
MAC_ENROLL_TOKEN=<token> \
|
| 376 |
+
MAC_VLLM_PORT=8001 \
|
| 377 |
+
docker compose -f docker-compose.worker.yml up -d
|
| 378 |
+
# Then: approve in MAC admin → Cluster tab
|
| 379 |
+
```
|
| 380 |
+
|
| 381 |
+
### HTTPS:
|
| 382 |
+
Drop certs into `nginx/ssl/`, swap bind-mount to `nginx/nginx.https.conf`, restart Nginx.
|
| 383 |
+
|
| 384 |
+
### Windows installer:
|
| 385 |
+
```powershell
|
| 386 |
+
powershell -ExecutionPolicy Bypass -File .\installer\build_installer.ps1
|
| 387 |
+
# → dist/MAC-Installer.exe
|
| 388 |
+
```
|
| 389 |
+
|
| 390 |
+
### Tests:
|
| 391 |
+
```bash
|
| 392 |
+
pytest # full suite
|
| 393 |
+
pytest -k "not gpu" # CPU-safe subset
|
| 394 |
+
```
|
| 395 |
+
|
| 396 |
+
---
|
| 397 |
+
|
| 398 |
+
## 20. How to Add a New Feature (the Recipe)
|
| 399 |
+
|
| 400 |
+
1. **Model:** SQLAlchemy class in `mac/models/<domain>.py`; import in `main.py::lifespan`
|
| 401 |
+
2. **Migration:** `alembic revision --autogenerate -m "add <thing>"` → review → commit
|
| 402 |
+
3. **Schema:** Pydantic in `mac/schemas/<domain>.py`
|
| 403 |
+
4. **Service:** pure logic in `mac/services/<domain>_service.py`; takes `db: AsyncSession`
|
| 404 |
+
5. **Router:** thin handler in `mac/routers/<domain>.py`; mount in `mac/main.py`
|
| 405 |
+
6. **Feature flag:** add default to `feature_seeder.DEFAULT_FLAGS`
|
| 406 |
+
7. **API client:** add method to `frontend/src/lib/api.js`
|
| 407 |
+
8. **Store (if UI state):** add to `frontend/src/lib/stores.js`
|
| 408 |
+
9. **Route:** `frontend/src/routes/<feature>/+page.svelte`
|
| 409 |
+
10. **Sidebar:** edit `frontend/src/lib/components/Sidebar.svelte`
|
| 410 |
+
11. **i18n:** add strings to `BASE` in `frontend/src/lib/i18n.js`
|
| 411 |
+
12. **Test:** happy-path + auth-failure pytest in `tests/`
|
| 412 |
+
|
| 413 |
+
---
|
| 414 |
+
|
| 415 |
+
## 21. Build Progress Summary (as of 2026-04-27)
|
| 416 |
+
|
| 417 |
+
### Completed:
|
| 418 |
+
- ✅ Session 1 — Full backend foundation (DB, migrations, auth, JWT blacklist, scoped keys, feature flags, system_config, setup wizard, cluster, file_share, academic, hardware, network, system routers + services)
|
| 419 |
+
- ✅ Session 2 — Full SvelteKit PWA (all routes: login, setup, chat, dashboard, admin, cluster, keys, settings, notifications, rag), design system, API client, i18n (19 languages), PWA manifest + service worker, Nginx configs (HTTP + HTTPS), Docker Compose (master + worker), `worker_agent.py`
|
| 420 |
+
|
| 421 |
+
### Remaining / Optional:
|
| 422 |
+
| Item | Priority |
|
| 423 |
+
|---|---|
|
| 424 |
+
| Frontend PWA icons (icon-192.png, icon-512.png, favicon.ico) | Medium |
|
| 425 |
+
| Frontend: silent JWT refresh token flow in `api.js` | Medium |
|
| 426 |
+
| Multi-stage Dockerfile (node build + python + nginx) | Low |
|
| 427 |
+
| Feature flag wiring on `/query/*` routes | Low |
|
| 428 |
+
| HTTPS cert setup for production | Deployment |
|
| 429 |
+
| Video generation router (models exist) | Future |
|
| 430 |
+
|
| 431 |
+
---
|
| 432 |
+
|
| 433 |
+
---
|
| 434 |
+
|
| 435 |
+
## 22. Project Origin — Vision & Architecture Requirements
|
| 436 |
+
|
| 437 |
+
The project was born from this exact goal:
|
| 438 |
+
|
| 439 |
+
> "I am building a multi-node GPU cluster using standard PCs over a LAN (connected via Wi-Fi) to provide offline AI services. The goal is to make the entire cluster accessible through one single IP address that provides both an AI Chat interface (Svelte-based) and a Kaggle-like environment for Python notebooks. Additionally, I need to issue custom OpenAI-compatible API keys to students so they can access these models from anywhere in the world."
|
| 440 |
+
|
| 441 |
+
### Original architecture requirements:
|
| 442 |
+
- **Hardware:** Multiple PCs each with dedicated GPUs. Wi-Fi connected → each PC runs its own model instance (no network model-sharding — latency would kill it)
|
| 443 |
+
- **One IP entry point:** Nginx reverse proxy on Master PC routing `/chat` → local vLLM, `/notebook` → JupyterHub/GPUSTACK on another PC
|
| 444 |
+
- **API Management:** LiteLLM for student API key management, usage limits, OpenAI-compatible endpoint
|
| 445 |
+
- **Global Access:** Cloudflare Tunnel to expose master IP publicly without opening router ports
|
| 446 |
+
- **UI:** Svelte dashboard as primary interface
|
| 447 |
+
|
| 448 |
+
### Evolution:
|
| 449 |
+
MAC replaced LiteLLM + JupyterHub with a fully custom FastAPI + SvelteKit stack, giving complete control over auth, roles, feature flags, quota, and cluster routing — while preserving the OpenAI-compatible API surface via vLLM.
|
| 450 |
+
|
| 451 |
+
---
|
| 452 |
+
|
| 453 |
+
## 23. Session Work Log — What Claude Built (Session 2 Detailed)
|
| 454 |
+
|
| 455 |
+
### Phase 1 — JWT Blacklist
|
| 456 |
+
|
| 457 |
+
**Goal:** Prevent token reuse after logout.
|
| 458 |
+
|
| 459 |
+
Steps taken:
|
| 460 |
+
1. Added `jti` (UUID) claim to every access token in `mac/utils/security.py`
|
| 461 |
+
2. Updated `auth_middleware.py` to check `mac:bl:{jti}` in Redis on every request
|
| 462 |
+
3. Updated `mac/routers/auth.py` logout endpoint to:
|
| 463 |
+
- Blacklist current `jti` in Redis with TTL = remaining token life
|
| 464 |
+
- Revoke all refresh tokens for the user
|
| 465 |
+
4. Fallback to in-process set if Redis unreachable (dev only)
|
| 466 |
+
|
| 467 |
+
### Phase 2 — Distributed Computing Core
|
| 468 |
+
|
| 469 |
+
**Foundation:** `WorkerNode`, `NodeModelDeployment`, `EnrollmentToken` models were already solid. Built on top.
|
| 470 |
+
|
| 471 |
+
Steps taken:
|
| 472 |
+
1. Added `notebook_port` and `tags` columns to `WorkerNode` model
|
| 473 |
+
2. Created `mac/services/load_balancer.py` — score-based routing:
|
| 474 |
+
- `SELECT WorkerNode JOIN NodeModelDeployment WHERE status='active' AND last_heartbeat within 30s`
|
| 475 |
+
- `ORDER BY gpu_util*0.5 + vram_ratio*0.3`
|
| 476 |
+
- Returns best worker or `None` (triggers local vLLM fallback)
|
| 477 |
+
3. Updated `mac/services/llm_service.py` → `_resolve_model_cluster` now calls `get_best_worker()` before falling back to local config
|
| 478 |
+
4. Created full `mac/routers/cluster.py` with all endpoints (enroll-token, register, heartbeat, node CRUD, deploy, history)
|
| 479 |
+
5. Created `worker_agent.py` — standalone Python script for worker PCs:
|
| 480 |
+
- Reads `MAC_MASTER_URL`, `MAC_ENROLL_TOKEN`, `MAC_VLLM_PORT` from env
|
| 481 |
+
- Self-registers on startup via enrollment token
|
| 482 |
+
- Sends heartbeats every 10s with GPU/CPU/RAM metrics (`pynvml` + `psutil`)
|
| 483 |
+
- Queries local vLLM `/v1/models` to report active models
|
| 484 |
+
- Handles stale/auth errors gracefully
|
| 485 |
+
|
| 486 |
+
### Phase 3 — Academic + File Share Routers
|
| 487 |
+
|
| 488 |
+
- Created `mac/routers/academic.py` — full CRUD for branches and sections
|
| 489 |
+
- Created `mac/routers/file_share.py` — admin upload, user download, download stats, delete
|
| 490 |
+
- Created `alembic/versions/20260427_0003_file_share_node_columns.py` — fixes mismatched column names between model and migration (display_name, storage_path, recipient_type, etc.) + adds node notebook_port/tags
|
| 491 |
+
|
| 492 |
+
### Phase 4 — Migration 0002
|
| 493 |
+
|
| 494 |
+
`20260427_0002_session1_tables.py` adds:
|
| 495 |
+
- `feature_flags` table
|
| 496 |
+
- `system_config` table
|
| 497 |
+
- `branches`, `sections` tables
|
| 498 |
+
- `cluster_heartbeats` table (time-series, append-only)
|
| 499 |
+
- `shared_files`, `file_downloads` tables
|
| 500 |
+
- `video_projects`, `video_jobs` tables
|
| 501 |
+
- New user columns
|
| 502 |
+
|
| 503 |
+
### Phase 5 — Frontend Pages
|
| 504 |
+
|
| 505 |
+
**Checked existing routes, then added:**
|
| 506 |
+
|
| 507 |
+
1. Updated `frontend/src/lib/components/Sidebar.svelte` — added 6 nav items: RAG, Notifications, API Keys, Settings, Cluster (admin-only)
|
| 508 |
+
2. Updated `frontend/src/lib/api.js` — added `cluster`, `academic`, `files` API exports
|
| 509 |
+
3. Created `frontend/src/routes/cluster/+page.svelte` — node list with live metrics, detail panel, approve/drain/remove, GPU history sparkline, enrollment token generation with setup instructions
|
| 510 |
+
4. Created `frontend/src/routes/keys/+page.svelte` — generate, copy, revoke scoped API keys
|
| 511 |
+
5. Created `frontend/src/routes/settings/+page.svelte` — profile, change password, language picker
|
| 512 |
+
6. Created `frontend/src/routes/notifications/+page.svelte` — notification list with mark-read
|
| 513 |
+
7. Created `frontend/src/routes/rag/+page.svelte` — drag-and-drop document upload + list
|
| 514 |
+
|
| 515 |
+
### Phase 6 — Infrastructure
|
| 516 |
+
|
| 517 |
+
- Created `docker-compose.worker.yml` — worker node compose: vLLM + optional Jupyter kernel gateway (`--profile notebook`) + worker-agent
|
| 518 |
+
- Created `nginx/nginx.https.conf` — HTTPS with TLS, HSTS, WebSocket proxy for notebooks
|
| 519 |
+
- Updated `MAC-PROGRESS.md`
|
| 520 |
+
|
| 521 |
+
### Verification checks run:
|
| 522 |
+
- `btn-secondary` CSS class existence confirmed in `app.css`
|
| 523 |
+
- `__init__.py` in routers is empty → module imports work directly
|
| 524 |
+
- `file_share.py` model vs migration column name mismatch found → fixed in 0003 migration
|
| 525 |
+
- All new router imports in `main.py` verified against existing files
|
| 526 |
+
|
| 527 |
+
---
|
| 528 |
+
|
| 529 |
+
## 24. Final Tech Stack (Canonical Reference)
|
| 530 |
+
|
| 531 |
+
### Backend
|
| 532 |
+
| Package | Purpose |
|
| 533 |
+
|---|---|
|
| 534 |
+
| Python 3.11 | Core language |
|
| 535 |
+
| FastAPI 0.115 | API server |
|
| 536 |
+
| PostgreSQL 16 | Main database (master node only) |
|
| 537 |
+
| Redis 7 | Cache, pub/sub, JWT blacklist |
|
| 538 |
+
| vLLM | LLM inference (GPU workers, OpenAI-compatible) |
|
| 539 |
+
| llama.cpp-python | CPU inference fallback |
|
| 540 |
+
| faster-whisper | Speech-to-text (offline) |
|
| 541 |
+
| piper-tts | Text-to-speech (offline) |
|
| 542 |
+
| ffmpeg-python | Video/audio editing |
|
| 543 |
+
| python-on-whales | Docker Engine API |
|
| 544 |
+
| Alembic | Database migrations |
|
| 545 |
+
| bcrypt | Password hashing |
|
| 546 |
+
| python-jose | JWT encode/decode |
|
| 547 |
+
| qdrant-client | Vector DB client for RAG |
|
| 548 |
+
| httpx | Async HTTP client (LLM proxy, search) |
|
| 549 |
+
| sse-starlette | SSE streaming to browser |
|
| 550 |
+
| pywebpush | Web Push notifications (VAPID) |
|
| 551 |
+
| psutil | CPU/RAM metrics |
|
| 552 |
+
| pynvml (GPUtil) | GPU metrics on worker nodes |
|
| 553 |
+
| fpdf2 | PDF report generation |
|
| 554 |
+
| qrcode | QR code for attendance sessions |
|
| 555 |
+
| py-cpuinfo | Hardware detection |
|
| 556 |
+
|
| 557 |
+
### Frontend
|
| 558 |
+
| Package | Purpose |
|
| 559 |
+
|---|---|
|
| 560 |
+
| SvelteKit 2 | Framework (compiles to vanilla JS) |
|
| 561 |
+
| Svelte 5 | Compiler |
|
| 562 |
+
| Vite 5 | Build tool |
|
| 563 |
+
| TypeScript | Throughout |
|
| 564 |
+
| TailwindCSS 3 | Styling (CSS variables only) |
|
| 565 |
+
| svelte-i18n | 19 Indian languages offline |
|
| 566 |
+
| CodeMirror 6 | MBM Book code editor |
|
| 567 |
+
| Mermaid.js | Flowcharts in chat |
|
| 568 |
+
| Chart.js | Admin dashboard charts |
|
| 569 |
+
| Lucide Svelte | Icons |
|
| 570 |
+
| marked + highlight.js | Markdown + syntax highlighting |
|
| 571 |
+
| @fontsource/* | Geist + all Indic fonts (bundled) |
|
| 572 |
+
|
| 573 |
+
### Infrastructure
|
| 574 |
+
| Tool | Purpose |
|
| 575 |
+
|---|---|
|
| 576 |
+
| Docker Compose | All services containerised |
|
| 577 |
+
| Nginx | Reverse proxy, SSL, ports 80/443 |
|
| 578 |
+
| OpenSSL | Self-signed SSL for PWA (LAN only) |
|
| 579 |
+
|
| 580 |
+
### Installer
|
| 581 |
+
| Tool | Purpose |
|
| 582 |
+
|---|---|
|
| 583 |
+
| Inno Setup 6 | Windows .exe installer |
|
| 584 |
+
| start-mac.bat | One-click server start |
|
| 585 |
+
|
| 586 |
+
### DevOps
|
| 587 |
+
| Tool | Purpose |
|
| 588 |
+
|---|---|
|
| 589 |
+
| GitHub | Source code + releases |
|
| 590 |
+
| GitHub Actions | Auto-build .exe on version tag push |
|
| 591 |
+
| `mac/VERSION` | Single source of truth for version |
|
| 592 |
+
|
| 593 |
+
---
|
| 594 |
+
|
| 595 |
+
## 25. Full Environment Variables Reference (.env.example)
|
| 596 |
+
|
| 597 |
+
```env
|
| 598 |
+
# App
|
| 599 |
+
MAC_ENV=development
|
| 600 |
+
MAC_HOST=0.0.0.0
|
| 601 |
+
MAC_PORT=8000
|
| 602 |
+
MAC_DEBUG=false
|
| 603 |
+
MAC_SECRET_KEY=change-me
|
| 604 |
+
MAC_CORS_ORIGINS=["*"] # ← set explicit origins in prod!
|
| 605 |
+
MAC_WORKERS=4
|
| 606 |
+
APP_HOST=0.0.0.0
|
| 607 |
+
APP_PORT=80
|
| 608 |
+
|
| 609 |
+
# Database
|
| 610 |
+
DATABASE_URL=postgresql+asyncpg://mac:mac_password@localhost:5432/mac_db
|
| 611 |
+
|
| 612 |
+
# Redis
|
| 613 |
+
REDIS_URL=redis://localhost:6379/0
|
| 614 |
+
|
| 615 |
+
# JWT
|
| 616 |
+
JWT_SECRET_KEY=change-me # ← NOT used in prod; stored in system_config instead
|
| 617 |
+
JWT_ALGORITHM=HS256
|
| 618 |
+
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=1440
|
| 619 |
+
|
| 620 |
+
# vLLM endpoints
|
| 621 |
+
VLLM_BASE_URL=http://localhost:8001
|
| 622 |
+
VLLM_SPEED_URL=http://localhost:8001
|
| 623 |
+
VLLM_CODE_URL=http://localhost:8002
|
| 624 |
+
VLLM_REASONING_URL=http://localhost:8003
|
| 625 |
+
VLLM_INTELLIGENCE_URL=http://localhost:8004
|
| 626 |
+
VLLM_API_KEY=
|
| 627 |
+
VLLM_TIMEOUT=120
|
| 628 |
+
VLLM_HEALTH_TIMEOUT=5
|
| 629 |
+
|
| 630 |
+
# Model registry overrides
|
| 631 |
+
MAC_MODELS_JSON= # full JSON array → replaces built-ins
|
| 632 |
+
MAC_ENABLED_MODELS= # comma-separated IDs → filters built-ins
|
| 633 |
+
MAC_AUTO_FALLBACK= # model ID for model="auto"
|
| 634 |
+
MAC_DEFAULT_MAX_TOKENS=2048
|
| 635 |
+
|
| 636 |
+
# Model auto-download
|
| 637 |
+
MAC_MODEL_AUTO_DOWNLOAD_ON_USE=true
|
| 638 |
+
MAC_MODEL_AUTO_DOWNLOAD_LIMIT=0
|
| 639 |
+
|
| 640 |
+
# vLLM tuning (per-model)
|
| 641 |
+
VLLM_SPEED_MODEL=Qwen/Qwen2.5-7B-Instruct
|
| 642 |
+
VLLM_SPEED_PORT=8001
|
| 643 |
+
VLLM_SPEED_GPU_MEM=0.22
|
| 644 |
+
VLLM_SPEED_MAX_LEN=8192
|
| 645 |
+
|
| 646 |
+
VLLM_CODE_MODEL=Qwen/Qwen2.5-Coder-7B-Instruct
|
| 647 |
+
VLLM_CODE_PORT=8002
|
| 648 |
+
VLLM_CODE_GPU_MEM=0.22
|
| 649 |
+
VLLM_CODE_MAX_LEN=8192
|
| 650 |
+
|
| 651 |
+
VLLM_REASON_MODEL=deepseek-ai/DeepSeek-R1-Distill-Qwen-14B
|
| 652 |
+
VLLM_REASON_PORT=8003
|
| 653 |
+
VLLM_REASON_GPU_MEM=0.35
|
| 654 |
+
VLLM_REASON_MAX_LEN=8192
|
| 655 |
+
|
| 656 |
+
VLLM_DTYPE=auto
|
| 657 |
+
```
|
| 658 |
+
|
| 659 |
+
---
|
| 660 |
+
|
| 661 |
+
## 26. requirements.txt (Pinned)
|
| 662 |
+
|
| 663 |
+
```
|
| 664 |
+
fastapi==0.115.6
|
| 665 |
+
uvicorn[standard]==0.34.0
|
| 666 |
+
pydantic==2.10.4
|
| 667 |
+
pydantic-settings==2.7.1
|
| 668 |
+
sqlalchemy[asyncio]==2.0.36
|
| 669 |
+
asyncpg==0.30.0
|
| 670 |
+
psycopg2-binary==2.9.10
|
| 671 |
+
alembic==1.14.1
|
| 672 |
+
aiosqlite==0.20.0
|
| 673 |
+
python-jose[cryptography]==3.3.0
|
| 674 |
+
bcrypt==4.2.1
|
| 675 |
+
redis[hiredis]==5.2.1
|
| 676 |
+
httpx==0.28.1
|
| 677 |
+
sse-starlette==2.2.1
|
| 678 |
+
qdrant-client==1.12.1
|
| 679 |
+
huggingface-hub==0.31.2
|
| 680 |
+
python-multipart==0.0.20
|
| 681 |
+
aiofiles==24.1.0
|
| 682 |
+
pywebpush==2.0.1
|
| 683 |
+
psutil==6.1.1
|
| 684 |
+
websockets>=12.0
|
| 685 |
+
GPUtil>=1.4.0
|
| 686 |
+
fpdf2==2.8.2
|
| 687 |
+
py-cpuinfo>=9.0.0
|
| 688 |
+
qrcode[pil]>=7.4.0
|
| 689 |
+
aiohttp>=3.9.0
|
| 690 |
+
cryptography>=42.0.0
|
| 691 |
+
pytest==8.3.4
|
| 692 |
+
pytest-asyncio==0.25.0
|
| 693 |
+
pytest-httpx>=0.30.0
|
| 694 |
+
```
|
| 695 |
+
|
| 696 |
+
---
|
| 697 |
+
|
| 698 |
+
## 27. UI Design System — Light Theme (Default)
|
| 699 |
+
|
| 700 |
+
The app defaults to **light theme**. Dark mode available via toggle (bottom-right of landing page only).
|
| 701 |
+
|
| 702 |
+
### Color tokens:
|
| 703 |
+
```css
|
| 704 |
+
--page-bg: #FAF9F7; /* warm off-white cream */
|
| 705 |
+
--card-bg: #FFFFFF; /* pure white */
|
| 706 |
+
--surface-2: #F5F4F0; /* slightly warm gray */
|
| 707 |
+
--surface-3: #ECEAE4; /* warmer gray */
|
| 708 |
+
--text-primary: #1A1A1A; /* near black, warm */
|
| 709 |
+
--text-secondary: #666560; /* warm medium gray */
|
| 710 |
+
--text-muted: #999791; /* warm light gray */
|
| 711 |
+
--accent: #D97449; /* coral orange */
|
| 712 |
+
--accent-hover: #C4623D; /* deeper coral */
|
| 713 |
+
--border: rgba(0,0,0,0.12);
|
| 714 |
+
--code-bg: #F0EDE8; /* warm parchment */
|
| 715 |
+
```
|
| 716 |
+
|
| 717 |
+
### Key UI features to implement / in progress:
|
| 718 |
+
- **MAC title glitch effect** — vanilla JS CSS glitch animation (from previous pretext.js) ported to Svelte
|
| 719 |
+
- **Background hover particle effect** — physics particle canvas (from previous UI), already in `ParticleCanvas.svelte`
|
| 720 |
+
- **Extendable sidebar** — VS Code-style drag-to-resize sidebar panels
|
| 721 |
+
- **Notebook UI** — Kaggle/Colab-style cells with CodeMirror 6
|
| 722 |
+
- **Loader animation** — `delete later/Loader.svelte` — used on any delay (chat response, notebook execution, page load)
|
| 723 |
+
- **MBM→MAC morph animation** — Devanagari letter morph (`delete later/MBM-MAC Globe.html`) — first-time landing page only
|
| 724 |
+
- **Smooth light↔dark transition** — CSS variable swap with transition, toggle button bottom-right on landing only
|
| 725 |
+
|
| 726 |
+
### Font:
|
| 727 |
+
- Geist (Latin) + all Indic fonts via `@fontsource/*` — bundled offline, no CDN
|
| 728 |
+
|
| 729 |
+
---
|
| 730 |
+
|
| 731 |
+
## 28. Notebook Architecture Target
|
| 732 |
+
|
| 733 |
+
Goal: **Kaggle/Colab-style notebook** that runs on the MAC cluster.
|
| 734 |
+
|
| 735 |
+
```
|
| 736 |
+
Browser (CodeMirror 6 cell editor)
|
| 737 |
+
│ WebSocket /ws/notebook/{id}?token=JWT
|
| 738 |
+
▼
|
| 739 |
+
notebook_ws.py → kernel_manager.py
|
| 740 |
+
├── Docker mode (prod): mac-kernel-{lang} container
|
| 741 |
+
├── Subprocess mode (dev): direct interpreter
|
| 742 |
+
└── Remote worker mode: Jupyter kernel gateway on GPU worker
|
| 743 |
+
```
|
| 744 |
+
|
| 745 |
+
Multi-language support: Python, JavaScript, SQL (at minimum).
|
| 746 |
+
Kernel lifecycle: idle timeout 120s, max 10 per node.
|
| 747 |
+
Persistent: cell content stored as JSON in `notebooks` table.
|
| 748 |
+
|
| 749 |
+
---
|
| 750 |
+
|
| 751 |
+
## 29. Global Access Strategy
|
| 752 |
+
|
| 753 |
+
For students accessing from outside LAN:
|
| 754 |
+
- **Cloudflare Tunnel** (`cloudflared`) on master PC → exposes local HTTPS to public domain
|
| 755 |
+
- No router port-forwarding needed
|
| 756 |
+
- Students use OpenAI-compatible API keys (`mac_sk_*`) against the public domain
|
| 757 |
+
- Same keys work on LAN (direct) and WAN (via tunnel) — same auth chain
|
| 758 |
+
|
| 759 |
+
---
|
| 760 |
+
|
| 761 |
+
## 30. Source Files with No .claude or .vscode Config
|
| 762 |
+
|
| 763 |
+
Checked: no `.claude/` directory, no `CLAUDE.md`, no `.vscode/settings.json` or `.vscode/extensions.json` exist in `D:\MAC`.
|
| 764 |
+
All Claude session context lives in this file + `ARCHITECTURE.md` + `MAC-PROGRESS.md`.
|
| 765 |
+
VS Code Copilot context: Antigravity trajectory `e36c8c56-d0d8-4913-8da2-90176f0c34d3`, session log at `c:\Users\rampy\AppData\Roaming\Code\User\workspaceStorage\26393181f28fefe9ec94c456e08b07ec\GitHub.copilot-chat\debug-logs\e36c8c56-d0d8-4913-8da2-90176f0c34d3`.
|
| 766 |
+
|
| 767 |
+
---
|
| 768 |
+
|
| 769 |
+
---
|
| 770 |
+
|
| 771 |
+
## 31. Reference Codebases on Disk
|
| 772 |
+
|
| 773 |
+
Three reference repos exist locally that informed MAC's design and are the source for UI features Claude was asked to port:
|
| 774 |
+
|
| 775 |
+
### A. `D:\MBMmac\MAC\frontend` — Original Vanilla JS Frontend
|
| 776 |
+
The **original MAC frontend** before the SvelteKit rewrite. This is the source of the UI features that must be ported to Svelte:
|
| 777 |
+
- `frontend/app.js` — ~4782 lines, entire frontend in one file
|
| 778 |
+
- `frontend/style.css` — black & white premium dark theme
|
| 779 |
+
- `frontend/index.html` — SPA shell with Chart.js, highlight.js, Mermaid.js loaded from `/static/libs/`
|
| 780 |
+
- `frontend/libs/` — bundled JS: chart.umd, highlight.min, mermaid.min, hljs language packs
|
| 781 |
+
- **Has the MAC glitch text effect, background hover particle animation, and sidebar drag/expand** — must be ported to Svelte
|
| 782 |
+
|
| 783 |
+
### B. `D:\MBMmac\Mbmbook\frontend` — MBMBook Notebook UI (React/TypeScript)
|
| 784 |
+
The **notebook UI reference** — Kaggle/Colab-style built in React + Monaco Editor + TypeScript:
|
| 785 |
+
```
|
| 786 |
+
src/
|
| 787 |
+
App.tsx
|
| 788 |
+
components/
|
| 789 |
+
AnimatedTitle.tsx ← Animated MAC/MBM title
|
| 790 |
+
CellOutput.tsx ← Notebook cell output rendering
|
| 791 |
+
ClusterPanel.tsx ← GPU cluster management panel
|
| 792 |
+
NotebookCell.tsx ← Individual notebook cell (CodeMirror/Monaco)
|
| 793 |
+
NotebookView.tsx ← Full notebook layout
|
| 794 |
+
ResizeHandle.tsx ← VS Code-style drag-to-resize panels
|
| 795 |
+
Sidebar.tsx ← Navigation sidebar
|
| 796 |
+
ThemeToggle.tsx ← Light/dark toggle
|
| 797 |
+
Toolbar.tsx ← Notebook toolbar
|
| 798 |
+
services/
|
| 799 |
+
stores/
|
| 800 |
+
monaco-setup.ts
|
| 801 |
+
```
|
| 802 |
+
Stack: React + TypeScript + Vite + Tailwind + Monaco Editor
|
| 803 |
+
**Port the notebook UI patterns (ResizeHandle, NotebookCell, CellOutput) to Svelte.**
|
| 804 |
+
|
| 805 |
+
### C. `D:\MAC-ref` — Original Multi-Node Reference Repo
|
| 806 |
+
The original MAC codebase from before `D:\mac2`. Has 5 docker-compose files for the multi-node cluster topology:
|
| 807 |
+
- `docker-compose.control-node.yml`
|
| 808 |
+
- `docker-compose.pc1-gpu.yml`
|
| 809 |
+
- `docker-compose.pc2-app.yml`
|
| 810 |
+
- `docker-compose.worker-node.yml`
|
| 811 |
+
- `docker-compose.yml`
|
| 812 |
+
Also has: `worker-agent.py`, `kernels/`, `examples/`, `test_students.csv/json`, `START-MAC.bat`, `START-WIFI.bat`, `setup-firewall.ps1`
|
| 813 |
+
|
| 814 |
+
### D. MAC-PROJECT-SAMJHO.md
|
| 815 |
+
Hinglish explanation doc at `D:\MAC-PROJECT-SAMJHO.md` — explains the full project in Hindi/English for onboarding. Describes the original 8-table schema (student_registry, users, refresh_tokens, usage_logs, guardrail_rules, quota_overrides, rag_collections, rag_documents).
|
| 816 |
+
|
| 817 |
+
### E. GitHub Repos
|
| 818 |
+
- **`https://github.com/mbmuniversity2026/MAC`** — target push repo (Claude attempted push on 2026-04-27, failed — not a git repo at `D:\mac2` at that time)
|
| 819 |
+
- **`https://github.com/Mebeingmealways/MAC/tree/main/frontend`** — another reference frontend Claude was asked to draw inspiration from
|
| 820 |
+
|
| 821 |
+
---
|
| 822 |
+
|
| 823 |
+
## 32. Full Session Timeline (Claude Desktop Sessions)
|
| 824 |
+
|
| 825 |
+
| Date | Session File | cwd | Key Work |
|
| 826 |
+
|---|---|---|---|
|
| 827 |
+
| 2026-04-26 | `D--mac2/4e4ab358` | `D:\mac2` | Session 1 plan: backend foundation, cleanup of old vanilla JS frontend, Alembic wiring, feature flags, hardware/network/system/setup services — see `gleaming-purring-mitten.md` plan |
|
| 828 |
+
| 2026-04-26–27 | `D--mac2/833b6073` | `D:\mac2` | Session 1 execution continued; context ran out; push to GitHub attempted (failed — not a git repo) |
|
| 829 |
+
| 2026-04-27 08:28 | `d--MAC/8b8be9aa` (5.7MB) | `D:\mac2` | Session 2: JWT blacklist, cluster, all frontend pages; blank screen bug appeared; SW cache issues; theme change requested; MBMBook reference fetched; model switched to claude-sonnet-4-6 |
|
| 830 |
+
| 2026-04-27 14:43 | `d--MAC/969becc6` | `D:\mac2` | Short session, opened MAC-KNOWLEDGE-BASE.md |
|
| 831 |
+
| 2026-04-27 16:52 | `d--MAC/91e087de` (1.5MB) | `D:\mac2` | Frontend blank screen still broken; user asked for ARCHITECTURE.md prompt; model switched to claude-opus-4-7 |
|
| 832 |
+
| 2026-04-27 19:54 | `d--MAC/877da497` (1MB) | `D:\mac2` | Debugging frontend hosting: wrong IP served wrong code; LAN IP = `192.168.1.34`; login working |
|
| 833 |
+
| 2026-04-27 20:12 | `d--MAC/8b8be9aa` (5.7MB) | `D:\mac2` | Continuation: blank screen fixed; CSS theme applied; frontend inspected from `D:\hey\MAC\frontend` (now `D:\MBMmac\MAC\frontend`) |
|
| 834 |
+
| 2026-04-27 21:52 | `d--MAC/7b593b21` (1.5MB) | `D:\mac2` | Requested ARCHITECTURE.md write-up |
|
| 835 |
+
| 2026-04-27 22:29 | `d--MAC/1fe8755d` | `D:\mac2` | Admin login working (`abhisek.cse@mbm.ac.in / Admin@1234`); asked to add more sidebar items and apply `D:\hey\MAC\frontend` glitch effects |
|
| 836 |
+
| 2026-04-28 09:58 | `d--MAC/beae7ebc` (2.6MB) | `D:\MAC` | TODAY: Wrong code on LAN; language switcher broken; codebase in `D:\MAC` now; MAC-CONTEXT.md created |
|
| 837 |
+
|
| 838 |
+
---
|
| 839 |
+
|
| 840 |
+
## 33. Known Bugs Encountered & Status
|
| 841 |
+
|
| 842 |
+
| Bug | Root Cause | Status |
|
| 843 |
+
|---|---|---|
|
| 844 |
+
| Blank screen on frontend load | SvelteKit SSR hydration issue + `export const ssr = false` not applied | Fixed: added `+layout.js` with `ssr=false, prerender=false` |
|
| 845 |
+
| Stale build served after rebuild | Service worker caching old `index.html` and `_app/` chunks | Fixed: SW now wipes all caches on install/activate, no fetch handler |
|
| 846 |
+
| Wrong code on LAN IP (`192.168.1.34`) | Docker serving a different container/port | Investigated; correct project is `D:\MAC` not `D:\mac2` |
|
| 847 |
+
| Language switcher not working | i18n locale strings not loading / locale change not triggering reactive update | In progress |
|
| 848 |
+
| Login page not advancing past splash | Particle canvas blocking click events or slow authStore.init() | Fixed: layout guard redirects on init completion |
|
| 849 |
+
| `mac_sk_live_*` vs `mac_sk_*` prefix | Legacy key check order in auth_middleware | Resolved: legacy checked first |
|
| 850 |
+
|
| 851 |
+
---
|
| 852 |
+
|
| 853 |
+
## 34. Dev Credentials & Local Network
|
| 854 |
+
|
| 855 |
+
| | Value |
|
| 856 |
+
|---|---|
|
| 857 |
+
| Admin email | `abhisek.cse@mbm.ac.in` |
|
| 858 |
+
| Admin password | `Admin@1234` |
|
| 859 |
+
| LAN IP | `192.168.1.34` |
|
| 860 |
+
| Frontend URL | `http://192.168.1.34` (port 80 via Nginx) |
|
| 861 |
+
| API URL | `http://192.168.1.34:8000` or `http://192.168.1.34/api/v1` |
|
| 862 |
+
| API docs | `http://192.168.1.34:8000/docs` |
|
| 863 |
+
|
| 864 |
+
Default seeded dev accounts (to create via setup or seed script):
|
| 865 |
+
- Admin: roll `ADMIN001`, role `admin`
|
| 866 |
+
- Faculty: roll `FAC001`, role `faculty`
|
| 867 |
+
- Student: roll `STU001`, role `student`
|
| 868 |
+
|
| 869 |
+
---
|
| 870 |
+
|
| 871 |
+
## 35. Repo History — `D:\mac2` → `D:\MAC`
|
| 872 |
+
|
| 873 |
+
The codebase started at `D:\mac2`. At some point it was copied/moved to `D:\MAC`. Both directories exist:
|
| 874 |
+
- `D:\mac2` — old working directory (Claude sessions before 2026-04-28 used this)
|
| 875 |
+
- `D:\MAC` — current canonical location (all work from 2026-04-28 onward)
|
| 876 |
+
- `D:\MAC-ref` — older reference snapshot (pre-SvelteKit, vanilla JS frontend)
|
| 877 |
+
- `D:\MBMmac\MAC` — another older snapshot (same as MAC-ref structure)
|
| 878 |
+
|
| 879 |
+
When continuing work, always use `D:\MAC` as the project root.
|
| 880 |
+
|
| 881 |
+
---
|
| 882 |
+
|
| 883 |
+
*Context compiled from: ARCHITECTURE.md, MAC-PROGRESS.md, README.md, .env.example, requirements.txt, workspace structure, full Claude session timeline (all 11 sessions), plan file `gleaming-purring-mitten.md`, MAC-PROJECT-SAMJHO.md, reference codebases at D:\MBMmac\MAC, D:\MBMmac\Mbmbook, D:\MAC-ref, VS Code Copilot session (Antigravity trajectory `e36c8c56-d0d8-4913-8da2-90176f0c34d3`)*
|
docs/MAC-PROGRESS.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MAC — MBM AI Cloud · Build Progress
|
| 2 |
+
|
| 3 |
+
**Project:** Self-hosted AI inference platform for MBM University Jodhpur
|
| 4 |
+
**Stack:** FastAPI · SvelteKit · PostgreSQL · Redis · vLLM · Nginx · Docker
|
| 5 |
+
**Repo:** `D:\mac2` (push to `github.com/mbmuniversity2026/MAC`)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✅ Completed
|
| 10 |
+
|
| 11 |
+
### Session 1 — Backend Foundation
|
| 12 |
+
|
| 13 |
+
#### Database / Migrations
|
| 14 |
+
- Alembic wired — `alembic/env.py` imports all models
|
| 15 |
+
- `20260426_0001_initial_schema.py` — full initial schema capture
|
| 16 |
+
- `20260427_0002_session1_tables.py` — feature_flags, system_config, branches, sections, cluster_heartbeats, shared_files, file_downloads, video_projects, video_jobs + user columns
|
| 17 |
+
|
| 18 |
+
#### New Models (`mac/models/`)
|
| 19 |
+
| File | Tables |
|
| 20 |
+
|------|--------|
|
| 21 |
+
| `feature_flag.py` | `FeatureFlag` |
|
| 22 |
+
| `academic.py` | `Branch`, `Section` |
|
| 23 |
+
| `cluster.py` | `ClusterNode`, `ClusterHeartbeat` |
|
| 24 |
+
| `file_share.py` | `SharedFile`, `FileDownload` |
|
| 25 |
+
| `video.py` | `VideoProject`, `VideoJob` |
|
| 26 |
+
| `system_config.py` | `SystemConfig` |
|
| 27 |
+
|
| 28 |
+
#### New Routers (`mac/routers/`)
|
| 29 |
+
| File | Endpoints |
|
| 30 |
+
|------|-----------|
|
| 31 |
+
| `features.py` | GET /features/status, PATCH /admin/features/{key} |
|
| 32 |
+
| `hardware.py` | GET /hardware/local, /hardware/recommendations |
|
| 33 |
+
| `network.py` | GET /network/local-ip, /network/discover |
|
| 34 |
+
| `system.py` | GET /system/version, /system/update-status, POST /admin/system/restart |
|
| 35 |
+
| `setup.py` | GET /setup/status, POST /setup/create-admin, GET /setup/recovery |
|
| 36 |
+
|
| 37 |
+
#### New Services (`mac/services/`)
|
| 38 |
+
- `feature_seeder.py` — seeds default flags on startup
|
| 39 |
+
- `setup_service.py` — JWT secret management via system_config
|
| 40 |
+
- `token_blacklist_service.py` — JWT blacklist via Redis (TTL-matched)
|
| 41 |
+
|
| 42 |
+
#### Security Improvements
|
| 43 |
+
- JWT `jti` claim added to all access tokens (`mac/utils/security.py`)
|
| 44 |
+
- `auth_middleware.py` checks blacklist on every request
|
| 45 |
+
- Logout blacklists current access token + revokes refresh tokens
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
### Session 2 — SvelteKit Frontend
|
| 50 |
+
|
| 51 |
+
**Full PWA frontend at `frontend/`:**
|
| 52 |
+
|
| 53 |
+
| File | Description |
|
| 54 |
+
|------|-------------|
|
| 55 |
+
| `src/app.html` | PWA shell, Google Fonts, SW registration |
|
| 56 |
+
| `src/app.css` | Full design system — dark theme, mac-blue palette, all component classes |
|
| 57 |
+
| `src/lib/api.js` | Complete API client (auth, query, models, usage, quota, keys, features, hardware, network, system, users, guardrails, rag, notifications, cluster, academic, files) |
|
| 58 |
+
| `src/lib/stores.js` | authStore, chatStore, setupStore, featureStore, toast, sidebarOpen |
|
| 59 |
+
| `src/lib/i18n.js` | 19 Indian languages with lazy loading + RTL support |
|
| 60 |
+
| `src/lib/components/ParticleCanvas.svelte` | Physics particle animation (login/splash) |
|
| 61 |
+
| `src/lib/components/Sidebar.svelte` | Navigation sidebar with all routes |
|
| 62 |
+
| `src/lib/components/Toast.svelte` | Toast notification component |
|
| 63 |
+
| `src/lib/components/ChatMessage.svelte` | Chat bubble with markdown rendering |
|
| 64 |
+
| `src/routes/+layout.svelte` | Auth guard, setup check, shell layout |
|
| 65 |
+
| `src/routes/+page.svelte` | Landing / redirect |
|
| 66 |
+
| `src/routes/login/+page.svelte` | Animated login page with particle canvas |
|
| 67 |
+
| `src/routes/setup/+page.svelte` | First-run admin setup wizard |
|
| 68 |
+
| `src/routes/chat/+page.svelte` | SSE streaming chat with model picker |
|
| 69 |
+
| `src/routes/dashboard/+page.svelte` | Activity heatmap, quota rings, model distribution |
|
| 70 |
+
| `src/routes/admin/+page.svelte` | Admin panel: Users, Models, Features, Hardware, System tabs |
|
| 71 |
+
| `src/routes/cluster/+page.svelte` | Cluster management: node list, detail, actions, history chart, enrollment tokens |
|
| 72 |
+
| `src/routes/keys/+page.svelte` | API key management (generate, copy, revoke) |
|
| 73 |
+
| `src/routes/settings/+page.svelte` | Profile, change password, language picker |
|
| 74 |
+
| `src/routes/notifications/+page.svelte` | Notification list with mark-read |
|
| 75 |
+
| `src/routes/rag/+page.svelte` | RAG document upload (drag-and-drop) + list |
|
| 76 |
+
|
| 77 |
+
**Static assets:**
|
| 78 |
+
- `static/manifest.json` — PWA manifest with shortcuts
|
| 79 |
+
- `static/sw.js` — Service worker (cache-first shell, network-first API, SSE passthrough)
|
| 80 |
+
|
| 81 |
+
**Infrastructure:**
|
| 82 |
+
- `nginx/nginx.conf` — HTTP server (production)
|
| 83 |
+
- `nginx/nginx.https.conf` — HTTPS server with TLS, HSTS, WebSocket proxy
|
| 84 |
+
- `docker-compose.yml` — Master node: MAC API + vLLM + Postgres + Redis + Nginx + Qdrant + SearXNG
|
| 85 |
+
- `docker-compose.worker.yml` — Worker node: vLLM + optional Jupyter + worker-agent
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
### Session 2 — Distributed Cluster Backend
|
| 90 |
+
|
| 91 |
+
#### Cluster Architecture
|
| 92 |
+
```
|
| 93 |
+
Master node (this machine)
|
| 94 |
+
├── MAC API (FastAPI) — receives all user requests
|
| 95 |
+
├── PostgreSQL — DB (master-only)
|
| 96 |
+
├── Redis — cache, rate limiting, JWT blacklist
|
| 97 |
+
├── Nginx — reverse proxy + frontend
|
| 98 |
+
├── Qdrant — vector DB for RAG
|
| 99 |
+
└── SearXNG — web search
|
| 100 |
+
|
| 101 |
+
Worker nodes (any PC on same network)
|
| 102 |
+
├── vLLM — GPU inference (OpenAI-compatible)
|
| 103 |
+
├── Jupyter kernel gateway — notebook execution (optional)
|
| 104 |
+
└── worker_agent.py — heartbeat + registration agent
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
#### Cluster Services
|
| 108 |
+
- `mac/services/load_balancer.py` — score-based routing: `gpu_util×0.5 + vram_ratio×0.3`, 30s stale threshold
|
| 109 |
+
- `mac/services/llm_service.py` — updated `_resolve_model_cluster` to use load balancer before local vLLM
|
| 110 |
+
- `mac/models/node.py` — `WorkerNode` + `NodeModelDeployment` + `EnrollmentToken`; added `notebook_port`, `tags`
|
| 111 |
+
- `mac/models/cluster.py` — `ClusterHeartbeat` time-series
|
| 112 |
+
|
| 113 |
+
#### Cluster Router (`mac/routers/cluster.py`)
|
| 114 |
+
| Endpoint | Description |
|
| 115 |
+
|----------|-------------|
|
| 116 |
+
| `POST /cluster/enroll-token` | Admin generates one-time enrollment token |
|
| 117 |
+
| `GET /cluster/enroll-tokens` | List all tokens |
|
| 118 |
+
| `POST /cluster/register` | Worker self-registers (no JWT — uses enrollment token) |
|
| 119 |
+
| `POST /cluster/heartbeat` | Worker sends heartbeat every 10s |
|
| 120 |
+
| `GET /cluster/nodes` | List all nodes with live health |
|
| 121 |
+
| `GET /cluster/nodes/{id}` | Node detail with deployments |
|
| 122 |
+
| `POST /cluster/nodes/{id}/action` | approve / drain / reactivate / remove |
|
| 123 |
+
| `POST /cluster/nodes/{id}/deploy` | Register vLLM deployment on node |
|
| 124 |
+
| `DELETE /cluster/nodes/{id}/deploy/{dep_id}` | Remove deployment |
|
| 125 |
+
| `GET /cluster/nodes/{id}/history` | Heartbeat time-series (for charts) |
|
| 126 |
+
|
| 127 |
+
#### Worker Agent (`worker_agent.py`)
|
| 128 |
+
Standalone Python script for worker PCs:
|
| 129 |
+
- Reads `MAC_MASTER_URL`, `MAC_ENROLL_TOKEN`, `MAC_VLLM_PORT`, etc. from env
|
| 130 |
+
- Self-registers on startup via enrollment token
|
| 131 |
+
- Sends heartbeats every 10s with GPU/CPU/RAM metrics (via `pynvml` + `psutil`)
|
| 132 |
+
- Queries local vLLM `/v1/models` to report active models
|
| 133 |
+
- Handles stale/auth errors gracefully
|
| 134 |
+
|
| 135 |
+
#### Other New Routers
|
| 136 |
+
| File | Endpoints |
|
| 137 |
+
|------|-----------|
|
| 138 |
+
| `mac/routers/academic.py` | CRUD for branches and sections |
|
| 139 |
+
| `mac/routers/file_share.py` | Admin upload, user download, stats |
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## 🔲 Remaining / Optional
|
| 144 |
+
|
| 145 |
+
| Item | Priority | Notes |
|
| 146 |
+
|------|----------|-------|
|
| 147 |
+
| Frontend PWA icons | Medium | `static/icon-192.png`, `static/icon-512.png`, `static/favicon.ico` — need actual PNG files |
|
| 148 |
+
| Frontend: refresh token flow | Medium | Silent JWT refresh in `api.js` before expiry |
|
| 149 |
+
| Multi-stage Dockerfile | Low | Stage 1: node build frontend; Stage 2: python + nginx |
|
| 150 |
+
| Feature flag wiring | Low | `feature_required("ai_chat")` on `/query/*`, etc. |
|
| 151 |
+
| HTTPS cert setup | Deployment | Use `nginx.https.conf` + Let's Encrypt / self-signed |
|
| 152 |
+
| `alembic/versions/0003` | When schema changes | Node notebook_port and tags columns |
|
| 153 |
+
| Video generation service | Future | `VideoProject` / `VideoJob` models exist, router not yet created |
|
| 154 |
+
|
| 155 |
+
---
|
| 156 |
+
|
| 157 |
+
## Deployment Quick-Start
|
| 158 |
+
|
| 159 |
+
### Master node
|
| 160 |
+
```bash
|
| 161 |
+
# 1. Build frontend
|
| 162 |
+
cd frontend && npm install && npm run build && cd ..
|
| 163 |
+
|
| 164 |
+
# 2. Configure environment
|
| 165 |
+
cp .env.example .env # edit DB, Redis, model settings
|
| 166 |
+
|
| 167 |
+
# 3. Run DB migrations
|
| 168 |
+
docker compose up postgres -d
|
| 169 |
+
docker compose run --rm mac alembic upgrade head
|
| 170 |
+
|
| 171 |
+
# 4. Start all services
|
| 172 |
+
docker compose up -d
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
### Adding a worker node
|
| 176 |
+
```bash
|
| 177 |
+
# On the master — generate enrollment token
|
| 178 |
+
curl -X POST http://MASTER_IP:8000/api/v1/cluster/enroll-token \
|
| 179 |
+
-H "Authorization: Bearer ADMIN_JWT" \
|
| 180 |
+
-d '{"label":"Lab PC 1","expires_hours":24}'
|
| 181 |
+
|
| 182 |
+
# On the worker PC
|
| 183 |
+
MAC_MASTER_URL=http://MASTER_IP:8000 \
|
| 184 |
+
MAC_ENROLL_TOKEN=<token_from_above> \
|
| 185 |
+
MAC_VLLM_PORT=8001 \
|
| 186 |
+
docker compose -f docker-compose.worker.yml up -d
|
| 187 |
+
|
| 188 |
+
# Then approve the node in MAC admin panel → Cluster tab
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
### HTTPS (production)
|
| 192 |
+
```bash
|
| 193 |
+
# Place certs in nginx/ssl/
|
| 194 |
+
# Swap nginx config:
|
| 195 |
+
# In docker-compose.yml, change:
|
| 196 |
+
# volumes: ./nginx/nginx.conf → ./nginx/nginx.https.conf
|
| 197 |
+
# Then restart nginx
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
---
|
| 201 |
+
|
| 202 |
+
*Last updated: 2026-04-27 — Session 2 complete*
|
frontend/build.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Build SvelteKit frontend for production.
|
| 3 |
+
# Output goes to frontend/build/ — Nginx mounts this directory.
|
| 4 |
+
set -e
|
| 5 |
+
|
| 6 |
+
cd "$(dirname "$0")"
|
| 7 |
+
|
| 8 |
+
echo "Installing dependencies…"
|
| 9 |
+
npm install
|
| 10 |
+
|
| 11 |
+
echo "Building SvelteKit app…"
|
| 12 |
+
npm run build
|
| 13 |
+
|
| 14 |
+
echo "Build complete → frontend/build/"
|
| 15 |
+
ls -lh build/
|
frontend/build/_app/env.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
export const env={}
|
frontend/build/_app/immutable/assets/0.DBvVKUFC.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:Fira Code,JetBrains Mono,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--bg: #FAF9F7;--surface: #FFFFFF;--surface2: #F5F4F0;--surface3: #ECEAE4;--border: rgba(0,0,0,.12);--border-strong: rgba(0,0,0,.22);--text: #1A1A1A;--text2: #666560;--text3: #999791;--accent: #D97449;--accent-hover: #C4623D;--accent-text: #FFFFFF;--success: #2D7D52;--success-bg: #F0FAF4;--warning: #B5620A;--warning-bg: #FFF8F0;--error: #C0392B;--error-bg: #FFF0EE;--code-bg: #F0EDE8;--shadow: 0 1px 3px rgba(0,0,0,.08)}[data-theme=dark]{--bg: #1A1917;--surface: #242220;--surface2: #2E2C29;--surface3: #393733;--border: rgba(255,255,255,.1);--border-strong: rgba(255,255,255,.18);--text: #E8E6E1;--text2: #9B9891;--text3: #6B6965;--accent: #E8855A;--accent-hover: #D9724A;--accent-text: #FFFFFF;--success: #4CAF80;--success-bg: #0F2D1E;--warning: #E8A030;--warning-bg: #2A1F0A;--error: #E05555;--error-bg: #2A0F0F;--code-bg: #161513;--shadow: 0 1px 3px rgba(0,0,0,.3)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{min-height:100vh;background-color:var(--bg);color:var(--text);font-family:Inter,system-ui,sans-serif;transition:background-color .35s ease,color .35s ease}*{transition:background-color .35s ease,color .2s ease,border-color .3s ease,box-shadow .3s ease}input,textarea,button,a,[class*=nav-],[class*=btn-],canvas{transition:none}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:var(--surface2)}::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--accent)}.btn-primary{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;background:var(--accent);color:var(--accent-text);font-size:14px;font-weight:600;border-radius:8px;border:none;cursor:pointer;transition:background .15s ease,transform .1s ease,box-shadow .15s ease;box-shadow:0 1px 3px #00000026;text-decoration:none}.btn-primary:hover:not(:disabled){background:var(--accent-hover);box-shadow:0 2px 6px #0003}.btn-primary:active:not(:disabled){transform:scale(.98)}.btn-primary:disabled{opacity:.5;cursor:not-allowed}.btn-secondary{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;background:var(--surface2);color:var(--text);font-size:14px;font-weight:500;border-radius:8px;border:1px solid var(--border);cursor:pointer;transition:background .15s ease,border-color .15s ease;text-decoration:none}.btn-secondary:hover:not(:disabled){background:var(--surface3);border-color:var(--border-strong)}.btn-secondary:disabled{opacity:.5;cursor:not-allowed}.btn-danger{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 16px;background:var(--error-bg);color:var(--error);font-size:14px;font-weight:600;border-radius:8px;border:1px solid var(--error);cursor:pointer;transition:background .15s ease}.btn-danger:hover:not(:disabled){background:var(--error);color:#fff}.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;box-shadow:var(--shadow)}.input-field{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;color:var(--text);font-size:14px;outline:none;transition:border-color .15s ease,box-shadow .15s ease}.input-field::-moz-placeholder{color:var(--text3)}.input-field::placeholder{color:var(--text3)}.input-field:focus{border-color:var(--accent);box-shadow:0 0 0 3px #d9744926}.label{display:block;font-size:13px;font-weight:500;color:var(--text2);margin-bottom:4px}.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;letter-spacing:.02em}.badge-green{background:var(--success-bg);color:var(--success)}.badge-yellow{background:var(--warning-bg);color:var(--warning)}.badge-gray{background:var(--surface3);color:var(--text2)}.nav-link{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:8px;color:var(--text2);font-size:13px;font-weight:500;text-decoration:none;cursor:pointer;background:none;border:none;transition:background .12s ease,color .12s ease;white-space:nowrap}.nav-link:hover{background:var(--surface2);color:var(--text)}.nav-link.active{background:#d974491f;color:var(--accent)}.nav-link.\!active{background:#d974491f!important;color:var(--accent)!important}.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:4px;box-shadow:var(--shadow)}.typing-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:bounce 1s infinite}.message-user{background:#d974491a;border:1px solid rgba(217,116,73,.2);border-radius:16px 4px 16px 16px;padding:12px 16px;max-width:80%;margin-left:auto;color:var(--text)}.message-assistant{background:var(--surface2);border:1px solid var(--border);border-radius:4px 16px 16px;padding:12px 16px;max-width:90%;color:var(--text)}pre code{display:block;background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:16px;font-family:Fira Code,JetBrains Mono,monospace;font-size:13px;overflow-x:auto;color:var(--text)}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.collapse{visibility:collapse}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-6{bottom:1.5rem}.right-6{right:1.5rem}.top-1{top:.25rem}.z-10{z-index:10}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-video{aspect-ratio:16 / 9}.h-1{height:.25rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-full{height:100%}.min-h-0{min-height:0px}.min-h-20{min-height:5rem}.min-h-24{min-height:6rem}.min-h-28{min-height:7rem}.min-h-\[520px\]{min-height:520px}.min-h-screen{min-height:100vh}.w-11{width:2.75rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-36{width:9rem}.w-4{width:1rem}.w-40{width:10rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-40{min-width:10rem}.min-w-44{min-width:11rem}.min-w-48{min-width:12rem}.min-w-56{min-width:14rem}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-72{max-width:18rem}.max-w-7xl{max-width:80rem}.max-w-\[85\%\]{max-width:85%}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.animate-fade-in{animation:fadeIn .3s ease-in-out}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes slideUp{0%{transform:translateY(10px);opacity:0}to{transform:translateY(0);opacity:1}}.animate-slide-up{animation:slideUp .3s ease-out}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-dark-600>:not([hidden])~:not([hidden]){border-color:var(--surface3)}.self-end{align-self:flex-end}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-dashed{border-style:dashed}.border-blue-800\/40{border-color:#1e40af66}.border-dark-500{border-color:var(--border-strong)}.border-dark-600{border-color:var(--surface3)}.border-green-500{--tw-border-opacity: 1;border-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-green-800\/40{border-color:#16653466}.border-mac-500,.border-mac-600{border-color:var(--accent)}.border-mac-700{border-color:var(--accent-hover)}.border-orange-800\/40{border-color:#9a341266}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-800{--tw-border-opacity: 1;border-color:rgb(153 27 27 / var(--tw-border-opacity, 1))}.border-red-800\/40{border-color:#991b1b66}.border-transparent{border-color:transparent}.border-yellow-500{--tw-border-opacity: 1;border-color:rgb(234 179 8 / var(--tw-border-opacity, 1))}.border-yellow-800\/40{border-color:#854d0e66}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-blue-900\/30{background-color:#1e3a8a4d}.bg-blue-900\/40{background-color:#1e3a8a66}.bg-dark-500{background-color:var(--border-strong)}.bg-dark-600{background-color:var(--surface3)}.bg-dark-700{background-color:var(--surface2)}.bg-dark-900{background-color:var(--bg)}.bg-gray-900\/40{background-color:#11182766}.bg-green-700\/30{background-color:#15803d4d}.bg-green-900\/30{background-color:#14532d4d}.bg-green-900\/40{background-color:#14532d66}.bg-green-900\/50{background-color:#14532d80}.bg-green-950\/40{background-color:#052e1666}.bg-mac-400,.bg-mac-500,.bg-mac-600{background-color:var(--accent)}.bg-mac-700{background-color:var(--accent-hover)}.bg-mac-800{background-color:var(--surface3)}.bg-orange-900\/30{background-color:#7c2d124d}.bg-orange-900\/40{background-color:#7c2d1266}.bg-red-900\/30{background-color:#7f1d1d4d}.bg-red-900\/40{background-color:#7f1d1d66}.bg-red-950\/50{background-color:#450a0a80}.bg-surface2{background-color:var(--surface2)}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-900\/30{background-color:#713f124d}.bg-yellow-900\/40{background-color:#713f1266}.bg-gradient-radial{background-image:radial-gradient(var(--tw-gradient-stops))}.to-dark-900{--tw-gradient-to: var(--bg) var(--tw-gradient-to-position)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-4{padding-bottom:1rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:Fira Code,JetBrains Mono,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-mac-300,.text-mac-400{color:var(--accent)}.text-orange-400{--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.opacity-0{opacity:0}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/50{--tw-shadow-color: rgb(0 0 0 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-black\/60{--tw-shadow-color: rgb(0 0 0 / .6);--tw-shadow: var(--tw-shadow-colored)}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-mac-500{--tw-ring-color: var(--accent)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-xl{--tw-backdrop-blur: blur(24px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.mac-glitch{position:relative;display:inline-block;font-family:Fira Code,JetBrains Mono,monospace;font-weight:900;letter-spacing:.12em;color:var(--text)}.mac-glitch:before,.mac-glitch:after{content:attr(data-text);position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;opacity:.65}.mac-glitch:before{color:var(--accent);clip-path:inset(0 0 62% 0);text-shadow:-2px 0 rgba(224,85,85,.42);animation:macGlitchA 3.2s infinite linear alternate-reverse}.mac-glitch:after{color:var(--success);clip-path:inset(58% 0 0 0);text-shadow:2px 0 rgba(58,125,68,.38);animation:macGlitchB 2.7s infinite linear alternate-reverse}@keyframes macGlitchA{0%,to{transform:translate(0);clip-path:inset(0 0 62% 0)}25%{transform:translate(-2px,1px);clip-path:inset(12% 0 50% 0)}50%{transform:translate(2px,-1px);clip-path:inset(32% 0 38% 0)}75%{transform:translate(-1px,2px);clip-path:inset(5% 0 70% 0)}}@keyframes macGlitchB{0%,to{transform:translate(0);clip-path:inset(58% 0 0 0)}30%{transform:translate(2px,-2px);clip-path:inset(48% 0 16% 0)}60%{transform:translate(-2px,1px);clip-path:inset(68% 0 6% 0)}}.glitch{position:relative;display:inline-block;font-family:Courier New,monospace;font-weight:900;letter-spacing:.15em;color:var(--text, var(--fg))}.glitch:before,.glitch:after{content:attr(data-text);position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.glitch:before{color:var(--text, var(--fg));animation:glitch-1 3s infinite linear alternate-reverse;clip-path:inset(0 0 65% 0);text-shadow:-2px 0 rgba(255,0,0,.35)}.glitch:after{color:var(--text, var(--fg));animation:glitch-2 2.5s infinite linear alternate-reverse;clip-path:inset(65% 0 0 0);text-shadow:2px 0 rgba(0,0,255,.35)}@keyframes glitch-1{0%,to{clip-path:inset(0 0 65% 0);transform:translate(0)}20%{clip-path:inset(10% 0 55% 0);transform:translate(-3px,1px)}40%{clip-path:inset(30% 0 40% 0);transform:translate(2px,-1px)}60%{clip-path:inset(5% 0 70% 0);transform:translate(-1px,2px)}80%{clip-path:inset(20% 0 50% 0);transform:translate(3px)}}@keyframes glitch-2{0%,to{clip-path:inset(65% 0 0 0);transform:translate(0)}25%{clip-path:inset(50% 0 10% 0);transform:translate(2px,-2px)}50%{clip-path:inset(70% 0 5% 0);transform:translate(-3px,1px)}75%{clip-path:inset(60% 0 15% 0);transform:translate(1px,2px)}}.hover\:border-dark-500:hover{border-color:var(--border-strong)}.hover\:border-mac-500:hover{border-color:var(--accent)}.hover\:bg-dark-500:hover{background-color:var(--border-strong)}.hover\:bg-green-900\/70:hover{background-color:#14532db3}.hover\:bg-orange-900\/70:hover{background-color:#7c2d12b3}.hover\:bg-red-900\/70:hover{background-color:#7f1d1db3}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-300:hover{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.hover\:opacity-100:hover,.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:768px){.md\:col-span-2{grid-column:span 2 / span 2}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr}.lg\:grid-cols-\[360px_1fr\]{grid-template-columns:360px 1fr}}.\[\&_code\]\:text-mac-300 code{color:var(--accent)}.\[\&_h1\]\:text-gray-100 h1,.\[\&_h2\]\:text-gray-100 h2{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.\[\&_h3\]\:text-gray-200 h3{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.\[\&_ol\]\:list-decimal ol{list-style-type:decimal}.\[\&_ol\]\:pl-4 ol{padding-left:1rem}.\[\&_pre\]\:mt-2 pre{margin-top:.5rem}.\[\&_pre_code\]\:text-gray-200 pre code{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.\[\&_strong\]\:text-gray-100 strong{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.\[\&_ul\]\:list-disc ul{list-style-type:disc}.\[\&_ul\]\:pl-4 ul{padding-left:1rem}.sidebar.svelte-129hoe0{position:relative;z-index:2;display:flex;flex-direction:column;height:100vh;background:var(--surface);border-right:1px solid var(--border);flex-shrink:0;transition:width .15s ease;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none}.sidebar.dragging.svelte-129hoe0{transition:none}.logo-area.svelte-129hoe0{display:flex;align-items:center;gap:10px;padding:14px;border-bottom:1px solid var(--border);flex-shrink:0;overflow:hidden}.sidebar-mac.svelte-129hoe0{font-size:16px;flex-shrink:0}.logo-sub.svelte-129hoe0{font-size:10px;color:var(--text3);white-space:nowrap;margin-top:1px}.nav-section.svelte-129hoe0{flex:1;padding:8px 6px;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column;gap:2px}.nav-section.svelte-129hoe0::-webkit-scrollbar{width:4px}.nav-section.svelte-129hoe0::-webkit-scrollbar-track{background:transparent}.nav-section.svelte-129hoe0::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:2px}.nav-link.svelte-129hoe0{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;color:var(--text2);font-size:13px;font-weight:500;text-decoration:none;cursor:pointer;background:none;border:none;width:100%;transition:background .15s,color .15s;white-space:nowrap;overflow:hidden}.nav-link.svelte-129hoe0:hover{background:var(--surface2);color:var(--text)}.nav-link.active.svelte-129hoe0{background:#d974491a;color:var(--accent)}.nav-link.active.svelte-129hoe0 .nav-icon:where(.svelte-129hoe0) svg{stroke:var(--accent)}.nav-icon.svelte-129hoe0{width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.nav-icon.svelte-129hoe0 svg{width:18px;height:18px;stroke:currentColor;flex-shrink:0}.nav-label.svelte-129hoe0{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.section-header.svelte-129hoe0{padding:10px 12px 4px;font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.12em;font-weight:600}.section-divider.svelte-129hoe0{height:1px;background:var(--border);margin:6px 8px}.compact.svelte-129hoe0 .nav-link:where(.svelte-129hoe0){justify-content:center;padding:10px;gap:0}.compact.svelte-129hoe0 .logo-area:where(.svelte-129hoe0){justify-content:center;padding:14px 10px}.user-area.svelte-129hoe0{border-top:1px solid var(--border);padding:8px 6px;flex-shrink:0;display:flex;flex-direction:column;gap:2px}.user-info.svelte-129hoe0{display:flex;align-items:center;gap:10px;padding:6px 10px 4px;overflow:hidden}.user-info.compact-user.svelte-129hoe0{justify-content:center;padding:6px 10px}.avatar.svelte-129hoe0{width:28px;height:28px;border-radius:50%;background:#d9744926;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--accent);flex-shrink:0}.user-text.svelte-129hoe0{display:flex;flex-direction:column;min-width:0;overflow:hidden}.user-name.svelte-129hoe0{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.user-role.svelte-129hoe0{font-size:10px;color:var(--text3);text-transform:capitalize;white-space:nowrap}.logout-btn.svelte-129hoe0{color:var(--text3)}.logout-btn.svelte-129hoe0:hover{background:var(--error-bg);color:var(--error)}.resizer.svelte-129hoe0{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;display:flex;align-items:stretch;justify-content:flex-end}.resizer-line.svelte-129hoe0{width:2px;background:transparent;transition:background .2s;border-radius:1px;margin-right:1px}.resizer.svelte-129hoe0:hover .resizer-line:where(.svelte-129hoe0),.dragging.svelte-129hoe0 .resizer:where(.svelte-129hoe0) .resizer-line:where(.svelte-129hoe0){background:var(--accent)}.mac-backdrop.svelte-1qbfbt1{position:fixed;top:0;right:0;bottom:0;left:0;z-index:0;pointer-events:none;overflow:hidden;opacity:.72}.word.svelte-1qbfbt1{position:absolute;color:var(--accent);opacity:.045;font-weight:800;letter-spacing:.12em;font-family:Inter,system-ui,sans-serif;animation:svelte-1qbfbt1-drift 10s ease-in-out infinite alternate}.scanlines.svelte-1qbfbt1{position:absolute;top:0;right:0;bottom:0;left:0;background:repeating-linear-gradient(to bottom,transparent 0,transparent 5px,rgba(217,116,73,.025) 6px);-webkit-mask-image:linear-gradient(to bottom,transparent,#000 18%,#000 80%,transparent);mask-image:linear-gradient(to bottom,transparent,#000 18%,#000 80%,transparent)}@keyframes svelte-1qbfbt1-drift{0%{translate:0 0}to{translate:10px -8px}}@media(prefers-reduced-motion:reduce){.word.svelte-1qbfbt1{animation:none}}.pretext-bg.svelte-1w4fu1y{position:fixed;top:0;right:0;bottom:0;left:0;z-index:0;pointer-events:auto;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.pt-scanlines.svelte-1w4fu1y{position:absolute;top:0;right:0;bottom:0;left:0;background:repeating-linear-gradient(to bottom,transparent 0,transparent 5px,rgba(217,116,73,.018) 6px);-webkit-mask-image:linear-gradient(to bottom,transparent,#000 18%,#000 80%,transparent);mask-image:linear-gradient(to bottom,transparent,#000 18%,#000 80%,transparent);pointer-events:none}.pt-word.svelte-1w4fu1y{position:absolute;color:var(--accent, #D97449);font-family:Courier New,monospace;letter-spacing:.15em;pointer-events:none;will-change:transform,opacity;transition:opacity .3s ease;text-transform:uppercase}@media(prefers-reduced-motion:reduce){.pt-word.svelte-1w4fu1y{transition:none!important}}.loading-overlay.svelte-1qpkoic{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:#0000002e;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);animation:svelte-1qpkoic-fadeIn .25s ease}.loading-content.svelte-1qpkoic{display:flex;flex-direction:column;align-items:center;gap:18px}.loading-msg.svelte-1qpkoic{font-size:14px;font-weight:500;color:var(--text, #1A1A1A);letter-spacing:.03em;opacity:.85;margin:0;text-align:center;max-width:280px}@keyframes svelte-1qpkoic-fadeIn{0%{opacity:0}to{opacity:1}}.app-shell.svelte-12qhfyh{display:flex;height:100vh;overflow:hidden;background:var(--bg)}.app-main.svelte-12qhfyh{flex:1;overflow:auto;min-width:0;background:var(--bg);color:var(--text);position:relative;z-index:1}
|
frontend/build/_app/immutable/assets/12.BQVrdhQn.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.theme-toggle.svelte-1cmi4dh{position:fixed;bottom:24px;right:24px;z-index:100;width:44px;height:44px;border:1px solid var(--border, rgba(0,0,0,.12));border-radius:50%;background:var(--surface, #fff);box-shadow:0 2px 12px #0000001f;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:transform .2s ease,box-shadow .2s ease,background-color .35s ease;outline:none;padding:0}.theme-toggle.svelte-1cmi4dh:hover{transform:scale(1.08);box-shadow:0 4px 20px #d9744940}.theme-toggle.svelte-1cmi4dh:active{transform:scale(.95)}.icon-wrap.svelte-1cmi4dh{position:relative;width:18px;height:18px}.sun.svelte-1cmi4dh,.moon.svelte-1cmi4dh{position:absolute;top:0;right:0;bottom:0;left:0;transition:opacity .35s ease,transform .35s ease}.sun.svelte-1cmi4dh{opacity:1;transform:rotate(0) scale(1);color:var(--accent, #D97449)}.moon.svelte-1cmi4dh{opacity:0;transform:rotate(-90deg) scale(.6);color:var(--accent, #D97449)}.icon-wrap.dark.svelte-1cmi4dh .sun:where(.svelte-1cmi4dh){opacity:0;transform:rotate(90deg) scale(.6)}.icon-wrap.dark.svelte-1cmi4dh .moon:where(.svelte-1cmi4dh){opacity:1;transform:rotate(0) scale(1)}.login-root.svelte-1x05zx6{position:fixed;top:0;right:0;bottom:0;left:0;background:var(--bg);display:flex;align-items:center;justify-content:center;overflow:hidden;font-family:Inter,system-ui,sans-serif;-webkit-tap-highlight-color:transparent}.orb.svelte-1x05zx6{position:absolute;border-radius:50%;filter:blur(80px);pointer-events:none;animation:svelte-1x05zx6-orbFloat 8s ease-in-out infinite alternate}.orb-1.svelte-1x05zx6{width:360px;height:360px;background:radial-gradient(circle,rgba(217,116,73,.14) 0%,transparent 70%);top:-80px;left:-80px}.orb-2.svelte-1x05zx6{width:280px;height:280px;background:radial-gradient(circle,rgba(196,98,61,.1) 0%,transparent 70%);bottom:-60px;right:-60px;animation-delay:-4s}@keyframes svelte-1x05zx6-orbFloat{0%{transform:translate(0) scale(1)}to{transform:translate(30px,20px) scale(1.08)}}.words-layer.svelte-1x05zx6{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden}.wm-word.svelte-1x05zx6{position:absolute;font-weight:900;letter-spacing:.15em;color:var(--accent);font-family:Courier New,monospace;pointer-events:none}.card-wrap.svelte-1x05zx6{position:relative;z-index:10;width:100%;max-width:410px;margin:0 16px;background:var(--surface);border:1px solid var(--border-strong);border-radius:20px;padding:36px 32px 28px;box-shadow:0 8px 40px #0000001f,var(--shadow);animation:svelte-1x05zx6-cardEnter .45s cubic-bezier(.16,1,.3,1) both}@keyframes svelte-1x05zx6-cardEnter{0%{transform:translateY(20px);opacity:.4}to{opacity:1;transform:translateY(0)}}.card-wrap.shake{animation:svelte-1x05zx6-shake .52s cubic-bezier(.36,.07,.19,.97) both!important}@keyframes svelte-1x05zx6-shake{10%,90%{transform:translate(-3px)}20%,80%{transform:translate(4px)}30%,50%,70%{transform:translate(-5px)}40%,60%{transform:translate(5px)}}.card-header.svelte-1x05zx6{text-align:center;margin-bottom:20px}.mac-title.svelte-1x05zx6{font-size:clamp(2.5rem,6vw,4rem);margin:0 0 6px;animation:svelte-1x05zx6-cardEnter .55s .05s cubic-bezier(.16,1,.3,1) both}.sub.svelte-1x05zx6{font-size:12px;color:var(--text3);letter-spacing:.02em;margin:0;animation:svelte-1x05zx6-cardEnter .55s .12s cubic-bezier(.16,1,.3,1) both}.view-hint.svelte-1x05zx6{font-size:13px;color:var(--text2);text-align:center;margin:0 0 18px;line-height:1.5}.back-btn.svelte-1x05zx6{display:inline-flex;align-items:center;gap:5px;background:none;border:none;cursor:pointer;color:var(--text3);font-size:12px;font-family:inherit;padding:4px 0;margin-bottom:12px;transition:color .15s}.back-btn.svelte-1x05zx6:hover{color:var(--text2)}.form.svelte-1x05zx6{display:flex;flex-direction:column;gap:14px}.field-wrap.svelte-1x05zx6{position:relative}.float-label.svelte-1x05zx6{position:absolute;top:50%;left:40px;transform:translateY(-50%);font-size:13px;color:var(--text3);pointer-events:none;transition:all .2s cubic-bezier(.16,1,.3,1);z-index:2}.field-wrap.focused.svelte-1x05zx6 .float-label:where(.svelte-1x05zx6),.field-wrap.filled.svelte-1x05zx6 .float-label:where(.svelte-1x05zx6){top:-8px;left:10px;font-size:10px;letter-spacing:.06em;color:var(--accent);background:var(--surface);padding:0 5px;border-radius:3px;text-transform:uppercase;font-weight:600}.field-inner.svelte-1x05zx6{position:relative;display:flex;align-items:center}.field-icon.svelte-1x05zx6{position:absolute;left:13px;color:var(--text3);transition:color .2s;pointer-events:none;z-index:1}.field-wrap.focused.svelte-1x05zx6 .field-icon:where(.svelte-1x05zx6){color:var(--accent)}.field-input.svelte-1x05zx6{width:100%;background:var(--surface2);border:1px solid var(--border);border-radius:11px;padding:13px 13px 13px 38px;font-size:14px;color:var(--text);outline:none;font-family:inherit;transition:border-color .2s,box-shadow .2s}.field-input.svelte-1x05zx6::-moz-placeholder{color:transparent}.field-input.svelte-1x05zx6::placeholder{color:transparent}.field-wrap.focused.svelte-1x05zx6 .field-input:where(.svelte-1x05zx6){border-color:var(--accent);box-shadow:0 0 0 3px #d9744926;background:var(--surface)}.field-input.mismatch.svelte-1x05zx6{border-color:var(--error)}.field-hint.svelte-1x05zx6{display:block;font-size:11px;color:var(--text3);margin-top:4px;padding-left:4px}.eye-btn.svelte-1x05zx6{position:absolute;right:11px;background:none;border:none;cursor:pointer;color:var(--text3);padding:4px;display:flex;align-items:center;border-radius:5px;outline:none;transition:color .2s}.eye-btn.svelte-1x05zx6:hover{color:var(--text2)}.pw-strength.svelte-1x05zx6{display:flex;align-items:center;gap:8px}.pw-bar.svelte-1x05zx6{flex:1;height:4px;background:var(--surface3);border-radius:2px;overflow:hidden}.pw-fill.svelte-1x05zx6{height:100%;border-radius:2px;transition:width .3s,background .3s}.pw-label.svelte-1x05zx6{font-size:11px;font-weight:600;width:40px;text-align:right}.err-msg.svelte-1x05zx6{display:flex;align-items:center;gap:8px;padding:10px 13px;border-radius:10px;background:var(--error-bg);border:1px solid var(--error);color:var(--error);font-size:13px}.sign-btn.svelte-1x05zx6{width:100%;display:flex;align-items:center;justify-content:center;gap:8px;padding:13px 20px;background:var(--accent);border:none;border-radius:11px;color:#fff;font-size:14px;font-weight:600;cursor:pointer;letter-spacing:.03em;box-shadow:0 4px 18px #d974494d;font-family:inherit;outline:none;transition:background .2s,box-shadow .2s}.sign-btn.svelte-1x05zx6:hover:not(:disabled){background:var(--accent-hover);box-shadow:0 6px 26px #d9744973}.sign-btn.svelte-1x05zx6:disabled{opacity:.4;cursor:not-allowed;box-shadow:none}.spinner.svelte-1x05zx6{width:15px;height:15px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:svelte-1x05zx6-spin .6s linear infinite}@keyframes svelte-1x05zx6-spin{to{transform:rotate(360deg)}}.divider.svelte-1x05zx6{display:flex;align-items:center;gap:10px;margin:14px 0 10px}.divider.svelte-1x05zx6:before,.divider.svelte-1x05zx6:after{content:"";flex:1;height:1px;background:var(--border)}.divider.svelte-1x05zx6 span:where(.svelte-1x05zx6){font-size:11px;color:var(--text3)}.alt-btn.svelte-1x05zx6{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;padding:11px 16px;background:none;border:1px solid var(--border);border-radius:11px;cursor:pointer;font-size:13px;font-weight:500;color:var(--text2);font-family:inherit;transition:border-color .2s,color .2s,background .2s}.alt-btn.svelte-1x05zx6:hover{border-color:var(--accent);color:var(--accent);background:#d974490a}.card-footer.svelte-1x05zx6{display:flex;align-items:center;justify-content:space-between;margin-top:20px;padding-top:14px;border-top:1px solid var(--border)}.version.svelte-1x05zx6{font-size:11px;color:var(--text3);font-family:Courier New,monospace}.locale-wrap.svelte-1x05zx6{position:relative}.locale-btn.svelte-1x05zx6{display:flex;align-items:center;gap:5px;background:none;border:none;cursor:pointer;color:var(--text3);font-size:12px;padding:4px 7px;border-radius:6px;font-family:inherit;transition:color .2s,background .2s}.locale-btn.svelte-1x05zx6:hover{color:var(--text2);background:var(--surface2)}.locale-dropdown.svelte-1x05zx6{position:absolute;bottom:calc(100% + 6px);right:0;background:var(--surface);border:1px solid var(--border-strong);border-radius:10px;box-shadow:0 8px 32px #00000026;z-index:50;width:180px;max-height:300px;overflow-y:auto;overscroll-behavior:contain;padding:4px;-webkit-overflow-scrolling:touch}.locale-dropdown.svelte-1x05zx6::-webkit-scrollbar{width:5px}.locale-dropdown.svelte-1x05zx6::-webkit-scrollbar-thumb{background:var(--accent);border-radius:6px}.locale-item.svelte-1x05zx6{display:block;width:100%;text-align:left;background:none;border:none;padding:8px 12px;font-size:13px;color:var(--text2);cursor:pointer;border-radius:7px;font-family:inherit;transition:background .15s,color .15s}.locale-item.svelte-1x05zx6:hover{background:var(--surface2);color:var(--text)}.locale-item.active.svelte-1x05zx6{color:var(--accent);font-weight:600;background:var(--surface2)}.locale-overlay.svelte-1x05zx6{position:fixed;top:0;right:0;bottom:0;left:0;z-index:40}@media(max-width:480px){.card-wrap.svelte-1x05zx6{padding:28px 20px 22px;border-radius:16px}}
|
frontend/build/_app/immutable/assets/13.M6eN8M_c.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.nb-root.svelte-t5mrr1{display:flex;height:100%;background:var(--bg);overflow:hidden}.nb-root.sb-dragging.svelte-t5mrr1{cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;user-select:none}.nb-sidebar.svelte-t5mrr1{position:relative;display:flex;flex-direction:column;background:var(--surface);border-right:1px solid var(--border);flex-shrink:0;height:100%;overflow:hidden;transition:none}.nb-sb-header.svelte-t5mrr1{display:flex;align-items:center;padding:12px 14px;border-bottom:1px solid var(--border);flex-shrink:0}.nb-sb-label.svelte-t5mrr1{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--text3)}.nb-sb-body.svelte-t5mrr1{flex:1;overflow-y:auto;padding:10px 8px}.nb-sb-body.svelte-t5mrr1::-webkit-scrollbar{width:4px}.nb-sb-body.svelte-t5mrr1::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:2px}.nb-new-form.svelte-t5mrr1{display:flex;flex-direction:column;gap:6px;padding:2px 4px 12px}.nb-input.svelte-t5mrr1{background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font-size:12px;color:var(--text);outline:none;width:100%;font-family:inherit}.nb-input.svelte-t5mrr1:focus{border-color:var(--accent)}.nb-select.svelte-t5mrr1{background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:5px 8px;font-size:12px;color:var(--text);outline:none;cursor:pointer;width:100%;font-family:inherit}.nb-btn-accent.svelte-t5mrr1{display:flex;align-items:center;justify-content:center;gap:5px;background:var(--accent);color:#fff;border:none;border-radius:7px;padding:7px 12px;font-size:12px;font-weight:600;cursor:pointer;width:100%;font-family:inherit}.nb-btn-accent.svelte-t5mrr1:hover{background:var(--accent-hover)}.nb-section-label.svelte-t5mrr1{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--text3);padding:2px 6px 6px}.nb-loader-row.svelte-t5mrr1{display:flex;justify-content:center;padding:16px 0}.nb-empty-list.svelte-t5mrr1{font-size:12px;color:var(--text3);padding:4px 6px}.nb-list-item.svelte-t5mrr1{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px;border-radius:7px;border:1px solid transparent;cursor:pointer;background:none;text-align:left;color:var(--text2);margin-bottom:2px;font-family:inherit}.nb-list-item.svelte-t5mrr1:hover{background:var(--surface2);color:var(--text)}.nb-list-item.active.svelte-t5mrr1{background:#d9744914;border-color:#d974492e;color:var(--accent)}.nb-list-icon.svelte-t5mrr1{width:14px;height:14px;flex-shrink:0}.nb-list-text.svelte-t5mrr1{display:flex;flex-direction:column;min-width:0}.nb-list-title.svelte-t5mrr1{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.nb-list-meta.svelte-t5mrr1{font-size:10px;color:var(--text3);margin-top:2px;display:flex;align-items:center;gap:4px}.nb-list-dot.svelte-t5mrr1{width:6px;height:6px;border-radius:50%;flex-shrink:0}.nb-sb-resizer.svelte-t5mrr1{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;display:flex;align-items:center;justify-content:flex-end}.nb-sb-resizer-pill.svelte-t5mrr1{width:3px;height:40px;border-radius:2px;background:transparent;transition:background .2s;margin-right:1px}.nb-sb-resizer.svelte-t5mrr1:hover .nb-sb-resizer-pill:where(.svelte-t5mrr1),.nb-sb-resizer.active.svelte-t5mrr1 .nb-sb-resizer-pill:where(.svelte-t5mrr1){background:var(--accent)}.nb-main.svelte-t5mrr1{flex:1;min-width:0;overflow-y:auto;background:var(--bg);display:flex;flex-direction:column}.nb-empty-state.svelte-t5mrr1{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;color:var(--text3)}.nb-empty-state.svelte-t5mrr1 p:where(.svelte-t5mrr1){font-size:13px}.nb-toolbar.svelte-t5mrr1{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 24px;border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:5;flex-shrink:0}.nb-toolbar-left.svelte-t5mrr1{display:flex;align-items:center;gap:10px}.nb-toolbar-right.svelte-t5mrr1{display:flex;align-items:center;gap:8px}.nb-nb-title.svelte-t5mrr1{font-size:14px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px}.nb-lang-badge.svelte-t5mrr1{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:500;border:1px solid;border-radius:20px;padding:2px 9px;opacity:.85}.nb-lang-dot.svelte-t5mrr1{width:7px;height:7px;border-radius:50%;flex-shrink:0}.nb-btn-ghost.svelte-t5mrr1{display:flex;align-items:center;gap:5px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:5px 12px;font-size:12px;font-weight:500;color:var(--text2);cursor:pointer;font-family:inherit}.nb-btn-ghost.svelte-t5mrr1:hover{background:var(--surface3);color:var(--text)}.nb-cells.svelte-t5mrr1{padding:16px 24px 48px;max-width:880px;width:100%;margin:0 auto;display:flex;flex-direction:column;gap:6px}.nb-no-cells.svelte-t5mrr1{text-align:center;color:var(--text3);font-size:13px;padding:28px;background:var(--surface);border:1px dashed var(--border);border-radius:8px;margin-bottom:6px}.nb-cell.svelte-t5mrr1{border:1px solid transparent;border-radius:8px;overflow:hidden;background:var(--surface);cursor:text}.nb-cell.svelte-t5mrr1:hover{border-color:var(--border)}.nb-cell.nb-cell-active.svelte-t5mrr1{border-color:#d9744973}.nb-cell.nb-cell-running.svelte-t5mrr1{border-color:#d97449a6;animation:svelte-t5mrr1-cell-pulse 1.4s ease-in-out infinite}@keyframes svelte-t5mrr1-cell-pulse{0%,to{box-shadow:0 0 #d9744900}50%{box-shadow:0 0 0 4px #d974491f}}.nb-cell-hdr.svelte-t5mrr1{display:flex;align-items:center;gap:5px;padding:5px 10px;border-bottom:1px solid var(--border);background:var(--surface2);min-height:32px}.nb-grip.svelte-t5mrr1{color:var(--text3);cursor:grab;flex-shrink:0;opacity:.45}.nb-exec.svelte-t5mrr1{font-size:10px;font-family:Courier New,monospace;color:var(--text3);width:32px;text-align:right;flex-shrink:0}.nb-exec-spin.svelte-t5mrr1{animation:svelte-t5mrr1-spin-blink .6s step-end infinite}@keyframes svelte-t5mrr1-spin-blink{0%,to{opacity:1}50%{opacity:0}}.nb-lang-sel.svelte-t5mrr1{background:var(--surface3);border:1px solid var(--border);border-radius:4px;padding:2px 6px;font-size:11px;color:var(--text2);outline:none;cursor:pointer;font-family:inherit}.nb-lang-pip.svelte-t5mrr1{width:8px;height:8px;border-radius:50%;flex-shrink:0}.nb-spacer.svelte-t5mrr1{flex:1}.nb-cell-actions.svelte-t5mrr1{display:flex;align-items:center;gap:1px;opacity:0;transition:opacity .15s}.nb-cell.svelte-t5mrr1:hover .nb-cell-actions:where(.svelte-t5mrr1),.nb-cell.nb-cell-active.svelte-t5mrr1 .nb-cell-actions:where(.svelte-t5mrr1){opacity:1}.nb-act.svelte-t5mrr1{width:26px;height:26px;border:none;background:none;border-radius:5px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text3);padding:0}.nb-act.svelte-t5mrr1:hover{background:var(--surface3);color:var(--text)}.nb-act.svelte-t5mrr1:disabled{opacity:.35;cursor:not-allowed}.nb-run.svelte-t5mrr1{color:var(--success)}.nb-run.svelte-t5mrr1:hover{background:var(--success-bg);color:var(--success)}.nb-del.svelte-t5mrr1:hover{background:var(--error-bg);color:var(--error)}.nb-editor.svelte-t5mrr1{width:100%;background:transparent;border:none;outline:none;resize:none;font-size:13px;line-height:1.65;color:var(--text);padding:12px 16px;min-height:80px;display:block;overflow:hidden;font-family:inherit}.nb-code-editor.svelte-t5mrr1{background:var(--code-bg);font-family:Courier New,JetBrains Mono,Fira Code,monospace;font-size:13px;-moz-tab-size:4;-o-tab-size:4;tab-size:4;caret-color:var(--accent)}.nb-md-editor.svelte-t5mrr1{background:var(--surface2);font-family:inherit}.nb-md-preview.svelte-t5mrr1{padding:12px 20px;cursor:pointer;min-height:44px;font-size:14px;line-height:1.75;color:var(--text2)}.nb-md-preview.svelte-t5mrr1:hover{background:#00000004}.nb-md-preview p{margin:.3em 0}.nb-md-preview .md-h1{font-size:1.5em;font-weight:700;color:var(--text);margin:.6em 0 .3em}.nb-md-preview .md-h2{font-size:1.25em;font-weight:600;color:var(--text);margin:.5em 0 .25em}.nb-md-preview .md-h3{font-size:1.1em;font-weight:600;color:var(--text);margin:.4em 0 .2em}.nb-md-preview .md-pre{background:var(--code-bg);padding:10px 14px;border-radius:6px;overflow-x:auto;margin:.5em 0;font-family:Courier New,monospace;font-size:12.5px}.nb-md-preview .md-code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-family:Courier New,monospace;font-size:.88em}.nb-md-preview .md-li{margin-left:1.5em;display:list-item;list-style-type:disc}.nb-md-preview .md-oli{list-style-type:decimal}.nb-output.svelte-t5mrr1{border-top:1px solid var(--border);padding:10px 16px;background:var(--bg);display:flex;flex-direction:column;gap:4px}.nb-out.svelte-t5mrr1{font-family:Courier New,monospace;font-size:12.5px;line-height:1.55;white-space:pre-wrap;word-break:break-all;margin:0}.nb-out-stream.svelte-t5mrr1{color:var(--text)}.nb-out-err.svelte-t5mrr1{color:var(--error)}.nb-out-result.svelte-t5mrr1{color:var(--text2)}.nb-out-error-block.svelte-t5mrr1{display:flex;flex-direction:column;gap:2px}.nb-err-name.svelte-t5mrr1{font-size:11px;font-weight:700;color:var(--error);font-family:Courier New,monospace}.nb-add-row.svelte-t5mrr1{display:flex;justify-content:center;gap:8px;padding:16px 0 4px}.nb-add-btn.svelte-t5mrr1{display:inline-flex;align-items:center;gap:5px;padding:6px 16px;border:1px dashed var(--border);background:none;border-radius:7px;font-size:12px;font-weight:500;color:var(--text3);cursor:pointer;font-family:inherit}.nb-add-btn.svelte-t5mrr1:hover{border-color:var(--accent);color:var(--accent)}.nb-add-md.svelte-t5mrr1:hover{border-color:#7c6ff7;color:#7c6ff7}
|
frontend/build/_app/immutable/assets/2.DfxUCL9T.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.morph-overlay.svelte-5m2nwc{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10000;background:#0e0d0c;display:flex;align-items:center;justify-content:center;transition:opacity .4s ease}.morph-overlay.hidden.svelte-5m2nwc{pointer-events:none}.morph-canvas.svelte-5m2nwc{width:100%;height:100%;position:absolute;top:0;right:0;bottom:0;left:0}.morph-loader.svelte-5m2nwc{position:absolute;z-index:2;display:flex;align-items:center;justify-content:center}.morph-skip.svelte-5m2nwc{position:absolute;bottom:5%;right:4%;z-index:3;font-size:12px;color:#fff3;background:none;border:none;cursor:pointer;letter-spacing:.08em;font-family:inherit;padding:4px 8px;border-radius:4px;transition:color .2s,background .2s}.morph-skip.svelte-5m2nwc:hover{color:#ffffff80;background:#ffffff0d}
|
frontend/build/_app/immutable/assets/5.Bb_sFVPM.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.chat-root.svelte-23dtxz{display:flex;height:100%;background:var(--bg);overflow:hidden}.chat-sidebar.svelte-23dtxz{width:220px;flex-shrink:0;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;height:100%}.chat-sb-top.svelte-23dtxz{padding:12px;border-bottom:1px solid var(--border);flex-shrink:0}.chat-new-btn.svelte-23dtxz{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;background:var(--accent);color:#fff;border:none;border-radius:7px;padding:8px 14px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}.chat-new-btn.svelte-23dtxz:hover{background:var(--accent-hover)}.chat-conv-list.svelte-23dtxz{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:2px}.chat-conv-list.svelte-23dtxz::-webkit-scrollbar{width:4px}.chat-conv-list.svelte-23dtxz::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:2px}.chat-conv-item.svelte-23dtxz{width:100%;text-align:left;padding:8px 10px;border-radius:7px;border:none;cursor:pointer;background:none;font-size:12px;font-weight:500;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit}.chat-conv-item.svelte-23dtxz:hover{background:var(--surface2);color:var(--text)}.chat-conv-item.active.svelte-23dtxz{background:#d974491a;color:var(--accent);border:1px solid rgba(217,116,73,.18)}.chat-conv-empty.svelte-23dtxz{font-size:11px;color:var(--text3);text-align:center;padding:16px 8px}.chat-main.svelte-23dtxz{flex:1;min-width:0;display:flex;flex-direction:column;height:100%}.chat-topbar.svelte-23dtxz{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--surface);flex-shrink:0}.chat-conv-title.svelte-23dtxz{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chat-model-row.svelte-23dtxz{display:flex;align-items:center;gap:6px}.chat-model-label.svelte-23dtxz{font-size:11px;color:var(--text3)}.chat-model-sel.svelte-23dtxz{background:var(--surface2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11px;color:var(--text);outline:none;cursor:pointer;font-family:inherit}.chat-messages.svelte-23dtxz{flex:1;overflow-y:auto;padding:20px 24px;display:flex;flex-direction:column;gap:16px}.chat-messages.svelte-23dtxz::-webkit-scrollbar{width:5px}.chat-messages.svelte-23dtxz::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:3px}.chat-empty.svelte-23dtxz{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;text-align:center;padding:32px;margin:auto}.chat-empty-icon.svelte-23dtxz{width:56px;height:56px;border-radius:16px;background:var(--surface2);display:flex;align-items:center;justify-content:center;color:var(--text3);margin-bottom:4px}.chat-empty-title.svelte-23dtxz{font-size:16px;font-weight:600;color:var(--text)}.chat-empty-hint.svelte-23dtxz{font-size:13px;color:var(--text3);max-width:340px}.chat-suggestions.svelte-23dtxz{display:grid;grid-template-columns:1fr 1fr;gap:8px;width:100%;max-width:480px;margin-top:8px}.chat-suggest-btn.svelte-23dtxz{text-align:left;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:var(--surface);font-size:12px;color:var(--text2);cursor:pointer;line-height:1.4;font-family:inherit}.chat-suggest-btn.svelte-23dtxz:hover{border-color:var(--accent);color:var(--text);background:var(--surface2)}.chat-input-area.svelte-23dtxz{border-top:1px solid var(--border);background:var(--surface);padding:14px 20px;flex-shrink:0}.chat-input-box.svelte-23dtxz{display:flex;align-items:flex-end;gap:10px;background:var(--surface2);border:1px solid var(--border);border-radius:12px;padding:10px 12px 10px 16px}.chat-input-box.svelte-23dtxz:focus-within{border-color:var(--accent)}.chat-textarea.svelte-23dtxz{flex:1;background:transparent;border:none;outline:none;resize:none;font-size:14px;line-height:1.5;color:var(--text);min-height:1.5rem;max-height:200px;font-family:inherit}.chat-textarea.svelte-23dtxz::-moz-placeholder{color:var(--text3)}.chat-textarea.svelte-23dtxz::placeholder{color:var(--text3)}.chat-send-btn.svelte-23dtxz{flex-shrink:0;width:34px;height:34px;border-radius:8px;border:none;background:var(--accent);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center}.chat-send-btn.svelte-23dtxz:hover:not(:disabled){background:var(--accent-hover)}.chat-send-btn.svelte-23dtxz:disabled{opacity:.4;cursor:not-allowed}.chat-footer-note.svelte-23dtxz{font-size:11px;color:var(--text3);text-align:center;margin-top:8px}
|
frontend/build/_app/immutable/assets/8.D2JiE0Gd.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.dash.svelte-x1i5gj{padding:24px;max-width:1100px;margin:0 auto;display:flex;flex-direction:column;gap:20px}.dash-header.svelte-x1i5gj{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.dash-title.svelte-x1i5gj{font-size:22px;font-weight:700;color:var(--text);line-height:1.2}.dash-sub.svelte-x1i5gj{font-size:13px;color:var(--text3);margin-top:4px}.dash-sub.svelte-x1i5gj strong:where(.svelte-x1i5gj){color:var(--text2);font-weight:600}.role-badge.svelte-x1i5gj{display:inline-block;padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;text-transform:capitalize;letter-spacing:.02em}.role-admin.svelte-x1i5gj{background:var(--error-bg);color:var(--error)}.role-faculty.svelte-x1i5gj{background:var(--warning-bg);color:var(--warning)}.role-student.svelte-x1i5gj{background:#d974491f;color:var(--accent)}.dash-header-actions.svelte-x1i5gj{display:flex;align-items:center;gap:12px;flex-shrink:0}.dash-date.svelte-x1i5gj{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--text3);background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:6px 10px;white-space:nowrap}.dash-loading.svelte-x1i5gj{display:flex;flex-direction:column;gap:20px}.skeleton.svelte-x1i5gj{height:96px;background:var(--surface2)!important;animation:svelte-x1i5gj-pulse 1.5s ease-in-out infinite}@keyframes svelte-x1i5gj-pulse{0%,to{opacity:1}50%{opacity:.5}}.dash-error.svelte-x1i5gj{display:flex;align-items:center;gap:8px;padding:14px 16px;background:var(--error-bg);border:1px solid var(--error);border-radius:10px;color:var(--error);font-size:13px}.stat-grid.svelte-x1i5gj{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}@media(max-width:900px){.stat-grid.svelte-x1i5gj{grid-template-columns:repeat(2,1fr)}}@media(max-width:500px){.stat-grid.svelte-x1i5gj{grid-template-columns:1fr}}.stat-card.svelte-x1i5gj{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;display:flex;align-items:flex-start;gap:12px;box-shadow:var(--shadow)}.stat-icon-wrap.svelte-x1i5gj{width:38px;height:38px;border-radius:10px;background:var(--surface2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;color:var(--text3);flex-shrink:0}.accent-icon.svelte-x1i5gj{background:#d974491f;border-color:#d9744933;color:var(--accent)}.stat-body.svelte-x1i5gj{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.stat-label.svelte-x1i5gj{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text3)}.stat-value.svelte-x1i5gj{font-size:24px;font-weight:700;color:var(--text);line-height:1.1}.stat-bar-track.svelte-x1i5gj{height:3px;background:var(--surface3);border-radius:2px;overflow:hidden;margin-top:6px}.stat-bar-fill.svelte-x1i5gj{height:100%;border-radius:2px;transition:width .6s ease}.stat-sub.svelte-x1i5gj{font-size:11px;color:var(--text3);margin-top:2px}.section-card.svelte-x1i5gj{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px 20px;box-shadow:var(--shadow)}.section-header.svelte-x1i5gj{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:8px}.section-title.svelte-x1i5gj{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--text)}.section-sub.svelte-x1i5gj{font-size:11px;color:var(--text3)}.quota-rings.svelte-x1i5gj{display:flex;gap:32px;flex-wrap:wrap}.quota-ring-item.svelte-x1i5gj{display:flex;align-items:center;gap:14px}.quota-info.svelte-x1i5gj{display:flex;flex-direction:column;gap:3px}.quota-label.svelte-x1i5gj{font-size:13px;font-weight:500;color:var(--text2)}.quota-used.svelte-x1i5gj{font-size:20px;font-weight:700;color:var(--text)}.quota-limit.svelte-x1i5gj{font-size:12px;font-weight:400;color:var(--text3)}.two-col.svelte-x1i5gj{display:grid;grid-template-columns:1fr 1fr;gap:14px}@media(max-width:780px){.two-col.svelte-x1i5gj{grid-template-columns:1fr}}.heatmap-wrap.svelte-x1i5gj{display:flex;flex-direction:column;gap:8px}.heatmap-grid.svelte-x1i5gj{display:grid;grid-template-columns:repeat(26,1fr);grid-template-rows:repeat(7,1fr);gap:2px}.hm-cell.svelte-x1i5gj{width:100%;padding-bottom:100%;border-radius:2px;cursor:default;min-width:8px;min-height:8px}.heatmap-legend.svelte-x1i5gj{display:flex;align-items:center;gap:3px}.legend-label.svelte-x1i5gj{font-size:10px;color:var(--text3)}.heatmap-empty.svelte-x1i5gj{display:flex;flex-direction:column;align-items:center;gap:4px;padding:16px;color:var(--text3);font-size:12px;text-align:center}.heatmap-empty.svelte-x1i5gj p:where(.svelte-x1i5gj){font-weight:500;color:var(--text2);margin:0}.heatmap-empty.svelte-x1i5gj span:where(.svelte-x1i5gj){font-size:11px}.model-dist.svelte-x1i5gj{display:flex;flex-direction:column;gap:12px}.model-row.svelte-x1i5gj{display:flex;flex-direction:column;gap:4px}.model-row-top.svelte-x1i5gj{display:flex;justify-content:space-between;align-items:center}.model-name.svelte-x1i5gj{font-size:12px;color:var(--text2);font-family:Fira Code,JetBrains Mono,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:70%}.model-pct.svelte-x1i5gj{font-size:11px;color:var(--text3);flex-shrink:0}.model-bar-track.svelte-x1i5gj{height:5px;background:var(--surface3);border-radius:3px;overflow:hidden}.model-bar-fill.svelte-x1i5gj{height:100%;border-radius:3px;transition:width .6s ease}.table-wrap.svelte-x1i5gj{overflow-x:auto}.activity-table.svelte-x1i5gj{width:100%;border-collapse:collapse;font-size:13px}.activity-table.svelte-x1i5gj thead:where(.svelte-x1i5gj) tr:where(.svelte-x1i5gj){border-bottom:1px solid var(--border)}.activity-table.svelte-x1i5gj th:where(.svelte-x1i5gj){text-align:left;padding:6px 12px 8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);white-space:nowrap}.activity-table.svelte-x1i5gj th.num:where(.svelte-x1i5gj){text-align:right}.activity-table.svelte-x1i5gj tbody:where(.svelte-x1i5gj) tr:where(.svelte-x1i5gj){border-bottom:1px solid var(--border)}.activity-table.svelte-x1i5gj tbody:where(.svelte-x1i5gj) tr:where(.svelte-x1i5gj):last-child{border-bottom:none}.activity-table.svelte-x1i5gj tbody:where(.svelte-x1i5gj) tr:where(.svelte-x1i5gj):hover{background:var(--surface2)}.activity-table.svelte-x1i5gj td:where(.svelte-x1i5gj){padding:9px 12px;color:var(--text2);white-space:nowrap}.activity-table.svelte-x1i5gj td.num:where(.svelte-x1i5gj){text-align:right;color:var(--text3)}.activity-table.svelte-x1i5gj td.muted:where(.svelte-x1i5gj){color:var(--text3);font-size:12px}.model-tag.svelte-x1i5gj{display:inline-block;font-family:Fira Code,JetBrains Mono,monospace;font-size:11px;background:var(--surface2);border:1px solid var(--border);border-radius:5px;padding:2px 6px;color:var(--text2);max-width:200px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.empty-hint.svelte-x1i5gj{font-size:13px;color:var(--text3);text-align:center;padding:24px 0 8px}
|
frontend/build/_app/immutable/assets/Loader.CSywfDIO.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.mac-loader.svelte-v1tg6x{display:inline-flex;align-items:center;justify-content:center}.hex-sweep.svelte-v1tg6x{transform-origin:50% 50%;animation:svelte-v1tg6x-hexSweep 2.4s linear infinite}@keyframes svelte-v1tg6x-hexSweep{0%{stroke-dashoffset:0}to{stroke-dashoffset:calc(var(--perim) * -1px)}}.spoke-pulse.svelte-v1tg6x{animation:svelte-v1tg6x-spokePulse 1.8s ease-in-out infinite}@keyframes svelte-v1tg6x-spokePulse{0%{stroke-dashoffset:0;stroke-opacity:0}10%{stroke-opacity:.9}80%{stroke-dashoffset:calc(var(--spoke-len) * -1px);stroke-opacity:0}to{stroke-dashoffset:calc(var(--spoke-len) * -1px);stroke-opacity:0}}.node-glow.svelte-v1tg6x{animation:svelte-v1tg6x-nodeGlow 1.8s ease-in-out infinite}@keyframes svelte-v1tg6x-nodeGlow{0%,to{fill-opacity:0;r:0}40%{fill-opacity:.18}60%{fill-opacity:.08}}.node-dot.svelte-v1tg6x{animation:svelte-v1tg6x-nodeDot 1.8s ease-in-out infinite}@keyframes svelte-v1tg6x-nodeDot{0%,to{fill-opacity:.25}50%{fill-opacity:1}}.center-arc.svelte-v1tg6x{transform-origin:50% 50%;animation:svelte-v1tg6x-centerSpin 1.2s linear infinite}@keyframes svelte-v1tg6x-centerSpin{0%{transform:rotate(-90deg)}to{transform:rotate(270deg)}}.center-core.svelte-v1tg6x{animation:svelte-v1tg6x-corePulse 1.8s ease-in-out infinite}@keyframes svelte-v1tg6x-corePulse{0%,to{fill-opacity:.7;transform:scale(1)}50%{fill-opacity:1;transform:scale(1.15)}}
|
frontend/build/_app/immutable/chunks/B8pdRQVM.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{U as l,aq as c,ae as f,m as b,K as o,ar as d,as as p,g,n as _}from"./CPYeCQyA.js";let s=!1,i=Symbol();function y(e,n,r){const u=r[n]??(r[n]={store:null,source:b(void 0),unsubscribe:f});if(o&&(u.source.label=n),u.store!==e&&!(i in r))if(u.unsubscribe(),u.store=e??null,e==null)u.source.v=void 0,u.unsubscribe=f;else{var a=!0;u.unsubscribe=d(e,t=>{a?u.source.v=t:_(u.source,t)}),a=!1}return e&&i in r?p(e):g(u.source)}function U(){const e={};function n(){l(()=>{for(var r in e)e[r].unsubscribe();c(e,i,{enumerable:!1,value:!0})})}return[e,n]}function D(e){var n=s;try{return s=!1,[e(),s]}finally{s=n}}export{y as a,D as c,U as s};
|
frontend/build/_app/immutable/chunks/BIHI7g3E.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
const e={};export{e as default};
|
frontend/build/_app/immutable/chunks/BOGzIfIj.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var at=e=>{throw TypeError(e)};var Ht=(e,t,n)=>t.has(e)||at("Cannot "+n);var y=(e,t,n)=>(Ht(e,t,"read from private field"),n?n.call(e):t.get(e)),L=(e,t,n)=>t.has(e)?at("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n);import{b as j,_ as Ft}from"./DmxDsgrj.js";import{K as S,ap as He,ae as ot,bm as U,g as A,n as P}from"./CPYeCQyA.js";import{t as we,s as Mt}from"./BprE2qdV.js";class Fe{constructor(t,n){this.status=t,typeof n=="string"?this.body={message:n}:n?this.body=n:this.body={message:`Error: ${t}`}}toString(){return JSON.stringify(this.body)}}class Me{constructor(t,n){try{new Headers({location:n})}catch{throw new Error(`Invalid redirect location ${JSON.stringify(n)}: this string contains characters that cannot be used in HTTP headers`)}this.status=t,this.location=n}}class ve extends Error{constructor(t,n,r){super(r),this.status=t,this.text=n}}new URL("sveltekit-internal://");function Wt(e,t){return e==="/"||t==="ignore"?e:t==="never"?e.endsWith("/")?e.slice(0,-1):e:t==="always"&&!e.endsWith("/")?e+"/":e}function Jt(e){return e.split("%25").map(decodeURI).join("%25")}function Yt(e){for(const t in e)e[t]=decodeURIComponent(e[t]);return e}function je({href:e}){return e.split("#")[0]}function I(){}function zt(...e){let t=5381;for(const n of e)if(typeof n=="string"){let r=n.length;for(;r;)t=t*33^n.charCodeAt(--r)}else if(ArrayBuffer.isView(n)){const r=new Uint8Array(n.buffer,n.byteOffset,n.byteLength);let a=r.length;for(;a;)t=t*33^r[--a]}else throw new TypeError("value must be a string or TypedArray");return(t>>>0).toString(36)}new TextEncoder;new TextDecoder;function Xt(e){if(!j&&globalThis.Buffer){const r=globalThis.Buffer.from(e,"base64");return new Uint8Array(r)}const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r++)n[r]=t.charCodeAt(r);return n}let Qt=0;const st=j?window.fetch:I;if(S&&j){let e=!1;(async()=>{e=new Error().stack.includes("check_stack_trace")})(),window.fetch=(n,r)=>{const a=n instanceof Request?n.url:n.toString(),o=new Error().stack.split(`
|
| 2 |
+
`),s=o.findIndex(f=>f.includes("load@")||f.includes("at load")),i=o.slice(0,s+2).join(`
|
| 3 |
+
`),l=e?i.includes("src/runtime/client/client.js"):Qt,c=r==null?void 0:r.__sveltekit_fetch__;return l&&!c&&console.warn(`Loading ${a} using \`window.fetch\`. For best results, use the \`fetch\` that is passed to your \`load\` function: https://svelte.dev/docs/kit/load#making-fetch-requests`),(n instanceof Request?n.method:(r==null?void 0:r.method)||"GET")!=="GET"&&M.delete(ye(n)),st(n,r)}}else j&&(window.fetch=(e,t)=>((e instanceof Request?e.method:(t==null?void 0:t.method)||"GET")!=="GET"&&M.delete(ye(e)),st(e,t)));const M=new Map;function Zt(e,t){const n=ye(e,t),r=document.querySelector(n);if(r!=null&&r.textContent){r.remove();let{body:a,...o}=JSON.parse(r.textContent);const s=r.getAttribute("data-ttl");return s&&M.set(n,{body:a,init:o,ttl:1e3*Number(s)}),r.getAttribute("data-b64")!==null&&(a=Xt(a)),Promise.resolve(new Response(a,o))}return S?kt(e,t):window.fetch(e,t)}function en(e,t,n){if(M.size>0){const r=ye(e,n),a=M.get(r);if(a){if(performance.now()<a.ttl&&["default","force-cache","only-if-cached",void 0].includes(n==null?void 0:n.cache))return new Response(a.body,a.init);M.delete(r)}}return S?kt(t,n):window.fetch(t,n)}function kt(e,t){const n={...t};return Object.defineProperty(n,"__sveltekit_fetch__",{value:!0,writable:!0,configurable:!0}),window.fetch(e,n)}function ye(e,t){let r=`script[data-sveltekit-fetched][data-url=${JSON.stringify(e instanceof Request?e.url:e)}]`;if(t!=null&&t.headers||t!=null&&t.body){const a=[];t.headers&&a.push([...new Headers(t.headers)].join(",")),t.body&&(typeof t.body=="string"||ArrayBuffer.isView(t.body))&&a.push(t.body),r+=`[data-hash="${zt(...a)}"]`}return r}const tn=/^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;function nn(e){const t=[];return{pattern:e==="/"?/^\/$/:new RegExp(`^${an(e).map(r=>{const a=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(r);if(a)return t.push({name:a[1],matcher:a[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const o=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(r);if(o)return t.push({name:o[1],matcher:o[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!r)return;const s=r.split(/\[(.+?)\](?!\])/);return"/"+s.map((l,c)=>{if(c%2){if(l.startsWith("x+"))return Ce(String.fromCharCode(parseInt(l.slice(2),16)));if(l.startsWith("u+"))return Ce(String.fromCharCode(...l.slice(2).split("-").map(m=>parseInt(m,16))));const d=tn.exec(l);if(!j&&!d)throw new Error(`Invalid param: ${l}. Params and matcher names can only have underscores and alphanumeric characters.`);const[,f,_,g,u]=d;return t.push({name:g,matcher:u,optional:!!f,rest:!!_,chained:_?c===1&&s[0]==="":!1}),_?"([^]*?)":f?"([^/]*)?":"([^/]+?)"}return Ce(l)}).join("")}).join("")}/?$`),params:t}}function rn(e){return e!==""&&!/^\([^)]+\)$/.test(e)}function an(e){return e.slice(1).split("/").filter(rn)}function on(e,t,n){const r={},a=e.slice(1),o=a.filter(i=>i!==void 0);let s=0;for(let i=0;i<t.length;i+=1){const l=t[i];let c=a[i-s];if(l.chained&&l.rest&&s&&(c=a.slice(i-s,i+1).filter(d=>d).join("/"),s=0),c===void 0)if(l.rest)c="";else continue;if(!l.matcher||n[l.matcher](c)){r[l.name]=c;const d=t[i+1],f=a[i+1];d&&!d.rest&&d.optional&&f&&l.chained&&(s=0),!d&&!f&&Object.keys(r).length===o.length&&(s=0);continue}if(l.optional&&l.chained){s++;continue}return}if(!s)return r}function Ce(e){return e.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}function sn({nodes:e,server_loads:t,dictionary:n,matchers:r}){const a=new Set(t);return Object.entries(n).map(([i,[l,c,d]])=>{const{pattern:f,params:_}=nn(i),g={id:i,exec:u=>{const m=f.exec(u);if(m)return on(m,_,r)},errors:[1,...d||[]].map(u=>e[u]),layouts:[0,...c||[]].map(s),leaf:o(l)};return g.errors.length=g.layouts.length=Math.max(g.errors.length,g.layouts.length),g});function o(i){const l=i<0;return l&&(i=~i),[l,e[i]]}function s(i){return i===void 0?i:[a.has(i),e[i]]}}function Et(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function it(e,t,n=JSON.stringify){const r=n(t);try{sessionStorage[e]=r}catch{}}let D="",ln=D;var mt,wt;(wt=(mt=globalThis.process)==null?void 0:mt.versions)!=null&&wt.webcontainer;Ft(()=>import("./BIHI7g3E.js"),[],import.meta.url).then(e=>new e.AsyncLocalStorage).catch(()=>{});const cn="1777350896255",St="sveltekit:snapshot",Rt="sveltekit:scroll",xt="sveltekit:states",fn="sveltekit:pageurl",W="sveltekit:history",te="sveltekit:navigation",B={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},Ue=j?location.origin:"";function We(e){if(e instanceof URL)return e;let t=document.baseURI;if(!t){const n=document.getElementsByTagName("base");t=n.length?n[0].href:document.URL}return new URL(e,t)}function G(){return{x:pageXOffset,y:pageYOffset}}const lt=new WeakSet,ct={"preload-code":["","off","false","tap","hover","viewport","eager"],"preload-data":["","off","false","tap","hover"],keepfocus:["","true","off","false"],noscroll:["","true","off","false"],reload:["","true","off","false"],replacestate:["","true","off","false"]};function F(e,t){const n=e.getAttribute(`data-sveltekit-${t}`);return S&&un(e,t,n),n}function un(e,t,n){n!==null&&!lt.has(e)&&!ct[t].includes(n)&&(console.error(`Unexpected value for ${t} — should be one of ${ct[t].map(r=>JSON.stringify(r)).join(", ")}`,e),lt.add(e))}const ft={...B,"":B.hover};function Tt(e){let t=e.assignedSlot??e.parentNode;return(t==null?void 0:t.nodeType)===11&&(t=t.host),t}function $t(e,t){for(;e&&e!==t;){if(e.nodeName.toUpperCase()==="A"&&e.hasAttribute("href"))return e;e=Tt(e)}}function qe(e,t,n){let r;try{if(r=new URL(e instanceof SVGAElement?e.href.baseVal:e.href,document.baseURI),n&&r.hash.match(/^#[^/]/)){const i=location.hash.split("#")[1]||"/";r.hash=`#${i}${r.hash}`}}catch{}const a=e instanceof SVGAElement?e.target.baseVal:e.target,o=!r||!!a||Ae(r,t,n)||(e.getAttribute("rel")||"").split(/\s+/).includes("external"),s=(r==null?void 0:r.origin)===Ue&&e.hasAttribute("download");return{url:r,external:o,target:a,download:s}}function be(e){let t=null,n=null,r=null,a=null,o=null,s=null,i=e;for(;i&&i!==document.documentElement;)r===null&&(r=F(i,"preload-code")),a===null&&(a=F(i,"preload-data")),t===null&&(t=F(i,"keepfocus")),n===null&&(n=F(i,"noscroll")),o===null&&(o=F(i,"reload")),s===null&&(s=F(i,"replacestate")),i=Tt(i);function l(c){switch(c){case"":case"true":return!0;case"off":case"false":return!1;default:return}}return{preload_code:ft[r??"off"],preload_data:ft[a??"off"],keepfocus:l(t),noscroll:l(n),reload:l(o),replace_state:l(s)}}function ut(e){const t=He(e);let n=!0;function r(){n=!0,t.update(s=>s)}function a(s){n=!1,t.set(s)}function o(s){let i;return t.subscribe(l=>{(i===void 0||n&&l!==i)&&s(i=l)})}return{notify:r,set:a,subscribe:o}}const Lt={v:I};function dn(){const{set:e,subscribe:t}=He(!1);if(S||!j)return{subscribe:t,check:async()=>!1};let n;async function r(){clearTimeout(n);try{const a=await fetch(`${ln}/_app/version.json`,{headers:{pragma:"no-cache","cache-control":"no-cache"}});if(!a.ok)return!1;const s=(await a.json()).version!==cn;return s&&(e(!0),Lt.v(),clearTimeout(n)),s}catch{return!1}}return{subscribe:t,check:r}}function Ae(e,t,n){return e.origin!==Ue||!e.pathname.startsWith(t)?!0:n?e.pathname!==location.pathname:!1}function Hn(e){}function hn(e){function t(n,r){if(n)for(const a in n){if(a[0]==="_"||e.has(a))continue;const o=[...e.values()],s=pn(a,r==null?void 0:r.slice(r.lastIndexOf(".")))??`valid exports are ${o.join(", ")}, or anything with a '_' prefix`;throw new Error(`Invalid export '${a}'${r?` in ${r}`:""} (${s})`)}}return t}function pn(e,t=".js"){const n=[];if(Je.has(e)&&n.push(`+layout${t}`),Ut.has(e)&&n.push(`+page${t}`),At.has(e)&&n.push(`+layout.server${t}`),gn.has(e)&&n.push(`+page.server${t}`),_n.has(e)&&n.push(`+server${t}`),n.length>0)return`'${e}' is a valid export in ${n.slice(0,-1).join(", ")}${n.length>1?" or ":""}${n.at(-1)}`}const Je=new Set(["load","prerender","csr","ssr","trailingSlash","config"]),Ut=new Set([...Je,"entries"]),At=new Set([...Je]),gn=new Set([...At,"actions","entries"]),_n=new Set(["GET","POST","PATCH","PUT","DELETE","OPTIONS","HEAD","fallback","prerender","trailingSlash","config","entries"]),mn=hn(Ut);function wn(e){return e.filter(t=>t!=null)}function Ye(e){return e instanceof Fe||e instanceof ve?e.status:500}function vn(e){return e instanceof ve?e.text:"Internal Error"}let x,ne,Ne;const yn=ot.toString().includes("$$")||/function \w+\(\) \{\}/.test(ot.toString()),dt="a:";var se,ie,le,ce,fe,ue,de,he,vt,pe,yt,ge,bt;yn?(x={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL(dt)},ne={current:null},Ne={current:!1}):(x=new(vt=class{constructor(){L(this,se,U({}));L(this,ie,U(null));L(this,le,U(null));L(this,ce,U({}));L(this,fe,U({id:null}));L(this,ue,U({}));L(this,de,U(-1));L(this,he,U(new URL(dt)))}get data(){return A(y(this,se))}set data(t){P(y(this,se),t)}get form(){return A(y(this,ie))}set form(t){P(y(this,ie),t)}get error(){return A(y(this,le))}set error(t){P(y(this,le),t)}get params(){return A(y(this,ce))}set params(t){P(y(this,ce),t)}get route(){return A(y(this,fe))}set route(t){P(y(this,fe),t)}get state(){return A(y(this,ue))}set state(t){P(y(this,ue),t)}get status(){return A(y(this,de))}set status(t){P(y(this,de),t)}get url(){return A(y(this,he))}set url(t){P(y(this,he),t)}},se=new WeakMap,ie=new WeakMap,le=new WeakMap,ce=new WeakMap,fe=new WeakMap,ue=new WeakMap,de=new WeakMap,he=new WeakMap,vt),ne=new(yt=class{constructor(){L(this,pe,U(null))}get current(){return A(y(this,pe))}set current(t){P(y(this,pe),t)}},pe=new WeakMap,yt),Ne=new(bt=class{constructor(){L(this,ge,U(!1))}get current(){return A(y(this,ge))}set current(t){P(y(this,ge),t)}},ge=new WeakMap,bt),Lt.v=()=>Ne.current=!0);function bn(e){Object.assign(x,e)}const kn=new Set(["icon","shortcut icon","apple-touch-icon"]);let Q=null;const q=Et(Rt)??{},re=Et(St)??{};if(S&&j){let e=!1;const t=import.meta.url.split("?")[0],n=()=>{var s,i;if(e)return;let o=(s=new Error().stack)==null?void 0:s.split(`
|
| 4 |
+
`);o&&(!o[0].includes("https:")&&!o[0].includes("http:")&&(o=o.slice(1)),o=o.slice(2),!((i=o[0])!=null&&i.includes(t))&&(e=!0,console.warn("Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use the `pushState` and `replaceState` imports from `$app/navigation` instead.")))},r=history.pushState;history.pushState=(...o)=>(n(),r.apply(history,o));const a=history.replaceState;history.replaceState=(...o)=>(n(),a.apply(history,o))}const N={url:ut({}),page:ut({}),navigating:He(null),updated:dn()};function ze(e){q[e]=G()}function En(e,t){let n=e+1;for(;q[n];)delete q[n],n+=1;for(n=t+1;re[n];)delete re[n],n+=1}function ae(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise(I)}async function Pt(){if("serviceWorker"in navigator){const e=await navigator.serviceWorker.getRegistration("/");e&&await e.update()}}let Xe,Ve,ke,O,Be,k;const Ee=[],Se=[];let v=null;function Re(){var e;(e=v==null?void 0:v.fork)==null||e.then(t=>t==null?void 0:t.discard()),v=null}const me=new Map,Ot=new Set,Sn=new Set,ee=new Set;let w={branch:[],error:null,url:null},It=!1,xe=!1,ht=!0,oe=!1,Z=!1,jt=!1,Qe=!1,Ct,E,$,K;const Te=new Set,pt=new Map;async function Jn(e,t,n){var o,s,i,l;S&&t===document.body&&console.warn(`Placing %sveltekit.body% directly inside <body> is not recommended, as your app may break for users who have certain browser extensions installed.
|
| 5 |
+
|
| 6 |
+
Consider wrapping it in an element:
|
| 7 |
+
|
| 8 |
+
<div style="display: contents">
|
| 9 |
+
%sveltekit.body%
|
| 10 |
+
</div>`),globalThis.__sveltekit_10sd49c&&(globalThis.__sveltekit_10sd49c.query,globalThis.__sveltekit_10sd49c.prerender),document.URL!==location.href&&(location.href=location.href),k=e,await((s=(o=e.hooks).init)==null?void 0:s.call(o)),Xe=sn(e),O=document.documentElement,Be=t,Ve=e.nodes[0],ke=e.nodes[1],Ve(),ke(),E=(i=history.state)==null?void 0:i[W],$=(l=history.state)==null?void 0:l[te],E||(E=$=Date.now(),history.replaceState({...history.state,[W]:E,[te]:$},""));const r=q[E];function a(){r&&(history.scrollRestoration="manual",scrollTo(r.x,r.y))}n?(a(),await Cn(Be,n)):(await J({type:"enter",url:We(k.hash?qn(new URL(location.href)):location.href),replace_state:!0}),a()),jn()}function Rn(){Ee.length=0,Qe=!1}function Nt(e){Se.some(t=>t==null?void 0:t.snapshot)&&(re[e]=Se.map(t=>{var n;return(n=t==null?void 0:t.snapshot)==null?void 0:n.capture()}))}function Dt(e){var t;(t=re[e])==null||t.forEach((n,r)=>{var a,o;(o=(a=Se[r])==null?void 0:a.snapshot)==null||o.restore(n)})}function gt(){ze(E),it(Rt,q),Nt($),it(St,re)}async function qt(e,t,n,r){let a;t.invalidateAll&&Re(),await J({type:"goto",url:We(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:r,accept:()=>{t.invalidateAll&&(Qe=!0,a=[],pt.forEach((o,s)=>{for(const i of o.keys())a.push(s+"/"+i)})),t.invalidate&&t.invalidate.forEach(In)}}),t.invalidateAll&&we().then(we).then(()=>{pt.forEach((o,s)=>{o.forEach(({resource:i},l)=>{var c;a!=null&&a.includes(s+"/"+l)&&((c=i.refresh)==null||c.call(i))})})})}async function _t(e){if(e.id!==(v==null?void 0:v.id)){Re();const t={};Te.add(t),v={id:e.id,token:t,promise:Bt({...e,preload:t}).then(n=>(Te.delete(t),n.type==="loaded"&&n.state.error&&Re(),n)),fork:null}}return v.promise}async function De(e){var n;const t=(n=await Pe(e,!1))==null?void 0:n.route;t&&await Promise.all([...t.layouts,t.leaf].filter(Boolean).map(r=>r[1]()))}async function Vt(e,t,n){var o;if(S&&e.state.error&&document.querySelector("vite-error-overlay"))return;const r={params:w.params,route:{id:((o=w.route)==null?void 0:o.id)??null},url:new URL(location.href)};w={...e.state,nav:r};const a=document.querySelector("style[data-sveltekit]");if(a&&a.remove(),Object.assign(x,e.props.page),Ct=new k.root({target:t,props:{...e.props,stores:N,components:Se},hydrate:n,sync:!1,transformError:void 0}),await Promise.resolve(),Dt($),n){const s={from:null,to:{...r,scroll:q[E]??G()},willUnload:!1,type:"enter",complete:Promise.resolve()};ee.forEach(i=>i(s))}xe=!0}async function $e({url:e,params:t,branch:n,errors:r,status:a,error:o,route:s,form:i}){let l="never";for(const u of n)(u==null?void 0:u.slash)!==void 0&&(l=u.slash);e.pathname=Wt(e.pathname,l),e.search=e.search;const c={type:"loaded",state:{url:e,params:t,branch:n,error:o,route:s},props:{constructors:wn(n).map(u=>u.node.component),page:rt(x)}};i!==void 0&&(c.props.form=i);let d={},f=!x,_=0;for(let u=0;u<Math.max(n.length,w.branch.length);u+=1){const m=n[u],p=w.branch[u];(m==null?void 0:m.data)!==(p==null?void 0:p.data)&&(f=!0),m&&(d={...d,...m.data},f&&(c.props[`data_${_}`]=d),_+=1)}return(!w.url||e.href!==w.url.href||w.error!==o||i!==void 0&&i!==x.form||f)&&(c.props.page={error:o,params:t,route:{id:(s==null?void 0:s.id)??null},state:{},status:a,url:new URL(e),form:i??null,data:f?d:x.data}),c}async function Ze({loader:e,parent:t,url:n,params:r,route:a,server_data_node:o}){var c,d;let s=null;const i={dependencies:new Set,params:new Set,parent:!1,route:!1,url:!1,search_params:new Set},l=await e();if(S&&(mn(l.universal),l.universal&&k.hash)){const f=Object.keys(l.universal).filter(_=>_!=="load");if(f.length>0)throw new Error(`Page options are ignored when \`router.type === 'hash'\` (${a.id} has ${f.filter(_=>_!=="load").map(_=>`'${_}'`).join(", ")})`)}return{node:l,loader:e,server:o,universal:(c=l.universal)!=null&&c.load?{type:"data",data:s,uses:i}:null,data:s??(o==null?void 0:o.data)??null,slash:((d=l.universal)==null?void 0:d.trailingSlash)??(o==null?void 0:o.slash)}}function xn(e,t,n){let r=e instanceof Request?e.url:e;const a=new URL(r,n);a.origin===n.origin&&(r=a.href.slice(n.origin.length));const o=xe?en(r,a.href,t):Zt(r,t);return{resolved:a,promise:o}}function Tn(e,t,n,r,a,o){if(Qe)return!0;if(!a)return!1;if(a.parent&&e||a.route&&t||a.url&&n)return!0;for(const s of a.search_params)if(r.has(s))return!0;for(const s of a.params)if(o[s]!==w.params[s])return!0;for(const s of a.dependencies)if(Ee.some(i=>i(new URL(s))))return!0;return!1}function et(e,t){return(e==null?void 0:e.type)==="data"?e:(e==null?void 0:e.type)==="skip"?t??null:null}function $n(e,t){if(!e)return new Set(t.searchParams.keys());const n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(const r of n){const a=e.searchParams.getAll(r),o=t.searchParams.getAll(r);a.every(s=>o.includes(s))&&o.every(s=>a.includes(s))&&n.delete(r)}return n}function Ln({error:e,url:t,route:n,params:r}){return{type:"loaded",state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:rt(x),constructors:[]}}}async function Bt({id:e,invalidating:t,url:n,params:r,route:a,preload:o}){if((v==null?void 0:v.id)===e)return Te.delete(v.token),v.promise;const{errors:s,layouts:i,leaf:l}=a,c=[...i,l];s.forEach(p=>p==null?void 0:p().catch(I)),c.forEach(p=>p==null?void 0:p[1]().catch(I));const d=w.url?e!==Le(w.url):!1,f=w.route?a.id!==w.route.id:!1,_=$n(w.url,n);let g=!1;const u=c.map(async(p,h)=>{var C;if(!p)return;const b=w.branch[h];return p[1]===(b==null?void 0:b.loader)&&!Tn(g,f,d,_,(C=b.universal)==null?void 0:C.uses,r)?b:(g=!0,Ze({loader:p[1],url:n,params:r,route:a,parent:async()=>{var _e;const V={};for(let H=0;H<h;H+=1)Object.assign(V,(_e=await u[H])==null?void 0:_e.data);return V},server_data_node:et(p[0]?{type:"skip"}:null,p[0]?b==null?void 0:b.server:void 0)}))});for(const p of u)p.catch(I);const m=[];for(let p=0;p<c.length;p+=1)if(c[p])try{m.push(await u[p])}catch(h){if(h instanceof Me)return{type:"redirect",location:h.location};if(Te.has(o))return Ln({error:await Y(h,{params:r,url:n,route:{id:a.id}}),url:n,params:r,route:a});let b=Ye(h),T;if(h instanceof Fe)T=h.body;else{if(await N.updated.check())return await Pt(),await ae(n);T=await Y(h,{params:r,url:n,route:{id:a.id}})}const C=await Un(p,m,s);return C?$e({url:n,params:r,branch:m.slice(0,C.idx).concat(C.node),errors:s,status:b,error:T,route:a}):await Ke(n,{id:a.id},T,b)}else m.push(void 0);return $e({url:n,params:r,branch:m,errors:s,status:200,error:null,route:a,form:t?void 0:null})}async function Un(e,t,n){for(;e--;)if(n[e]){let r=e;for(;!t[r];)r-=1;try{return{idx:r+1,node:{node:await n[e](),loader:n[e],data:{},server:null,universal:null}}}catch{continue}}}async function tt({status:e,error:t,url:n,route:r}){const a={};let o=null;try{const s=await Ze({loader:Ve,url:n,params:a,route:r,parent:()=>Promise.resolve({}),server_data_node:et(o)}),i={node:await ke(),loader:ke,universal:null,server:null,data:null};return $e({url:n,params:a,branch:[s,i],status:e,error:t,errors:[],route:null})}catch(s){if(s instanceof Me)return qt(new URL(s.location,location.href),{},0);throw s}}async function An(e){const t=e.href;if(me.has(t))return me.get(t);let n;try{const r=(async()=>{let a=await k.hooks.reroute({url:new URL(e),fetch:async(o,s)=>xn(o,s,e).promise})??e;if(typeof a=="string"){const o=new URL(e);k.hash?o.hash=a:o.pathname=a,a=o}return a})();me.set(t,r),n=await r}catch(r){if(me.delete(t),S){console.error(r);debugger}return}return n}async function Pe(e,t){if(e&&!Ae(e,D,k.hash)){const n=await An(e);if(!n)return;const r=Pn(n);for(const a of Xe){const o=a.exec(r);if(o)return{id:Le(e),invalidating:t,route:a,params:Yt(o),url:e}}}}function Pn(e){return Jt(k.hash?e.hash.replace(/^#/,"").replace(/[?#].+/,""):e.pathname.slice(D.length))||"/"}function Le(e){return(k.hash?e.hash.replace(/^#/,""):e.pathname)+e.search}function Kt({url:e,type:t,intent:n,delta:r,event:a,scroll:o}){let s=!1;const i=nt(w,n,e,t,o??null);r!==void 0&&(i.navigation.delta=r),a!==void 0&&(i.navigation.event=a);const l={...i.navigation,cancel:()=>{s=!0,i.reject(new Error("navigation cancelled"))}};return oe||Ot.forEach(c=>c(l)),s?null:i}async function J({type:e,url:t,popped:n,keepfocus:r,noscroll:a,replace_state:o,state:s={},redirect_count:i=0,nav_token:l={},accept:c=I,block:d=I,event:f}){var H;const _=K;K=l;const g=await Pe(t,!1),u=e==="enter"?nt(w,g,t,e):Kt({url:t,type:e,delta:n==null?void 0:n.delta,intent:g,scroll:n==null?void 0:n.scroll,event:f});if(!u){d(),K===l&&(K=_);return}const m=E,p=$;c(),oe=!0,xe&&u.navigation.type!=="enter"&&N.navigating.set(ne.current=u.navigation);let h=g&&await Bt(g);if(!h)if(Ae(t,D,k.hash))if(S&&k.hash)h=await Ke(t,{id:null},await Y(new ve(404,"Not Found",`Not found: ${t.pathname} (did you forget the hash?)`),{url:t,params:{},route:{id:null}}),404,o);else return await ae(t,o);else h=await Ke(t,{id:null},await Y(new ve(404,"Not Found",`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,o);if(t=(g==null?void 0:g.url)||t,K!==l)return u.reject(new Error("navigation aborted")),!1;if(h.type==="redirect"){if(i<20){await J({type:e,url:new URL(h.location,t),popped:n,keepfocus:r,noscroll:a,replace_state:o,state:s,redirect_count:i+1,nav_token:l}),u.fulfil(void 0);return}h=await tt({status:500,error:await Y(new Error("Redirect loop"),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}})}else h.props.page.status>=400&&await N.updated.check()&&(await Pt(),await ae(t,o));if(Rn(),ze(m),Nt(p),h.props.page.url.pathname!==t.pathname&&(t.pathname=h.props.page.url.pathname),s=n?n.state:s,!n){const R=o?0:1,z={[W]:E+=R,[te]:$+=R,[xt]:s};(o?history.replaceState:history.pushState).call(history,z,"",t),o||En(E,$)}const b=g&&(v==null?void 0:v.id)===g.id?v.fork:null;v!=null&&v.fork&&!b&&Re(),v=null,h.props.page.state=s;let T;if(xe){const R=(await Promise.all(Array.from(Sn,X=>X(u.navigation)))).filter(X=>typeof X=="function");if(R.length>0){let X=function(){R.forEach(Ie=>{ee.delete(Ie)})};R.push(X),R.forEach(Ie=>{ee.add(Ie)})}const z=u.navigation.to;w={...h.state,nav:{params:z.params,route:z.route,url:z.url}},h.props.page&&(h.props.page.url=t);const Oe=b&&await b;Oe?T=Oe.commit():(Q=null,Ct.$set(h.props),Q&&Object.assign(h.props.page,Q),bn(h.props.page),T=(H=Mt)==null?void 0:H()),jt=!0}else await Vt(h,Be,!1);const{activeElement:C}=document;await T,await we(),await we();let V=null;if(ht){const R=n?n.scroll:a?G():null;R?scrollTo(R.x,R.y):(V=t.hash&&document.getElementById(Gt(t)))?V.scrollIntoView():scrollTo(0,0)}const _e=document.activeElement!==C&&document.activeElement!==document.body;!r&&!_e&&Dn(t,!V),ht=!0,h.props.page&&(Q&&Object.assign(h.props.page,Q),Object.assign(x,h.props.page)),oe=!1,e==="popstate"&&Dt($),u.fulfil(void 0),u.navigation.to&&(u.navigation.to.scroll=G()),ee.forEach(R=>R(u.navigation)),N.navigating.set(ne.current=null)}async function Ke(e,t,n,r,a){if(e.origin===Ue&&e.pathname===location.pathname&&!It)return await tt({status:r,error:n,url:e,route:t});if(S&&r!==404){console.error("An error occurred while loading the page. This will cause a full page reload. (This message will only appear during development.)");debugger}return await ae(e,a)}function On(){let e,t={element:void 0,href:void 0},n;O.addEventListener("mousemove",i=>{const l=i.target;clearTimeout(e),e=setTimeout(()=>{o(l,B.hover)},20)});function r(i){i.defaultPrevented||o(i.composedPath()[0],B.tap)}O.addEventListener("mousedown",r),O.addEventListener("touchstart",r,{passive:!0});const a=new IntersectionObserver(i=>{for(const l of i)l.isIntersecting&&(De(new URL(l.target.href)),a.unobserve(l.target))},{threshold:0});async function o(i,l){const c=$t(i,O),d=c===t.element&&(c==null?void 0:c.href)===t.href&&l>=n;if(!c||d)return;const{url:f,external:_,download:g}=qe(c,D,k.hash);if(_||g)return;const u=be(c),m=f&&Le(w.url)===Le(f);if(!(u.reload||m))if(l<=u.preload_data){t={element:c,href:c.href},n=B.tap;const p=await Pe(f,!1);if(!p)return;S?_t(p).then(h=>{h.type==="loaded"&&h.state.error&&console.warn(`Preloading data for ${p.url.pathname} failed with the following error: ${h.state.error.message}
|
| 11 |
+
If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. This route was preloaded due to a data-sveltekit-preload-data attribute. See https://svelte.dev/docs/kit/link-options for more info`)}):_t(p)}else l<=u.preload_code&&(t={element:c,href:c.href},n=l,De(f))}function s(){a.disconnect();for(const i of O.querySelectorAll("a")){const{url:l,external:c,download:d}=qe(i,D,k.hash);if(c||d)continue;const f=be(i);f.reload||(f.preload_code===B.viewport&&a.observe(i),f.preload_code===B.eager&&De(l))}}ee.add(s),s()}function Y(e,t){if(e instanceof Fe)return e.body;S&&console.warn("The next HMR update will cause the page to reload");const n=Ye(e),r=vn(e);return k.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function Yn(e,t={}){if(!j)throw new Error("Cannot call goto(...) on the server");return e=new URL(We(e)),e.origin!==Ue?Promise.reject(new Error(S?`Cannot use \`goto\` with an external URL. Use \`window.location = "${e}"\` instead`:"goto: invalid URL")):qt(e,t,0)}function In(e){if(typeof e=="function")Ee.push(e);else{const{href:t}=new URL(e,location.href);Ee.push(n=>n.href===t)}}function jn(){var t;history.scrollRestoration="manual",addEventListener("beforeunload",n=>{let r=!1;if(gt(),!oe){const a=nt(w,void 0,null,"leave"),o={...a.navigation,cancel:()=>{r=!0,a.reject(new Error("navigation cancelled"))}};Ot.forEach(s=>s(o))}r?(n.preventDefault(),n.returnValue=""):history.scrollRestoration="auto"}),addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&>()}),(t=navigator.connection)!=null&&t.saveData||On(),O.addEventListener("click",async n=>{if(n.button||n.which!==1||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||n.defaultPrevented)return;const r=$t(n.composedPath()[0],O);if(!r)return;const{url:a,external:o,target:s,download:i}=qe(r,D,k.hash);if(!a)return;if(s==="_parent"||s==="_top"){if(window.parent!==window)return}else if(s&&s!=="_self")return;const l=be(r);if(!(r instanceof SVGAElement)&&a.protocol!==location.protocol&&!(a.protocol==="https:"||a.protocol==="http:")||i)return;const[d,f]=(k.hash?a.hash.replace(/^#/,""):a.href).split("#"),_=d===je(location);if(o||l.reload&&(!_||!f)){Kt({url:a,type:"link",event:n})?oe=!0:n.preventDefault();return}if(f!==void 0&&_){const[,g]=w.url.href.split("#");if(g===f){if(n.preventDefault(),f===""||f==="top"&&r.ownerDocument.getElementById("top")===null)scrollTo({top:0});else{const u=r.ownerDocument.getElementById(decodeURIComponent(f));u&&(u.scrollIntoView(),u.focus())}return}if(Z=!0,ze(E),e(a),!l.replace_state)return;Z=!1}n.preventDefault(),await new Promise(g=>{requestAnimationFrame(()=>{setTimeout(g,0)}),setTimeout(g,100)}),await J({type:"link",url:a,keepfocus:l.keepfocus,noscroll:l.noscroll,replace_state:l.replace_state??a.href===location.href,event:n})}),O.addEventListener("submit",n=>{if(n.defaultPrevented)return;const r=HTMLFormElement.prototype.cloneNode.call(n.target),a=n.submitter;if(((a==null?void 0:a.formTarget)||r.target)==="_blank"||((a==null?void 0:a.formMethod)||r.method)!=="get")return;const i=new URL((a==null?void 0:a.hasAttribute("formaction"))&&(a==null?void 0:a.formAction)||r.action);if(Ae(i,D,!1))return;const l=n.target,c=be(l);if(c.reload)return;n.preventDefault(),n.stopPropagation();const d=new FormData(l,a);i.search=new URLSearchParams(d).toString(),J({type:"form",url:i,keepfocus:c.keepfocus,noscroll:c.noscroll,replace_state:c.replace_state??i.href===location.href,event:n})}),addEventListener("popstate",async n=>{var r;if(!Ge){if((r=n.state)!=null&&r[W]){const a=n.state[W];if(K={},a===E)return;const o=q[a],s=n.state[xt]??{},i=new URL(n.state[fn]??location.href),l=n.state[te],c=w.url?je(location)===je(w.url):!1;if(l===$&&(jt||c)){s!==x.state&&(x.state=s),e(i),q[E]=G(),o&&scrollTo(o.x,o.y),E=a;return}const f=a-E;await J({type:"popstate",url:i,popped:{state:s,scroll:o,delta:f},accept:()=>{E=a,$=l},block:()=>{history.go(-f)},nav_token:K,event:n})}else if(!Z){const a=new URL(location.href);e(a),k.hash&&location.reload()}}}),addEventListener("hashchange",()=>{Z&&(Z=!1,history.replaceState({...history.state,[W]:++E,[te]:$},"",location.href))});for(const n of document.querySelectorAll("link"))kn.has(n.rel)&&(n.href=n.href);addEventListener("pageshow",n=>{n.persisted&&N.navigating.set(ne.current=null)});function e(n){w.url=x.url=n,N.page.set(rt(x)),N.page.notify()}}async function Cn(e,{status:t=200,error:n,node_ids:r,params:a,route:o,server_route:s,data:i,form:l}){It=!0;const c=new URL(location.href);let d;({params:a={},route:o={id:null}}=await Pe(c,!1)||{}),d=Xe.find(({id:g})=>g===o.id);let f,_=!0;try{const g=r.map(async(m,p)=>{const h=i[p];return h!=null&&h.uses&&(h.uses=Nn(h.uses)),Ze({loader:k.nodes[m],url:c,params:a,route:o,parent:async()=>{const b={};for(let T=0;T<p;T+=1)Object.assign(b,(await g[T]).data);return b},server_data_node:et(h)})}),u=await Promise.all(g);if(d){const m=d.layouts;for(let p=0;p<m.length;p++)m[p]||u.splice(p,0,void 0)}f=await $e({url:c,params:a,branch:u,status:t,error:n,errors:d==null?void 0:d.errors,form:l,route:d??null})}catch(g){if(g instanceof Me){await ae(new URL(g.location,location.href));return}f=await tt({status:Ye(g),error:await Y(g,{url:c,params:a,route:o}),url:c,route:o}),e.textContent="",_=!1}finally{}f.props.page&&(f.props.page.state={}),await Vt(f,e,_)}function Nn(e){return{dependencies:new Set((e==null?void 0:e.dependencies)??[]),params:new Set((e==null?void 0:e.params)??[]),parent:!!(e!=null&&e.parent),route:!!(e!=null&&e.route),url:!!(e!=null&&e.url),search_params:new Set((e==null?void 0:e.search_params)??[])}}let Ge=!1;function Dn(e,t=!0){const n=document.querySelector("[autofocus]");if(n)n.focus();else{const r=Gt(e);if(r&&document.getElementById(r)){const{x:o,y:s}=G();setTimeout(()=>{const i=history.state;Ge=!0,location.replace(new URL(`#${r}`,location.href)),history.replaceState(i,"",e),t&&scrollTo(o,s),Ge=!1})}else{const o=document.body,s=o.getAttribute("tabindex");o.tabIndex=-1,o.focus({preventScroll:!0,focusVisible:!1}),s!==null?o.setAttribute("tabindex",s):o.removeAttribute("tabindex")}const a=getSelection();if(a&&a.type!=="None"){const o=[];for(let s=0;s<a.rangeCount;s+=1)o.push(a.getRangeAt(s));setTimeout(()=>{if(a.rangeCount===o.length){for(let s=0;s<a.rangeCount;s+=1){const i=o[s],l=a.getRangeAt(s);if(i.commonAncestorContainer!==l.commonAncestorContainer||i.startContainer!==l.startContainer||i.endContainer!==l.endContainer||i.startOffset!==l.startOffset||i.endOffset!==l.endOffset)return}a.removeAllRanges()}})}}}function nt(e,t,n,r,a=null){var c,d;let o,s;const i=new Promise((f,_)=>{o=f,s=_});return i.catch(I),{navigation:{from:{params:e.params,route:{id:((c=e.route)==null?void 0:c.id)??null},url:e.url,scroll:G()},to:n&&{params:(t==null?void 0:t.params)??null,route:{id:((d=t==null?void 0:t.route)==null?void 0:d.id)??null},url:n,scroll:a},willUnload:!t,type:r,complete:i},fulfil:o,reject:s}}function rt(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function qn(e){const t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function Gt(e){let t;if(k.hash){const[,,n]=e.hash.split("#",3);t=n??""}else t=e.hash.slice(1);return decodeURIComponent(t)}if(S){const e=console.warn;console.warn=function(...n){n.length===1&&/<(Layout|Page|Error)(_[\w$]+)?> was created (with unknown|without expected) prop '(data|form)'/.test(n[0])||e(...n)}}export{Jn as a,Yn as g,Hn as l,x as p,N as s};
|
frontend/build/_app/immutable/chunks/BRcwu1Xf.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{ao as h,ap as d}from"./CPYeCQyA.js";const l=[{code:"en",name:"English",nativeName:"English"},{code:"hi",name:"Hindi",nativeName:"हिन्दी"},{code:"raj",name:"Rajasthani",nativeName:"राजस्थानी"},{code:"gu",name:"Gujarati",nativeName:"ગુજરાતી"},{code:"mr",name:"Marathi",nativeName:"मराठी"},{code:"pa",name:"Punjabi",nativeName:"ਪੰਜਾਬੀ"},{code:"bn",name:"Bengali",nativeName:"বাংলা"},{code:"ta",name:"Tamil",nativeName:"தமிழ்"},{code:"te",name:"Telugu",nativeName:"తెలుగు"},{code:"kn",name:"Kannada",nativeName:"ಕನ್ನಡ"},{code:"ml",name:"Malayalam",nativeName:"മലയാളം"},{code:"or",name:"Odia",nativeName:"ଓଡ଼ିଆ"},{code:"as",name:"Assamese",nativeName:"অসমীয়া"},{code:"ur",name:"Urdu",nativeName:"اردو"},{code:"ne",name:"Nepali",nativeName:"नेपाली"},{code:"si",name:"Sinhala",nativeName:"සිංහල"},{code:"kok",name:"Konkani",nativeName:"कोंकणी"},{code:"mai",name:"Maithili",nativeName:"मैथिली"},{code:"bho",name:"Bhojpuri",nativeName:"भोजपुरी"}],r=new Set(["ur"]),a={"nav.chat":"Chat","nav.dashboard":"Dashboard","nav.notebooks":"Notebooks","nav.admin":"Admin","nav.cluster":"Cluster","nav.rag":"Knowledge Base","nav.doubts":"Doubts","nav.attendance":"Attendance","nav.copycheck":"Copy Check","nav.files":"Files","nav.notifications":"Notifications","nav.keys":"API Keys","nav.settings":"Settings","nav.logout":"Log out","nav.profile":"Profile","auth.login":"Sign in to MAC","auth.email":"Email address","auth.password":"Password","auth.signin":"Sign in","auth.signing_in":"Signing in…","auth.forgot":"Forgot password?","auth.error":"Invalid credentials","setup.title":"Welcome to MAC","setup.subtitle":"MBM AI Cloud — First-time setup","setup.name":"Your name","setup.email":"Admin email","setup.password":"Password (min 8 chars)","setup.create":"Create admin account","setup.creating":"Creating account…","setup.success":"Account created! Redirecting…","chat.placeholder":"Ask anything… (Shift+Enter for new line)","chat.send":"Send","chat.new":"New chat","chat.model":"Model","chat.auto":"Auto (smart routing)","chat.thinking":"Thinking…","chat.error":"Something went wrong. Please try again.","chat.empty":"Start a conversation","chat.empty_hint":"Ask MAC anything — code, math, essays, or general questions.","dash.title":"Dashboard","dash.requests":"Total Requests","dash.tokens":"Tokens Used","dash.models":"Active Models","dash.days":"Days Active","dash.heatmap":"Activity (last 26 weeks)","dash.distribution":"Model Distribution","dash.hourly":"Hourly Usage","dash.quota":"Quota Status","dash.recent":"Recent Activity","dash.no_data":"No activity yet","admin.users":"Users","admin.models":"Models","admin.features":"Features","admin.hardware":"Hardware","admin.system":"System","admin.guardrails":"Guardrails","admin.rag":"Knowledge Base","common.save":"Save","common.cancel":"Cancel","common.delete":"Delete","common.edit":"Edit","common.loading":"Loading…","common.error":"Error","common.success":"Success","common.search":"Search","common.refresh":"Refresh","common.enabled":"Enabled","common.disabled":"Disabled","common.yes":"Yes","common.no":"No","common.unknown":"Unknown","common.copy":"Copy","common.copied":"Copied!","common.close":"Close","common.back":"Back","common.next":"Next","common.submit":"Submit"},u={"nav.chat":"चैट","nav.dashboard":"डैशबोर्ड","nav.notebooks":"नोटबुक","nav.admin":"प्रशासक","nav.cluster":"क्लस्टर","nav.rag":"ज्ञान आधार","nav.doubts":"शंका","nav.attendance":"उपस्थिति","nav.copycheck":"नकल जाँच","nav.files":"फ़ाइलें","nav.notifications":"सूचनाएं","nav.keys":"API कुंजी","nav.settings":"सेटिंग","nav.logout":"लॉग आउट","nav.profile":"प्रोफाइल","auth.login":"MAC में साइन इन करें","auth.email":"ईमेल पता","auth.password":"पासवर्ड","auth.signin":"साइन इन","auth.signing_in":"साइन इन हो रहा है…","auth.forgot":"पासवर्ड भूल गए?","auth.error":"गलत क्रेडेंशियल","setup.title":"MAC में आपका स्वागत है","setup.subtitle":"MBM AI Cloud — पहली बार सेटअप","setup.name":"आपका नाम","setup.email":"प्रशासक ईमेल","setup.password":"पासवर्ड (न्यूनतम 8 अक्षर)","setup.create":"प्रशासक खाता बनाएं","setup.creating":"खाता बन रहा है…","setup.success":"खाता बन गया! पुनर्निर्देशित हो रहे हैं…","chat.placeholder":"कुछ भी पूछें… (नई लाइन के लिए Shift+Enter)","chat.send":"भेजें","chat.new":"नई चैट","chat.model":"मॉडल","chat.auto":"स्वत: (स्मार्ट रूटिंग)","chat.thinking":"सोच रहा है…","chat.error":"कुछ गलत हुआ। कृपया पुनः प्रयास करें।","chat.empty":"बातचीत शुरू करें","chat.empty_hint":"MAC से कुछ भी पूछें — कोड, गणित, निबंध, या सामान्य प्रश्न।","dash.title":"डैशबोर्ड","dash.requests":"कुल अनुरोध","dash.tokens":"उपयोग किए गए टोकन","dash.models":"सक्रिय मॉडल","dash.days":"सक्रिय दिन","dash.heatmap":"गतिविधि (पिछले 26 सप्ताह)","dash.distribution":"मॉडल वितरण","dash.hourly":"प्रति घंटा उपयोग","dash.quota":"कोटा स्थिति","dash.recent":"हालिया गतिविधि","dash.no_data":"अभी तक कोई गतिविधि नहीं","admin.users":"उपयोगकर्ता","admin.models":"मॉडल","admin.features":"सुविधाएं","admin.hardware":"हार्डवेयर","admin.system":"सिस्टम","admin.guardrails":"सुरक्षा नियम","admin.rag":"ज्ञान आधार","common.save":"सहेजें","common.cancel":"रद्द करें","common.delete":"हटाएं","common.edit":"संपादित करें","common.loading":"लोड हो रहा है…","common.error":"त्रुटि","common.success":"सफलता","common.search":"खोजें","common.refresh":"ताज़ा करें","common.enabled":"सक्षम","common.disabled":"अक्षम","common.yes":"हाँ","common.no":"नहीं","common.unknown":"अज्ञात","common.copy":"कॉपी","common.copied":"कॉपी हो गया!","common.close":"बंद करें","common.back":"वापस","common.next":"अगला","common.submit":"जमा करें"},g={"nav.chat":"बात","nav.dashboard":"मुख पानो","nav.notebooks":"नोटबुक","nav.logout":"लॉग आउट","nav.settings":"सेटिंग","auth.login":"MAC में प्रवेश करो","auth.signin":"प्रवेश","auth.signing_in":"प्रवेश हो रह्यो है…","auth.password":"पासवर्ड","chat.placeholder":"कुछ भी पूछो… (नई लाइन खातर Shift+Enter)","chat.send":"भेजो","chat.new":"नई बात","chat.empty":"बात चालू करो","chat.empty_hint":"MAC सूं कुछ भी पूछो — कोड, गणित, निबंध।","dash.title":"मुख पानो","common.save":"संग्रह करो","common.cancel":"रद्द","common.loading":"लोड हो रह्यो है…","common.search":"ढूंढो","common.back":"पाछो"},v={"nav.chat":"ચેટ","nav.dashboard":"ડેશબોર્ડ","nav.notebooks":"નોટબુક","nav.logout":"લૉગ આઉટ","nav.settings":"સેટિંગ","nav.files":"ફ઼ાઇલો","nav.notifications":"સૂચનાઓ","auth.login":"MAC માં સાઇન ઇન કરો","auth.email":"ઇમેઇલ સરનામું","auth.password":"પાસવર્ડ","auth.signin":"સાઇન ઇન","auth.signing_in":"સાઇન ઇન થઈ રહ્યું છે…","auth.error":"ખોટી ઓળખ","chat.placeholder":"કંઈ પણ પૂછો… (નવી લીટી માટે Shift+Enter)","chat.send":"મોકલો","chat.new":"નવી ચેટ","chat.empty":"વાતચીત શરૂ કરો","chat.empty_hint":"MAC ને કોઈ પણ વિષે પૂછો — કોડ, ગણિત, નિબંધ.","dash.title":"ડેશબોર્ડ","dash.recent":"તાજેતરની પ્રવૃત્તિ","dash.no_data":"હજી કોઈ પ્રવૃત્તિ નથી","common.save":"સાચવો","common.cancel":"રદ કરો","common.loading":"લોડ થઈ રહ્યું છે…","common.search":"શોધો","common.back":"પાછળ","common.close":"બંધ"},p={"nav.chat":"चॅट","nav.dashboard":"डॅशबोर्ड","nav.notebooks":"नोटबुक","nav.logout":"लॉग आउट","nav.settings":"सेटिंग्ज","nav.attendance":"हजेरी","nav.doubts":"शंका","nav.files":"फाइल्स","nav.notifications":"सूचना","auth.login":"MAC मध्ये साइन इन करा","auth.email":"ईमेल पत्ता","auth.password":"पासवर्ड","auth.signin":"साइन इन","auth.signing_in":"साइन इन होत आहे…","auth.error":"चुकीची ओळख","chat.placeholder":"काहीही विचारा… (नवी ओळ साठी Shift+Enter)","chat.send":"पाठवा","chat.new":"नवीन चॅट","chat.empty":"संभाषण सुरू करा","chat.empty_hint":"MAC ला काहीही विचारा — कोड, गणित, निबंध.","dash.title":"डॅशबोर्ड","dash.recent":"अलीकडील क्रियाकलाप","dash.no_data":"अद्याप कोणतीही क्रियाकलाप नाही","common.save":"जतन करा","common.cancel":"रद्द करा","common.loading":"लोड होत आहे…","common.search":"शोधा","common.back":"मागे","common.close":"बंद करा"},b={"nav.chat":"ਚੈਟ","nav.dashboard":"ਡੈਸ਼ਬੋਰਡ","nav.notebooks":"ਨੋਟਬੁੱਕ","nav.logout":"ਲੌਗ ਆਉਟ","nav.settings":"ਸੈਟਿੰਗਜ਼","nav.files":"ਫਾਈਲਾਂ","nav.notifications":"ਸੂਚਨਾਵਾਂ","auth.login":"MAC ਵਿੱਚ ਸਾਈਨ ਇਨ ਕਰੋ","auth.email":"ਈਮੇਲ ਪਤਾ","auth.password":"ਪਾਸਵਰਡ","auth.signin":"ਸਾਈਨ ਇਨ","auth.signing_in":"ਸਾਈਨ ਇਨ ਹੋ ਰਿਹਾ ਹੈ…","auth.error":"ਗਲਤ ਜਾਣਕਾਰੀ","chat.placeholder":"ਕੁਝ ਵੀ ਪੁੱਛੋ… (ਨਵੀਂ ਲਾਈਨ ਲਈ Shift+Enter)","chat.send":"ਭੇਜੋ","chat.new":"ਨਵੀਂ ਚੈਟ","chat.empty":"ਗੱਲਬਾਤ ਸ਼ੁਰੂ ਕਰੋ","chat.empty_hint":"MAC ਨੂੰ ਕੁਝ ਵੀ ਪੁੱਛੋ — ਕੋਡ, ਗਣਿਤ, ਲੇਖ.","dash.title":"ਡੈਸ਼ਬੋਰਡ","common.save":"ਸੁਰੱਖਿਅਤ ਕਰੋ","common.cancel":"ਰੱਦ ਕਰੋ","common.loading":"ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ…","common.search":"ਖੋਜੋ","common.back":"ਵਾਪਸ"},w={"nav.chat":"চ্যাট","nav.dashboard":"ড্যাশবোর্ড","nav.notebooks":"নোটবুক","nav.logout":"লগ আউট","nav.settings":"সেটিংস","nav.files":"ফাইল","nav.notifications":"বিজ্ঞপ্তি","auth.login":"MAC-এ সাইন ইন করুন","auth.email":"ইমেল ঠিকানা","auth.password":"পাসওয়ার্ড","auth.signin":"সাইন ইন","auth.signing_in":"সাইন ইন হচ্ছে…","auth.error":"ভুল পরিচয়পত্র","chat.placeholder":"যেকোনো কিছু জিজ্ঞেস করুন… (নতুন লাইনের জন্য Shift+Enter)","chat.send":"পাঠান","chat.new":"নতুন চ্যাট","chat.empty":"কথোপকথন শুরু করুন","chat.empty_hint":"MAC-কে যেকোনো কিছু জিজ্ঞেস করুন — কোড, গণিত, প্রবন্ধ।","dash.title":"ড্যাশবোর্ড","dash.recent":"সাম্প্রতিক কার্যক্রম","dash.no_data":"এখনও কোনো কার্যক্রম নেই","common.save":"সংরক্ষণ করুন","common.cancel":"বাতিল করুন","common.loading":"লোড হচ্ছে…","common.search":"খুঁজুন","common.back":"ফিরে যান"},A={"nav.chat":"அரட்டை","nav.dashboard":"டாஷ்போர்டு","nav.notebooks":"நோட்புக்","nav.logout":"வெளியேறு","nav.settings":"அமைப்புகள்","auth.login":"MAC-ல் உள்நுழையவும்","auth.email":"மின்னஞ்சல் முகவரி","auth.password":"கடவுச்சொல்","auth.signin":"உள்நுழை","auth.signing_in":"உள்நுழைகிறது…","auth.error":"தவறான சான்றுகள்","chat.placeholder":"எதையும் கேளுங்கள்…","chat.send":"அனுப்பு","chat.new":"புதிய அரட்டை","chat.empty":"உரையாடலை தொடங்குங்கள்","chat.empty_hint":"MAC-ஐ எதையும் கேளுங்கள் — குறியீடு, கணிதம், கட்டுரை.","dash.title":"டாஷ்போர்டு","common.save":"சேமி","common.cancel":"ரத்து செய்","common.loading":"ஏற்றுகிறது…","common.search":"தேடு","common.back":"திரும்பு"},y={"nav.chat":"చాట్","nav.dashboard":"డాష్బోర్డ్","nav.notebooks":"నోట్బుక్","nav.logout":"లాగ్ అవుట్","nav.settings":"సెట్టింగ్లు","auth.login":"MAC లో సైన్ ఇన్ చేయండి","auth.password":"పాస్వర్డ్","auth.signin":"సైన్ ఇన్","auth.signing_in":"సైన్ ఇన్ అవుతోంది…","auth.error":"తప్పు ఆధారాలు","chat.placeholder":"ఏదైనా అడగండి…","chat.send":"పంపు","chat.new":"కొత్త చాట్","chat.empty":"సంభాషణ ప్రారంభించండి","dash.title":"డాష్బోర్డ్","common.save":"సేవ్ చేయి","common.cancel":"రద్దు చేయి","common.loading":"లోడ్ అవుతోంది…","common.search":"వెతకండి","common.back":"వెనుకకు"},f={"nav.chat":"ಚಾಟ್","nav.dashboard":"ಡ್ಯಾಶ್ಬೋರ್ಡ್","nav.logout":"ಲಾಗ್ ಔಟ್","nav.settings":"ಸೆಟ್ಟಿಂಗ್ಗಳು","auth.login":"MAC ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಿ","auth.password":"ಪಾಸ್ವರ್ಡ್","auth.signin":"ಸೈನ್ ಇನ್","auth.signing_in":"ಸೈನ್ ಇನ್ ಆಗುತ್ತಿದೆ…","chat.placeholder":"ಏನಾದರೂ ಕೇಳಿ…","chat.send":"ಕಳಿಸಿ","chat.new":"ಹೊಸ ಚಾಟ್","chat.empty":"ಸಂಭಾಷಣೆ ಪ್ರಾರಂಭಿಸಿ","dash.title":"ಡ್ಯಾಶ್ಬೋರ್ಡ್","common.save":"ಉಳಿಸಿ","common.cancel":"ರದ್ದು ಮಾಡಿ","common.loading":"ಲೋಡ್ ಆಗುತ್ತಿದೆ…","common.search":"ಹುಡುಕಿ"},k={"nav.chat":"ചാറ്റ്","nav.dashboard":"ഡാഷ്ബോർഡ്","nav.logout":"ലോഗ് ഔട്ട്","nav.settings":"ക്രമീകരണങ്ങൾ","auth.login":"MAC-ൽ സൈൻ ഇൻ ചെയ്യുക","auth.password":"പാസ്വേഡ്","auth.signin":"സൈൻ ഇൻ","auth.signing_in":"സൈൻ ഇൻ ചെയ്യുന്നു…","chat.placeholder":"എന്തും ചോദിക്കൂ…","chat.send":"അയക്കുക","chat.new":"പുതിയ ചാറ്റ്","chat.empty":"സംഭാഷണം ആരംഭിക്കുക","dash.title":"ഡാഷ്ബോർഡ്","common.save":"സേവ് ചെയ്യുക","common.cancel":"റദ്ദാക്കുക","common.loading":"ലോഡ് ചെയ്യുന്നു…","common.search":"തിരയുക"},C={"nav.chat":"ଚ୍ୟାଟ୍","nav.dashboard":"ଡ୍ୟାଶ୍ବୋର୍ଡ","nav.logout":"ଲଗ ଆଉଟ","auth.login":"MAC ରେ ସାଇନ ଇନ କରନ୍ତୁ","auth.password":"ପାସୱାର୍ଡ","auth.signin":"ସାଇନ ଇନ","chat.send":"ପଠାନ୍ତୁ","chat.new":"ନୂଆ ଚ୍ୟାଟ","dash.title":"ଡ୍ୟାଶ୍ବୋର୍ଡ","common.save":"ସଂରକ୍ଷଣ","common.cancel":"ବାତିଲ","common.loading":"ଲୋଡ ହେଉଛି…","common.search":"ଖୋଜ"},M={"nav.chat":"চেট","nav.dashboard":"ডেশ্ববৰ্ড","nav.logout":"লগ আউট","auth.login":"MAC ত চাইন ইন কৰক","auth.password":"পাছৱৰ্ড","auth.signin":"চাইন ইন","chat.send":"পঠাওক","chat.new":"নতুন চেট","dash.title":"ডেশ্ববৰ্ড","common.save":"সংৰক্ষণ","common.loading":"লোড হৈছে…"},S={"nav.chat":"چیٹ","nav.dashboard":"ڈیش بورڈ","nav.notebooks":"نوٹ بکس","nav.logout":"لاگ آؤٹ","nav.settings":"ترتیبات","auth.login":"MAC میں سائن ان کریں","auth.email":"ای میل پتہ","auth.password":"پاس ورڈ","auth.signin":"سائن ان","auth.signing_in":"سائن ان ہو رہا ہے…","auth.error":"غلط اعتماد نامہ","chat.placeholder":"کچھ بھی پوچھیں…","chat.send":"بھیجیں","chat.new":"نئی چیٹ","chat.empty":"گفتگو شروع کریں","chat.empty_hint":"MAC سے کچھ بھی پوچھیں — کوڈ، ریاضی، مضمون۔","dash.title":"ڈیش بورڈ","common.save":"محفوظ کریں","common.cancel":"منسوخ","common.loading":"لوڈ ہو رہا ہے…","common.search":"تلاش","common.back":"واپس"},_={"nav.chat":"च्याट","nav.dashboard":"ड्यासबोर्ड","nav.logout":"लग आउट","nav.settings":"सेटिङ","auth.login":"MAC मा साइन इन गर्नुहोस्","auth.password":"पासवर्ड","auth.signin":"साइन इन","auth.signing_in":"साइन इन भइरहेको छ…","chat.placeholder":"केही पनि सोध्नुहोस्…","chat.send":"पठाउनुहोस्","chat.new":"नयाँ च्याट","chat.empty":"कुराकानी सुरु गर्नुहोस्","dash.title":"ड्यासबोर्ड","common.save":"सुरक्षित गर्नुहोस्","common.cancel":"रद्द गर्नुहोस्","common.loading":"लोड भइरहेको छ…","common.search":"खोज्नुहोस्"},N={"nav.chat":"කතාබස","nav.dashboard":"උපකරණ පුවරුව","nav.logout":"නික්මෙන්න","auth.login":"MAC වෙත ඇතුල් වන්න","auth.password":"මුරපදය","auth.signin":"ඇතුල් වන්න","chat.send":"යවන්න","chat.new":"නව කතාබස","common.loading":"පූරණය වෙමින්…","common.save":"සුරකින්න"},E={"nav.chat":"चॅट","nav.dashboard":"डॅशबोर्ड","nav.logout":"लॉग आउट","auth.login":"MAC मदीं साइन इन करात","auth.password":"पासवर्ड","auth.signin":"साइन इन","chat.send":"धाडात","chat.new":"नवें चॅट","dash.title":"डॅशबोर्ड","common.loading":"लोड जाता…","common.save":"सांबाळात"},L={"nav.chat":"चैट","nav.dashboard":"डैशबोर्ड","nav.logout":"लॉग आउट","auth.login":"MAC मे साइन इन करू","auth.password":"पासवर्ड","auth.signin":"साइन इन","chat.send":"पठाउ","chat.new":"नव चैट","dash.title":"डैशबोर्ड","common.loading":"लोड भ रहल अछि…","common.save":"सहेजू"},R={"nav.chat":"चैट","nav.dashboard":"डैशबोर्ड","nav.logout":"लॉग आउट","auth.login":"MAC में साइन इन करीं","auth.password":"पासवर्ड","auth.signin":"साइन इन","auth.signing_in":"साइन इन हो रहल बा…","chat.placeholder":"कुछ भी पूछीं…","chat.send":"भेजीं","chat.new":"नया चैट","chat.empty":"बातचीत शुरू करीं","dash.title":"डैशबोर्ड","common.loading":"लोड हो रहल बा…","common.save":"सेव करीं","common.cancel":"रद्द करीं"},I={en:a,hi:{...a,...u},raj:{...a,...g},gu:{...a,...v},mr:{...a,...p},pa:{...a,...b},bn:{...a,...w},ta:{...a,...A},te:{...a,...y},kn:{...a,...f},ml:{...a,...k},or:{...a,...C},as:{...a,...M},ur:{...a,...S},ne:{...a,..._},si:{...a,...N},kok:{...a,...E},mai:{...a,...L},bho:{...a,...R}},s=d("en");function T(n){l.find(o=>o.code===n)&&(s.set(n),typeof localStorage<"u"&&localStorage.setItem("mac_locale",n),typeof document<"u"&&(document.documentElement.dir=r.has(n)?"rtl":"ltr",document.documentElement.lang=n))}function P(){var o;if(typeof localStorage>"u")return;const n=localStorage.getItem("mac_locale")||((o=navigator.language)==null?void 0:o.split("-")[0])||"en";T(n)}const O=s,D=h(s,n=>{const o=I[n]??a;return(t,c={})=>{let e=o[t]??a[t]??t;return Object.entries(c).forEach(([i,m])=>{e=e.replaceAll(`{${i}}`,m)}),e}});export{l as S,P as i,O as l,T as s,D as t};
|
frontend/build/_app/immutable/chunks/BT_qo9Cc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{T as s,f as o,U as c,V as b,W as m,X as h,Y as v}from"./CPYeCQyA.js";function d(e,r,f=!1){if(e.multiple){if(r==null)return;if(!b(r))return m();for(var a of e.options)a.selected=r.includes(t(a));return}for(a of e.options){var i=t(a);if(h(i,r)){a.selected=!0;return}}(!f||r!==void 0)&&(e.selectedIndex=-1)}function y(e){var r=new MutationObserver(()=>{d(e,e.__value)});r.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),c(()=>{r.disconnect()})}function S(e,r,f=r){var a=new WeakSet,i=!0;s(e,"change",u=>{var l=u?"[selected]":":checked",n;if(e.multiple)n=[].map.call(e.querySelectorAll(l),t);else{var _=e.querySelector(l)??e.querySelector("option:not([disabled])");n=_&&t(_)}f(n),e.__value=n,v!==null&&a.add(v)}),o(()=>{var u=r();if(e===document.activeElement){var l=v;if(a.has(l))return}if(d(e,u,i),i&&u===void 0){var n=e.querySelector(":checked");n!==null&&(u=t(n),f(u))}e.__value=u,i=!1}),y(e)}function t(e){return"__value"in e?e.__value:e.value}export{S as b,y as i,d as s};
|
frontend/build/_app/immutable/chunks/BcWCHg3k.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{h,A as M,B as f,t as T,a as m,C as g,D as v,E as H,F as A,G as L,H as O,I as R,J as S,K as w,L as D,N as $,M as b,O as I,P as N,Q as y,R as P,S as F}from"./CPYeCQyA.js";function G(l,o,_){var c,i;if(!o||o===I(String(_??"")))return;let s;const a=(c=l.__svelte_meta)==null?void 0:c.loc;a?s=`near ${a.file}:${a.line}:${a.column}`:(i=N)!=null&&i[y]&&(s=`in ${N[y]}`),P(F(s))}function k(l,o,_=!1,s=!1,a=!1,c=!1){var i=l,n="";if(_){var d=l;h&&(i=M(f(d)))}T(()=>{var t=g;if(n===(n=o()??"")){h&&m();return}if(_&&!h){t.nodes=null,d.innerHTML=n,n!==""&&v(f(d),d.lastChild);return}if(t.nodes!==null&&(H(t.nodes.start,t.nodes.end),t.nodes=null),n!==""){if(h){for(var p=A.data,e=m(),E=e;e!==null&&(e.nodeType!==L||e.data!=="");)E=e,e=O(e);if(e===null)throw R(),S;w&&!c&&G(e.parentNode,p,n),v(A,E),i=M(e);return}var C=s?$:a?b:void 0,u=D(s?"svg":a?"math":"template",C);u.innerHTML=n;var r=s||a?u:u.content;if(v(f(r),r.lastChild),s||a)for(;f(r);)i.before(f(r));else i.before(r)}})}export{k as h};
|
frontend/build/_app/immutable/chunks/BjvCllst.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{al as S,f as T,am as x,u as E,C as O,an as Y,a8 as k}from"./CPYeCQyA.js";function n(r,f){return r===f||(r==null?void 0:r[k])===f}function C(r={},f,i,A){var p=S.r,h=O;return T(()=>{var s,t;return x(()=>{s=t,t=[],E(()=>{r!==i(...t)&&(f(r,...t),s&&n(i(...s),r)&&f(null,...s))})}),()=>{let a=h;for(;a!==p&&a.parent!==null&&a.parent.f&Y;)a=a.parent;const w=()=>{t&&n(i(...t),r)&&f(null,...t)},c=a.teardown;a.teardown=()=>{w(),c==null||c()}}}),r}export{C as b};
|
frontend/build/_app/immutable/chunks/BkDXvb8s.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{ag as y,ah as u,ai as _,aj as g,h as t,G as o,H as i,ak as l,A as d,F as p,B as m}from"./CPYeCQyA.js";function F(n,r){let a=null,E=t;var s;if(t){a=p;for(var e=m(document.head);e!==null&&(e.nodeType!==o||e.data!==n);)e=i(e);if(e===null)l(!1);else{var f=i(e);e.remove(),d(f)}}t||(s=document.head.appendChild(y()));try{u(()=>r(s),_|g)}finally{E&&(l(!0),d(a))}}export{F as h};
|
frontend/build/_app/immutable/chunks/BprE2qdV.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{s as o}from"./CISzQ7XW.js";import{ae as n}from"./CPYeCQyA.js";function e(t){o.r.on_destroy(t)}function a(){return n}async function c(){}async function i(){}export{a as c,e as o,i as s,c as t};
|