feat: AI-generated theme cover images (Flux Schnell + R2)#63
Merged
Conversation
Closes #62 Adds manual cover-image generation for themes via Replicate's Flux Schnell. Writer clicks "Generate", picks from 4 candidates in a grid, chosen image is re-uploaded to R2 for a permanent URL. Displayed as a 56px thumbnail in the feed and 240px square on the permalink page. Prompt builder uses a fixed style suffix (flat oil, three-color palette, contemporary museum-quality, figurative) to keep all generated images visually cohesive. Tags feed in as mood, theme body as scene evocation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
🚅 Deployed to the breadcrumbs-pr-63 environment in Breadcrumbs
|
Nudge Flux toward depicting diverse figures — people of color, varied ages, varied body types — when humans appear in generated scenes. Without this, Flux defaults heavily toward a narrow demographic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address review findings on PR #63: Critical: - SSRF: validate source_url against Replicate host allowlist + reject non-https + disable redirect following in commit_image_to_r2 - R2 env-var validation: extract app/storage.py with assert_r2_env() and put_object() helpers; fail with 500 if R2 misconfigured instead of silently persisting relative URLs - Replicate error surface: catch ReplicateError + httpx.HTTPError explicitly, raise ImageGenerationError with proper logging; map to 503 in the endpoint with useful detail Important: - Extract R2 client to app/storage.py — both /uploads and the new commit endpoint use the same put_object() helper now - Hoist app.images imports to module top in api.py - Remove dead NotImplementedError catch - Drop image_url from THEME_UPDATABLE_FIELDS to prevent arbitrary URL injection via PUT /themes/{id}; add dedicated DELETE /themes/{id}/image for clearing - Reject unknown content-types instead of silently saving as .webp - Add image magic-byte validation before R2 upload - Render clearImage error in writer UI - Structured logging in app/images.py Tests (33 new): - 13 unit tests for build_theme_prompt, tags_for_prompt, _is_allowed_source, _looks_like_image - 12 endpoint tests covering 404s, missing token, host allowlist, https enforcement, happy paths with replicate mocked, whitelist guard, clear endpoint All 159 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #62
Summary
image_urlis set).Verified locally
Test plan
uv run dev— backend starts without errorcd frontend && npm run dev— frontend startsimage_urlpersisted/themes/{id}permalink — verify larger image above bodyREPLICATE_API_TOKENand click Generate — verify clean 503 error in UI (not a crash)Notes for reviewer
idx_breadcrumb_parent_id, removed columnsubscriber.confirmed) was detected during autogenerate and stripped from this migration. Needs a follow-up cleanup PR.go_fast=trueis Flux Schnell's default — seeds are non-deterministic. This is fine for the "get 4 fresh options" UX but means you can't reproduce a specific generation by freezing a seed./imagecommit endpoint downloads + re-uploads to R2 for permanent storage. Same R2 setup as the existing/uploadsendpoint.🤖 Generated with Claude Code