--- title: MyPal (9XAIPal) emoji: 📚 colorFrom: blue colorTo: indigo sdk: docker app_port: 7860 pinned: false license: mit --- # 9XAIPal — local-first reading companion Single-container Hugging Face Space deployment. A plain FastAPI app (`app.py`) mounts the real 9XAIPal backend API at `/api/v1` and serves the compiled React UI at `/` (SPA with client-side routing) — all on port `7860`. ## Layout | Path | Purpose | | ----------------- | -------------------------------------------------- | | `app.py` | FastAPI entrypoint (API + SPA, port 7860) | | `Dockerfile` | Builds the image; bakes in the Ollama embedder | | `start.sh` | Boot script: Redis, Ollama, then supervisord (API + Celery worker + optional local Postgres) | | `supervisord.conf`| Process supervisor config: keeps API + worker alive | | `requirements.txt`| Python dependencies | | `app/` | 9XAIPal backend package | | `dist/` | Compiled React UI | ## Links - **YouTube Demo Video:** [https://youtu.be/m-uIaNKOOrk?si=4FpcZtlKJwZpPJnz](https://youtu.be/m-uIaNKOOrk?si=4FpcZtlKJwZpPJnz) - **GitHub Repository:** [https://github.com/Khaled-Saleh-KL1/9XAIPal](https://github.com/Khaled-Saleh-KL1/9XAIPal) - **X (Twitter) Post:** [https://x.com/KL1_Suii/status/2066237932713775393?s=20](https://x.com/KL1_Suii/status/2066237932713775393?s=20) ## Configuration (set as Space **Secrets / Variables**) All config is read from environment variables — **no `.env` file is bundled**. Set these under *Settings → Variables and secrets*: - `OLLAMA_BASE_URL`, `OLLAMA_API_KEY` — chat/VLM backend (Ollama Cloud) - `CHAT_MODEL`, `VLM_MODEL` — **must match an exact tag served by your `OLLAMA_BASE_URL`**. If the endpoint lists `gemma4:31b` and not `gemma4:31b-cloud`, set `CHAT_MODEL=gemma4:31b` and `VLM_MODEL=gemma4:31b`. - `EMBEDDING_PROVIDER=ollama`, `EMBEDDING_BASE_URL=http://localhost:11434`, `EMBEDDING_MODEL=qwen3-embedding` — embeddings run on the **in-container** Ollama (the 31B chat model lives on Ollama Cloud, which hosts no embedding models, so a small embedding model is baked into the image and served locally) - `HF_TOKEN` / `HUGGING_FACE_HUB_TOKEN` — model-weight downloads - **Database** — `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `PGSSLMODE=require`. The Space container disk is **ephemeral**, so this deployment points at an **external managed Postgres** (Supabase, with `pgvector`) so uploaded papers persist across restarts/rebuilds. Use Supabase's **session-mode connection pooler** host (`aws-0-.pooler.supabase.com:5432`, user `postgres.`) — the direct `db..supabase.co` host is IPv6-only and unreachable from the IPv4-only Space container. - (optional) `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / … for cloud fallback > Note: `start.sh` boots Redis and an in-container Ollama (for embeddings), then > launches `supervisord` to keep the FastAPI app **and a Celery worker** alive. > PostgreSQL runs **externally** (managed) whenever `POSTGRES_HOST` is set to a > non-local host — the bundled in-container Postgres is started only as a fallback > for local/no-external-DB runs, and its data is ephemeral. Redis here is just an > ephemeral cache/queue. > > **PDF extraction:** the image enables the PyMuPDF text-only fallback by default > (`ALLOW_PYMUPDF_FALLBACK=1`) so uploads process immediately without baking > several gigabytes of MinerU + torch into the image. For higher-fidelity output > (OCR, tables, equations, figures), install `mineru[pipeline]>=3.2.0`, `torch`, > `torchvision`, and `scipy>=1.17.1` in `requirements.txt` and set > `ALLOW_PYMUPDF_FALLBACK=0` (or unset) in the Space variables. > > **Self-healing:** the container has a Docker `HEALTHCHECK` on `/healthz` and an > in-app watchdog that detects stuck ingestion jobs. If a paper hangs in > `queued` / `extracting` / `chunking` / `embedding` / `summarizing` for too long, > the watchdog revokes the stuck Celery task, cleans up partial state, and > re-dispatches the job so processing completes.