A custom single-restaurant web app: paste today's menu (like a WhatsApp message), click one button, get beautiful PNG posters ready for WhatsApp Status / Instagram / Facebook.
Every submission generates five posters — four hand-crafted templates (Organic, Elegant, Bold, Broadsheet) plus one AI-designed poster created on the fly by the LLM itself using your brand identity tokens. Download whichever looks best that day.
Staff pastes the menu text
↓
GLM-5.2 (via NVIDIA NIM + OpenAI SDK) turns it into structured JSON
{ title, mains[], complements[], drinks[] }
(LLM also picks the best matching photo filename per dish)
↓
Each dish is matched to a photo in media/foods/ by filename.
If a hero dish has no local photo, the image search waterfall runs:
Google Custom Search → DuckDuckGo → Unsplash
The first hit is downloaded and permanently cached in media/foods/.
↓
Five posters are rendered:
• organic — white bg, leaf decorations, circular dish photos
• elegant — cream bg, gold rules, hero photo strip
• bold — high-contrast, large type
• broadsheet — newspaper-style layout
• dynamic — designed from scratch by the LLM using brand tokens ✦
↓
Playwright (headless Chromium) screenshots each to a 1080×1350 PNG
↓
Staff downloads the PNG they prefer
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python manage.py setup_browser # installs Chromium for Playwright (one-time)
cp .env.example .env
# Open .env and add your NVIDIA_API_KEY
# (get one free at https://build.nvidia.com/)
python manage.py migrate
python manage.py runserverOpen http://127.0.0.1:8000 — paste a menu, click "Générer l'affiche", download.
| Variable | Required | Description |
|---|---|---|
NVIDIA_API_KEY |
Yes | NVIDIA NIM key for GLM-5.2 inference |
DJANGO_SECRET_KEY |
Yes (prod) | Long random string for Django sessions |
GOOGLE_SEARCH_API_KEY |
Optional | Upgrades image search to Google CSE |
GOOGLE_SEARCH_ENGINE_ID |
Optional | Goes with the key above |
RENDER_BASE_URL |
Optional | Playwright base URL (default: http://127.0.0.1:8000) |
- SDK: OpenAI Python SDK — same interface works with OpenAI, Anthropic, Mistral, NVIDIA NIM, and any other OpenAI-compatible endpoint.
- Model: zhipuai/glm-4-9b-chat (GLM-5.2) hosted on NVIDIA NIM.
- Switch models: change LLM_MODEL + NVIDIA_BASE_URL + NVIDIA_API_KEY in config/settings.py.
When a dish has no local photo and is needed as a hero, three sources are tried:
- Google Custom Search (optional) — highest quality, needs API key
- DuckDuckGo images (free, no key) — solid results for most dishes
- Unsplash Source (free, no key) — generic food photo as last resort
All downloads are cached permanently in media/foods/ so future menus never re-search the same dish.
The LLM receives layout skills (rules for great 1080x1350 menu poster structure), brand identity tokens (palette, typography, mood, hard constraints), and the full menu data with image URLs. It returns a complete HTML/CSS document which is Django-template-rendered server-side then screenshotted by Playwright.
The brand identity system lives in BRAND_IDENTITY in config/settings.py. Adapt it per restaurant by swapping colour values and fonts.
config/
settings.py Django settings + BRAND_IDENTITY + LLM config
menu/
llm_service.py OpenAI-SDK client for GLM-5.2 via NVIDIA NIM
image_matcher.py Filename-based dish to photo matching
image_search.py Image search waterfall: Google, DuckDuckGo, Unsplash
poster_renderer.py HTML to PNG via Playwright (static + dynamic modes)
models.py GeneratedMenu history record
views.py home / generate_menu / download_poster
templates/menu/
posters/ organic, elegant, bold, broadsheet (hand-crafted)
static/menu/
img/ logo.png, default-dish.png
media/
foods/ Real dish photos (matched by filename)
generated/ Output PNGs land here