Add a first-class office-document capability ALONGSIDE the ComfyUI/GPU creative
pipeline: structured content (typed template fields, filled by a person or the
AI) → a pure-Python renderer → a real .pdf/.docx/.xlsx/.pptx/.md. No graph, no
GPU. Orthogonal subsystem — not bolted onto graphs.
- middleware/doc_render.py: one neutral IR per archetype (doc/sheet/deck) and
exactly one renderer per format (DRY). PDF via WeasyPrint (full-CSS monochrome
print style) with a self-contained pure-Python PDF fallback (valid %PDF, zero
native deps — so CI/model-less pods still render). DOCX/XLSX/PPTX via
python-docx/openpyxl/python-pptx.
- middleware/documents.py: one declarative CATALOG of 22 common templates
(Documents 15, Spreadsheets 4, Presentations 3). Each template's typed fields
drive the form, the AI-fill JSON, and the renderer inputs (one schema). Per-org
store (orgs/<org>/documents, atomic index.json + <id>/doc.json), rendered on
demand. Endpoints (/v1 house style): GET /v1/documents, POST
/v1/documents/generate, GET /v1/documents/library, GET|PATCH|DELETE
/v1/documents/{id}, GET /v1/documents/{id}/download?format=. Registered via the
same injected tenancy resolvers as stacks_store.
- AI-fill is a layered enhancement — the form path renders with NO LLM, so a
broken/unauthorized gateway never blocks creation (422 needFields → prefilled
form). Reuses the shipped Chat's working token: the browser sends window.HZ.pk
(publishable pk- key) as the bearer; the backend forwards it fail-closed —
server key, else a gateway-verifiable pk-/RS-JWT; an HS256 session token or
sk-/hk- secret is NEVER forwarded. Model default enso (window.HZ.model).
- studio_home.html: a "Documents" Home tab (searchable catalog + your documents
with Download PDF/Word/Excel/PowerPoint) + describe/fill dialog; replaced the
dead doc/sheet/paper composer placeholders with one "Office documents" card.
- deps: weasyprint/python-docx/openpyxl/python-pptx (requirements.txt) + pango
native libs (Dockerfile). CI builds the image; not built locally.
- tests-unit/documents_test: catalog invariants, full template×format render
matrix asserted by magic numbers, AI-fill parse + fail-closed bearer allowlist,
and the REST surface incl. per-org isolation (44 tests, green).
- version 0.17.24 → 0.18.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
3.3 KiB
Docker
88 lines
3.3 KiB
Docker
FROM python:3.11-slim AS base
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
git \
|
|
build-essential \
|
|
libgl1 \
|
|
libglib2.0-0 \
|
|
# WeasyPrint native stack (office-document PDF renderer, middleware/doc_render.py):
|
|
# pango/cairo/gdk-pixbuf + a base font. Absent → the built-in PDF fallback is used.
|
|
libpango-1.0-0 \
|
|
libpangocairo-1.0-0 \
|
|
libgdk-pixbuf-2.0-0 \
|
|
shared-mime-info \
|
|
fonts-liberation \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /app
|
|
|
|
# Install PyTorch CPU first (large layer, cached separately)
|
|
RUN pip install --no-cache-dir \
|
|
torch torchvision torchaudio \
|
|
--index-url https://download.pytorch.org/whl/cpu
|
|
|
|
# Install remaining dependencies
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
# Apply Hanzo Studio branding to frontend package
|
|
COPY branding/ /tmp/branding/
|
|
RUN chmod +x /tmp/branding/apply-branding.sh && /tmp/branding/apply-branding.sh && rm -rf /tmp/branding
|
|
|
|
# Copy application code
|
|
COPY . .
|
|
|
|
# Vendor the Hanzo Studio custom-node packs (segmentation, controlnet, ipadapter,
|
|
# upscaling, video, utils) at their pinned commits and install their deps with the
|
|
# torch/numpy/transformers stack locked. See custom_nodes/hanzo-packs.txt.
|
|
RUN chmod +x scripts/install_custom_nodes.sh && scripts/install_custom_nodes.sh
|
|
|
|
# Create directories for runtime volumes
|
|
RUN mkdir -p models output input custom_nodes user
|
|
|
|
# /app/orgs is the tenant-data PVC; /app/models is ephemeral overlay. Register
|
|
# .models on the PVC as the default model store (main.py auto-loads this file)
|
|
# so models survive pod restarts and downloads land durable.
|
|
RUN printf '%s\n' \
|
|
'hanzo:' \
|
|
' base_path: /app/orgs/.models' \
|
|
' is_default: true' \
|
|
' checkpoints: checkpoints' \
|
|
' configs: configs' \
|
|
' loras: loras' \
|
|
' vae: vae' \
|
|
' text_encoders: text_encoders' \
|
|
' diffusion_models: diffusion_models' \
|
|
' clip_vision: clip_vision' \
|
|
' style_models: style_models' \
|
|
' embeddings: embeddings' \
|
|
' diffusers: diffusers' \
|
|
' vae_approx: vae_approx' \
|
|
' controlnet: controlnet' \
|
|
' gligen: gligen' \
|
|
' upscale_models: upscale_models' \
|
|
' latent_upscale_models: latent_upscale_models' \
|
|
' hypernetworks: hypernetworks' \
|
|
' photomaker: photomaker' \
|
|
' classifiers: classifiers' \
|
|
' model_patches: model_patches' \
|
|
' audio_encoders: audio_encoders' \
|
|
> extra_model_paths.yaml
|
|
|
|
EXPOSE 8188
|
|
|
|
# Default: listen on all interfaces, CPU mode
|
|
# Environment variables:
|
|
# STUDIO_ENABLE_IAM_AUTH=1 - Enable IAM auth via hanzo.id
|
|
# STUDIO_IAM_URL=https://hanzo.id
|
|
# STUDIO_NO_LOCALHOST_BYPASS=1 - Require auth even for localhost
|
|
# STUDIO_MULTI_TENANT=1 - Per-org storage isolation
|
|
# STUDIO_ORG_ID=default-org - Default org ID
|
|
# STUDIO_ENABLE_BILLING=1 - Commerce billing integration
|
|
# STUDIO_BILLING_CHECK_BALANCE=1 - Check balance before prompts
|
|
# STUDIO_COMMERCE_URL=http://commerce.hanzo.svc:8001
|
|
# STUDIO_COMMERCE_TOKEN=... - Commerce API token
|
|
# STUDIO_ENABLE_METRICS=1 - /metrics endpoint for VictoriaMetrics
|
|
# STUDIO_RATE_LIMIT_RPM=60 - Prompts per minute per org
|
|
CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--cpu"]
|