Aaryan17 commited on
Commit
0e76632
·
verified ·
1 Parent(s): 18b08e9

chore: upload MAC codebase to HF Space

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +126 -0
  2. .gitattributes +4 -35
  3. .gitignore +82 -0
  4. .vscode/settings.json +3 -0
  5. Dockerfile +27 -0
  6. README.md +252 -10
  7. alembic.ini +119 -0
  8. alembic/README +1 -0
  9. alembic/env.py +87 -0
  10. alembic/script.py.mako +26 -0
  11. alembic/versions/20260426_0001_initial_schema.py +56 -0
  12. alembic/versions/20260427_0002_session1_tables.py +200 -0
  13. alembic/versions/20260427_0003_file_share_node_columns.py +54 -0
  14. build/MAC-Installer/Analysis-00.toc +0 -0
  15. build/MAC-Installer/EXE-00.toc +0 -0
  16. build/MAC-Installer/MAC-Installer.pkg +3 -0
  17. build/MAC-Installer/PKG-00.toc +0 -0
  18. build/MAC-Installer/PYZ-00.pyz +3 -0
  19. build/MAC-Installer/PYZ-00.toc +1247 -0
  20. build/MAC-Installer/base_library.zip +3 -0
  21. build/MAC-Installer/warn-MAC-Installer.txt +230 -0
  22. build/MAC-Installer/xref-MAC-Installer.html +0 -0
  23. build/spec/MAC-Installer.spec +39 -0
  24. delete later/Loader.svelte +208 -0
  25. delete later/MAC Loader.html +287 -0
  26. delete later/MBM-MAC Globe.html +0 -0
  27. dist/MAC-Installer.exe +3 -0
  28. docker-compose.worker.yml +104 -0
  29. docker-compose.yml +226 -0
  30. docs/ARCHITECTURE.md +536 -0
  31. docs/MAC-CONTEXT.md +883 -0
  32. docs/MAC-PROGRESS.md +202 -0
  33. frontend/build.sh +15 -0
  34. frontend/build/_app/env.js +1 -0
  35. frontend/build/_app/immutable/assets/0.DBvVKUFC.css +1 -0
  36. frontend/build/_app/immutable/assets/12.BQVrdhQn.css +1 -0
  37. frontend/build/_app/immutable/assets/13.M6eN8M_c.css +1 -0
  38. frontend/build/_app/immutable/assets/2.DfxUCL9T.css +1 -0
  39. frontend/build/_app/immutable/assets/5.Bb_sFVPM.css +1 -0
  40. frontend/build/_app/immutable/assets/8.D2JiE0Gd.css +1 -0
  41. frontend/build/_app/immutable/assets/Loader.CSywfDIO.css +1 -0
  42. frontend/build/_app/immutable/chunks/B8pdRQVM.js +1 -0
  43. frontend/build/_app/immutable/chunks/BIHI7g3E.js +1 -0
  44. frontend/build/_app/immutable/chunks/BOGzIfIj.js +11 -0
  45. frontend/build/_app/immutable/chunks/BRcwu1Xf.js +1 -0
  46. frontend/build/_app/immutable/chunks/BT_qo9Cc.js +1 -0
  47. frontend/build/_app/immutable/chunks/BcWCHg3k.js +1 -0
  48. frontend/build/_app/immutable/chunks/BjvCllst.js +1 -0
  49. frontend/build/_app/immutable/chunks/BkDXvb8s.js +1 -0
  50. 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
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
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: pink
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ![1777301208961](image/ARCHITECTURE/1777301208961.png)![1777301226270](image/ARCHITECTURE/1777301226270.png)# 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"&&gt()}),(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};