# Existing React interface FROM node:22-alpine AS frontend WORKDIR /web ENV NODE_TLS_REJECT_UNAUTHORIZED=0 COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci COPY frontend/ ./ RUN npm run build # Backend API + embedded LanguageTool for HF Spaces / single-container FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ HOST=0.0.0.0 \ PORT=7860 \ APP_TITLE="ZuZu Writer" \ WN_DATA_DIR=/opt/wn_data \ ENGINE_PARAPHRASE=true \ ENGINE_PARAPHRASE_PRIMARY=true \ ENGINE_PARAPHRASE_MODEL=Vamsi/T5_Paraphrase_Paws \ ENGINE_PARAPHRASE_MIN_SIM=0.72 \ ENGINE_PARAPHRASE_NUM_RETURN=6 \ ENGINE_PARAPHRASE_MAX_NEW_TOKENS=72 \ ENGINE_PARAPHRASE_MAX_SURFACE=0.70 \ ENGINE_PARAPHRASE_MIN_DIVERGENCE=0.30 \ ENGINE_FORCE_REWRITE=false \ ENGINE_USE_MINILM_SAFETY=true \ ENGINE_CLASSICAL_AGGRESSIVE=true \ ENGINE_STRUCTURAL_VARIATION=true \ ENGINE_LEXICAL_REFINEMENT=true \ ENGINE_PHRASE_REWRITE=true \ ENGINE_PHRASE_USE_T5=false \ ENGINE_PRESERVE_LENGTH=true \ ENGINE_SPLIT_LONG=true \ HF_HOME=/opt/hf_cache \ TRANSFORMERS_CACHE=/opt/hf_cache \ SENTENCE_TRANSFORMERS_HOME=/opt/hf_cache \ LANGUAGETOOL_HOME=/opt/languagetool \ LANGUAGETOOL_PORT=8010 \ LANGUAGE_TOOL_URL=http://127.0.0.1:8010 \ LANGUAGE_TOOL_ENABLED=true \ LANGUAGE_TOOL_EMBEDDED=true \ LANGUAGE_TOOL_LANGUAGE=en-US \ LANGUAGE_TOOL_TIMEOUT=90 \ LANGUAGE_TOOL_CHUNK_CHARS=1800 \ GRAMMAR_MAX_CHARS=12000 \ LANGUAGETOOL_JAVA_OPTS="-Xms256m -Xmx1024m" WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ curl \ ca-certificates \ unzip \ openjdk-21-jre-headless \ && rm -rf /var/lib/apt/lists/* # LanguageTool stable server (self-hosted grammar) RUN curl -fsSL -o /tmp/lt.zip https://languagetool.org/download/LanguageTool-stable.zip \ && unzip -q /tmp/lt.zip -d /opt \ && LT_DIR="$(find /opt -maxdepth 1 -type d -name 'LanguageTool-*' | head -n 1)" \ && mv "$LT_DIR" /opt/languagetool \ && rm -f /tmp/lt.zip \ && test -f /opt/languagetool/languagetool-server.jar COPY requirements.txt . # spaCy model wheel is listed in requirements.txt. # CPU torch + transformers power the local T5 paraphraser and MiniLM ranker. RUN pip install --upgrade pip \ && pip install --index-url https://download.pytorch.org/whl/cpu torch \ && pip install -r requirements.txt # Offline lexical resource used only when ENGINE_LEXICAL_REFINEMENT=true. RUN curl -fsSL -o /tmp/oewn.xml.gz \ https://github.com/globalwordnet/english-wordnet/releases/download/2025-edition/english-wordnet-2025.xml.gz \ && python -c "import wn; wn.add('/tmp/oewn.xml.gz')" \ && rm -f /tmp/oewn.xml.gz COPY app ./app COPY app.py . COPY README.md . COPY scripts/start.sh /app/scripts/start.sh RUN sed -i 's/\r$//' /app/scripts/start.sh && chmod +x /app/scripts/start.sh COPY --from=frontend /web/dist ./frontend/dist # Prefetch paraphrase + MiniLM so Space cold-start does not hit the Hub. RUN python - <<'PY' from transformers import AutoModelForSeq2SeqLM, AutoTokenizer name = "Vamsi/T5_Paraphrase_Paws" AutoTokenizer.from_pretrained(name) AutoModelForSeq2SeqLM.from_pretrained(name) from app.pipeline.minilm import warm_minilm ok = warm_minilm() print("prefetch ok", name, "minilm", ok) PY EXPOSE 7860 # LT cold-start can take 1–2 minutes HEALTHCHECK --interval=30s --timeout=5s --start-period=180s --retries=5 \ CMD curl -fsS http://127.0.0.1:7860/health || exit 1 CMD ["/app/scripts/start.sh"]