Komponenty¶
Detail každé komponenty v systému: co dělá, kde běží, jak se konfiguruje.
Frontend (Cloudflare Worker)¶
Repo: Algawebbusiness/nemoreport-ai-frontend-v2 (private)
URL: https://nemoreport-ai-frontend-v2.algaweb.workers.dev
Stack: Next.js 16 + Tailwind v4 + Supabase SSR + Zustand
Deploy: npm run deploy (wrangler) — manuální, ne auto
Co dělá¶
- SSR rendering — server components fetch dat z Backend API přes JWT v cookies
- Auth flow — magic link sign-in, session management (Supabase SSR cookies)
- Upload UI — drag-and-drop, progress tracking přes Supabase Realtime
- Reports list + detail — grid karet + folder detail s přílohami
- Chat iframe (Phase A.1 port, Phase D bude rewire na RAG) — embed-first design
- Admin dashboard — feedback, providers, cost (ADMIN_HASH gated)
- Migrate UI — claim flow pro v1 testery
Architektonické pivoty¶
CF Pages → CF Workers: původní plán Pages + static export narazil na nekompatibilitu s Supabase SSR auth (middleware + server components potřebují Node-like runtime). Řešeno: @opennextjs/cloudflare adapter, deploy jako Worker. Build 6.3 MB / 1.3 MB gzip.
Žádný proxy.ts middleware: Next 16 proxy nepodporuje Edge runtime a CF Workers nemají Node.js. Session refresh se děje pasivně přes @supabase/ssr cookie handling při každém server-component requestu. getClaims() v root page.tsx + protected routes + apiFetch 401 fallback = funkční auth gating bez centralizovaného middleware.
Key files¶
src/app/— Next.js 16 app router pagessrc/components/— reusable React komponentysrc/lib/supabase/{client,server}.ts— Supabase SSR clients (browser + server)src/lib/api.ts— typed wrapper kolemfetchs JWT injectionnext.config.ts+open-next.config.ts+wrangler.jsonc— CF Workers config
Backend (Docker kontejner)¶
Repo: Algawebbusiness/nemoreport-ai-backend-v2 (private, sdílí codebase s workerem)
URL: https://nemoreport-ai-backend-v2.sliplane.app
Service ID: service_gp6fgtvypx2q
Stack: FastAPI 0.135 + Pydantic AI + uvicorn + slowapi (rate limit)
Deploy: auto-deploy z main branch (Docker hosting GitHub integration)
Co dělá¶
- REST API pro frontend (JWT-authed) i Nette integration (HMAC-authed)
- Storage uploads přes Supabase service_role (bypass RLS for path-scoped buckets)
- Async job dispatch — enqueue worker tasks přes taskiq broker
- Rate limiting — slowapi s Redis backendem (per-IP defaults)
- Auth verification — JWT přes JWKS cache, custom claims extraction, HMAC verification pro Nette path
- Retrieval orchestration (Phase C) — embed query, RPC call, Cohere rerank, response assembly
Key files¶
app/
├── main.py # FastAPI app + middleware + router registration
├── config.py # Pydantic Settings (fail-fast on missing secrets)
├── auth.py # JWT verification, AuthUser, require_admin, verify_nette_jwt (Phase E)
├── deps.py # FastAPI dependencies (CurrentUser, ServiceDB, RequireAdmin)
├── db.py # DB facade (40+ methods, service_role + user_jwt clients)
├── storage.py # Storage helpers (upload/download/sign URL/path builders)
├── supabase_client.py # Supabase Python client factories
├── rate_limit.py # slowapi limiter setup
├── routers/ # FastAPI routers (1 file per concern)
│ ├── ingestion.py # POST /ingest, /uploads, GET /attachments
│ ├── retrieve.py # POST /retrieve (Phase C)
│ ├── chat.py # POST /chat (legacy, Phase D rewrite TBD)
│ ├── reports.py # GET /reports, /reports/{id}
│ ├── admin.py # /admin/* (HASH-gated)
│ ├── nette.py # /reports/{id}/attachments/system (HMAC)
│ ├── me.py # GET /me
│ ├── migrate.py # /migrate/* (v1 → v2 claim)
│ └── ...
├── ingestion/ # Phase B helpers (Mistral, schemas, chunking)
├── embedding/ # Phase C — Gemini Embedding 2 wrapper
├── retrieval/ # Phase C — query orchestration, rerank, rewrite
└── worker.py # taskiq tasks (5-stage pipeline)
Konfigurace¶
ENV vars (Docker hosting secrets), všechny strict-validated v config.py:
SUPABASE_URL # https://cubdrgjdkatyecrgckwp.supabase.co
SUPABASE_ANON_KEY # PostgREST JWT-based ops
SUPABASE_SERVICE_ROLE_KEY # NEVER send to FE, secret
GEMINI_API_KEY # Gemini 3 Flash + Embedding 2
MISTRAL_API_KEY # Mistral OCR
COHERE_API_KEY # Cohere Rerank 4.0 (Phase C.7)
ADMIN_HASH # /admin/* gate
NETTE_HMAC_SECRET # Phase B.11 attachments endpoint
ALLOWED_ORIGINS # CORS — workers.dev,localhost:3000
RATE_LIMIT_STORAGE_URI # redis://nemoreport-redis.internal:6379/0
REDIS_URL # taskiq broker — db 1
EMBEDDING_MODEL # gemini-embedding-2 (GA)
EMBEDDING_MODEL_VERSION # ga-2026-04
COHERE_RERANK_MODEL # rerank-v4.0-pro
COHERE_RERANK_ENABLED # true / false
LLM_MODEL # google-gla:gemini-3-flash-preview
APP_ENV # production / staging / development
Worker (Docker kontejner)¶
Service ID: service_p8nz4hbtcw1r
Codebase: stejný jako Backend (nemoreport-ai-backend-v2)
CMD: uv run python -m app.worker_entry
Stack: taskiq + RedisStreamBroker + Pydantic AI + Mistral SDK
Co dělá¶
Konzumuje Redis Stream nemoreport_ingestion (consumer group nemoreport_workers), provádí 5-stage pipeline:
- scan_target — re-validate MIME + transition status='parsing'
- parse_target — Mistral OCR (PDF) / BS4 (MHTML) / python-docx (DOCX) → INSERT parsed_sections + figures
- annotate_target — Gemini multimodal fallback pro figures s
annotation_source='pending' - embed_target — chunkování + Gemini Embedding 2 (text + multimodal) → INSERT chunks
- finalize_target — status='ready'
Each stage je samostatný taskiq task s retry_on_error=True, max_retries=3 a idempotentní (DELETE existing data před re-run).
Lifecycle¶
app/worker_entry.py — production runner:
- Spustí uvicorn na :8000 (container healthcheck)
- Spustí taskiq worker subprocess (
taskiq worker app.worker:broker --max-async-tasks 10 --ack-type when_executed) - SIGTERM propagation, supervisor thread
2 worker procesy × 10 async tasks = až 20 souběžných stages.
Redis (Docker kontejner)¶
Service ID: service_omzjff1bshe5
Internal DNS: nemoreport-redis.internal:6379
Persistence: AOF (append-only file)
DB rozdělení¶
| DB | Použití |
|---|---|
redis://...:6379/0 |
slowapi rate limit storage |
redis://...:6379/1 |
taskiq broker (nemoreport_ingestion stream + result backend, 1h retention) |
Supabase¶
Project ref: cubdrgjdkatyecrgckwp
URL: https://cubdrgjdkatyecrgckwp.supabase.co
Postgres + RLS¶
Schema: nemoreport (NOT public — explicit isolation)
Tabulky: 22 (vytvořené 18 migracemi 0001-0018)
Detail viz Datový model a Databáze.
Auth¶
- Magic link přes Resend SMTP (sandbox = jen owner email zatím)
- Custom JWT hook (
private.custom_access_token_hook) injektuje claims: personal_tenant_idactive_tenant_id- Custom email template — token_hash query flow (ne implicit hash flow)
- handle_new_user trigger — auto-creates personal tenant + user_profiles + tenant_member
Config v supabase/config.toml (commited do repa, push přes supabase config push --yes — žádná Dashboard click-ops).
Storage (4 buckety)¶
| Bucket | Účel | Path layout |
|---|---|---|
nemoreport-uploads |
main uploaded files (user uploads) | {tenant_id}/reports/{report_id}/original.{ext} |
nemoreport-attachments |
přílohy (Nette + user_upload) | {tenant_id}/reports/{report_id}/attachments/{attachment_id}.{ext} |
nemoreport-figures |
extrahované obrázky / mapy / výkresy | {tenant_id}/reports/{report_id}/figures/{figure_id}.{ext} |
nemoreport-reports |
legacy (z Phase A migrace 0004, v2 nepoužívá) | — |
Storage backend: Supabase managed storage (jejich infrastruktura, S3-compatible API). Cloudflare R2 přímo NEPOUŽÍVÁME — cf-ray headers v Supabase Storage responses jsou jen kvůli tomu, že Supabase má Cloudflare jako CDN/edge proxy před svým API (vendor implementační detail).
Co je v DB: jen relativní storage_path (např. {tenant_id}/reports/{report_id}/original.pdf), žádné celé URL.
Jak frontend dostane file: backend volá supabase.storage.from_(bucket).create_signed_url(path, expires_in=3600) → vrací time-limited signed URL na endpoint https://cubdrgjdkatyecrgckwp.supabase.co/storage/v1/object/sign/....
RLS policies: bucket má path-based scoping {tenant_id}/... — user vidí jen svoje cesty. Service role bypassuje (worker, BE).
Realtime¶
Publication supabase_realtime obsahuje:
nemoreport.reportsnemoreport.attachments
(NE chunks, figures, parsed_sections — frontend si je pulluje on-demand když status='ready'.)
Frontend subscribe per report_id, dostává UPDATE events pro live status tracking.
Externí AI služby¶
Detail viz Externí API.
Mistral OCR¶
- Model:
mistral-ocr-latest - Účel: PDF + image OCR + bbox annotations + document annotations
- Cena: $0.001-0.030 / page
- Limity: HTTP timeout 15 min (config
mistral_timeout_ms=900_000), velké PDFs > 500 KB skipnu bbox annotations - License: komerční API (Mistral AI)
Google Gemini¶
- Modely:
gemini-3-flash-preview(LLM),gemini-embedding-2(GA, embedding) - Účel: figure annotation (multimodal), HyDE generation, embedding (text + multimodal)
- Cena: ~$0.0001-0.001 / call
- Limity: standard Gemini quotas (1500 RPM tier 1)
- License: Google AI Terms of Service (commercial OK)
Cohere Rerank 4.0¶
- Modely:
rerank-v4.0-pro(default),rerank-v4.0-fast(cost fallback) - Účel: cross-encoder rerank top-N kandidátů z hybrid retrieve
- Cena: $0.0025 / search (= 1 query, top_n libovolný)
- Limity: 32K context, 100+ jazyků vč. CZ
- Trial limit: ~1000 calls / měsíc, 10 RPM (potřeba production key pro pilot)
- License: Cohere Terms of Service (commercial OK pro paid tier)
Docker kontejnery¶
3 Docker services na společném hostu (single Docker daemon, internal network):
| Service | Účel | Spec |
|---|---|---|
nemoreport-ai-backend-v2 |
FastAPI | Single instance, auto-deploy z GitHub main |
nemoreport-ai-worker-v2 |
taskiq worker | 2 procs × 10 tasks (až 20 simultánních) |
nemoreport-redis |
Redis 7 (queue + cache) | AOF persistent |
Internal Docker network umožňuje BE → Redis a Worker → Redis komunikaci bez public exposure (stejný host, internal DNS resolution).
Aktuální hosting platforma viz Deployment.
Cloudflare¶
Pages projekt: ❌ původně plánovaný, opuštěný kvůli SSR nekompatibilitě
Workers: nemoreport-ai-frontend-v2 (manuální deploy přes wrangler)
R2: NEPOUŽÍVÁME přímo. Supabase Storage má vlastní backend (vendor managed).
Verzování + dual push¶
Kód je v dvou git remotes:
- GitHub (primary):
Algawebbusiness/nemoreport-ai-{backend,frontend}-v2 - GitLab (mirror):
git.algaweb.cz/algaweb/nemoreport-ai-{backend,frontend}-v2
origin má oba pushURL — git push origin main pushne na oba zároveň. fetch jen GitHub.