Skip to content

Latest commit

 

History

1,252 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GenZen

A personal workspace for making, comparing, and organizing AI images and video. Work from prompts and reference images, try several models at once, and keep the results together with the context that made them.

The working principle is immediate feedback: capture the idea, show the work in progress, and let the next idea follow. Image submissions show pending thumbnails before planning or rendering finishes, so you can keep prompting while earlier batches run in the background.

Images with grouped results, model labels, reference images, and a multi-model generator

Images: explore several directions at once

Send one or several prompts to multiple models, choose how many results you want from each, and compare the outputs side by side. Attach uploaded or generated images as references, with their order visible in the composer. The panel shows the output count and estimated cost before you generate.

Images is also the working library. Generate inside a group and the results land there; generate at the top level and they stay there. Hide the takes you do not want to look at, restore hidden images when needed, and use Trash for removal. Open an image at a larger size to judge it, inspect its details, or load its prompt and references back into the composer for another pass.

Storyboards: full-size shots from one idea

Type /storyboard directly in the image prompt field, followed by a scene idea. Attach references for the subject, setting, or look you want to carry through. For example:

/storyboard Six shots of this vehicle driving through an empty downtown.
Start wide, move closer, and finish beside a deserted fountain.

A shared plan establishes continuity and assigns each shot its own composition. Every shot is generated as a separate full-size image using the original references. The default is six 16:9 shots; request two through nine in the brief or with --shots N.

The output count includes shots, models, and variants. After any batch confirmation, every shot gets its own numbered pending thumbnail immediately, and the composer remains available for more work. Results stay in the current group or at the top level.

If you later want a single sheet, select the images and use Reference sheet to assemble a downloadable composite.

Develop a picture further

Shots explores camera angles around a subject, using a reasoning model to inspect the actual subject and turn the chosen direction into rendering instructions. Outpaint reframes a finished image into other aspect ratios.

These tools create new images, keeping the source available for another attempt. Download individual results, a group, or a selection as a ZIP.

Video: direct the motion and review the result

Generate a clip from a prompt, add a first or last frame, or supply reference images where the chosen model supports them. Image roles are explicit, and model compatibility, duration, shape, and resolution guide the available choices. Several prompts can produce several clips in one submission. The lineup is Seedance 2.5, Veo 3.1 Fast, Kling, MiniMax, LTX, and Flux; Seedance takes reference images or first/last frames, native audio, and clips up to 30 seconds. For models that support sound, Generate audio lets you choose audio or silent output.

Click a video thumbnail to open a large player with the complete frame, playback controls, and fullscreen support. First and last frame previews help you scan clips in the library. Continue takes a clip's ending as the starting image for the next generation and brings its prompt forward for editing.

Organize clips in video groups, hide takes while comparing results, and use Trash for removal. Generating inside a video group keeps the new clips there.

Director: build a sequence over time

A session is a name and a run of clips, watched back to back — the question the whole page is for is whether the order cuts together and whether the next clip follows. A session can hold several cuts, shown as tabs, each a run of its own.

Add gen makes the next clip from inside the run: the last clip's final frame in the first slot, a prompt, a duration, one button. The pencil on a tile names a clip, or re-rolls it in place — a clip in the middle is pinned at both ends, so the joins either side survive. New cut from script remakes the open cut in one pass, from optional direction on style, pacing, or tone, each clip starting from the last one's final frame. A session can also start as a chat: ask a question and an invented character answers in short clips.

Director's clips belong to their session: they are not on the Video wall, they show in Activity for the cost record, and removing one from the run trashes it.

Edit: trim clips on a timeline and watch the cut

An edit is a timeline of clips off the Video wall. Add clips, drag them into order, and drag each tile's edges to set where it starts and stops -- the tiles are as wide as they are long, a ruler above them is the clock, and the cut plays straight through in the browser with nothing rendered. Space plays and pauses, Left and Right step a frame, S splits at the playhead, and F saves the frame on screen to a group on Images named after the edit, shown under the timeline too. Continue makes the clip between the highlighted clip and the next, pinned at both frames, and holds its place on the strip while FAL works. Export to Video cuts it into one clip on the Video wall.

News: what changed in image and video models

News is an illustrated feed. A run has Claude search for recent image and video model news, compared against the models genzen already offers, and writes each story up as a post with a generated hero image. A post opens to its full write-up and sources.

On a phone

Below tablet width the app has a phone layout. A green menu button sits bottom left: Images, Video, and More, which flies out with everything else. A green plus bottom right opens a one-tap composer on Images and Video. Walls are 2-up, and the image and video viewers fill the screen; swipe the image viewer to move through the set. Edit is desktop only.

Keep the context and the cost

Activity records generation prompts, references, model settings, timing, estimated cost, and failures. Storyboard runs retain the shared plan and each shot's rendering request. For images, Load restores an editable starting point, while Retry replays a failed request's saved inputs and settings.

Account summarizes recorded spend and output counts by model, alongside connection status, appearance settings, and keyboard shortcuts. Costs are estimates, not invoices: provider billing can differ, and AI planning can add usage beyond the image estimate shown in the composer.

Account overview with estimated spend, output counts, model breakdowns, and connection status

Lab

Lab is where ideas, model capabilities, and workflows are being worked out and experimented with. Its tools can change as we learn what is useful; the main workspaces are where established workflows live.

Who it is for

GenZen is a personal creative workspace, with real accounts and per-user isolation but no public signup, teams, or sharing. Run it locally or deploy your own instance; generations bill the provider accounts whose keys you configure.

Postgres and media storage run locally with Docker. FAL provides image and video generation, and Anthropic provides the reasoning and vision used by AI-assisted workflows such as storyboards. The app can start without provider keys; the features that call those providers need their keys configured.

Run it locally

You need Docker, pnpm, and Node 22.13+. Postgres and media storage run in local containers; the app handles authentication.

The Node floor is not cosmetic: packageManager pins pnpm 11, which imports node:sqlite and cannot run on Node 20. Corepack fetches pnpm before anything reads engines, so an older Node fails during pnpm install with ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite and no mention of your Node version.

A FAL_KEY is optional to start — the app runs without one, but image and video generation need it. Supplying one means generations bill your fal.ai account; nothing is mocked.

pnpm install
pnpm local:up                  # asks for your API keys, sets up everything else
pnpm dev                       # http://localhost:3000

That is the whole setup — there is no global CLI to install and no env file to copy or edit. local:up starts Postgres and MinIO (S3-compatible storage) from docker-compose.yml, writes .env.local for you, applies any migrations the database has not seen, generates and provisions a login, and prompts for your FAL and optional Anthropic keys. Re-run it any time: it is idempotent, it keeps your key, and it never resets a database you have been working in. pnpm local:reset is the deliberate way to start over.

Thing Where
App http://localhost:3000
Sign in as printed by local:up, kept in .env.local
MinIO console http://localhost:9011 (genzenlocal/genzenlocal)
Postgres postgres://genzen:genzen@localhost:5434/genzen

There is no shipped account. local:up generates a password on first run, creates the user, and prints the login; it lands in .env.local as LOCAL_DEV_EMAIL / LOCAL_DEV_PASSWORD. Edit either one and re-run to change it — the file is the source of truth and the password is re-synced from it, which is also the whole password-reset story. pnpm users manages accounts on a deployed instance — list, add, delete — and takes --local to work on the docker stack instead.

FAL is not mocked — generation calls fal.ai for real and costs real money. The app boots and everything else works without a key. Generation cost estimates are recorded in the Activity log.

Ports: MinIO is on 9010/9011 rather than its default 9000/9001, so this stack can run alongside another local one holding MinIO's defaults.

If a shell-exported FAL_KEY shadows the one in .env.local, local:up warns about it — that's the usual reason generation 401s.

Scripts

Command Purpose
pnpm local:up Start the local stack, write .env.local
pnpm local:down Stop it (data kept)
pnpm local:reset Stop it and delete the volumes
pnpm dev Next dev server on :3000
pnpm build Production build
pnpm test Vitest
pnpm check Prettier + ESLint --fix + color, token and class checks (run before commit)
pnpm check:colors Fail on a raw color outside tokens.css
pnpm check:tokens Fail on a var(--x) that is declared nowhere
pnpm check:classes Fail on a CSS module class that is used but never defined
pnpm typecheck tsc --noEmit (the build typechecks too)
pnpm db:migrate Apply pending migrations/*.sql
pnpm users List/add/delete logins; -h for usage, --local for docker. Reaching a deployed database needs an authenticated Railway CLI
pnpm check:claude-md What the pre-commit hook checks (advisory)
pnpm activity:inspect Load a pasted Activity URL's stored data and media, no browser needed
pnpm context:find Find uploads/generations by recency, local date or text
pnpm context:inspect Load a generation's stored data and media by URL or id
pnpm rive:build Rebuild the News progress animation from rive/news-progress/

Stack

Layer Tech
App Next.js App Router (React 19 + Turbopack)
UI CSS Modules + Base UI, on the tokens in src/styles/
Data Postgres, queried with SQL via postgres (no ORM)
Auth scrypt + signed session cookie, own users table
Storage S3 — MinIO locally, a Railway bucket in production
Images/video FAL
Text/vision Anthropic — prompt work, and vision

Repo map

Checked by src/lib/repo-map.test.ts — a path named here that does not exist fails the build.

Path What's there
src/features/<name>/ Domain modules. Each has its own CLAUDE.md — read it before editing the feature.
src/lib/server/ .server.ts = never client-importable; .action.ts = a 'use server' module.
src/components/ Primitives, one folder each, imported from the root barrel #/components.
app/api/ Route handlers (app/api/auth/sign-out/).
migrations/ Numbered SQL migrations, applied by pnpm db:migrate.
docs/SPEC.md What the app does and the rules that must hold.
docs/OVERVIEW.md What genzen is, and what it deliberately is not.
docs/DELTAS.md genzen's deltas from project-standard.

Env

Locally there is nothing to configure. pnpm local:up writes .env.local itself and prompts you for the values that are actually yours: your FAL key, and your Anthropic key if you want the AI-assisted features. The app runs without the second.

.env.example is the reference for deploying, split into Required (a Postgres URL, a session secret, FAL, an S3 bucket) and Optional (Anthropic). docs/deploying.md covers the rest: what a deployment needs, the two non-default settings, and how the first user is made.

The R2_* names are historical and are staying that way (#242). The storage layer is plain S3 pointing wherever R2_ENDPOINT says — MinIO locally, a Railway bucket in production, Cloudflare R2 nowhere. The bucket must be private (#226); the app serves images itself. R2_ACCOUNT_ID derives Cloudflare's endpoint and is unused.

Provider keys are server-only. Only NEXT_PUBLIC_* reaches the browser — Next inlines nothing else, and the VITE_ prefix carries no meaning here (#225).

Conventions / gotchas

  • Route protection is deny-by-default in proxy.ts — a new public path must be listed in its PUBLIC_PATHS.
  • No Tailwind and no CSS framework. src/styles/tokens.css is the token layer, src/styles/base.css the reset; everything else is a .module.css beside its component. src/styles.css imports those two and nothing else. Colors live in tokens.css alone — pnpm check:colors enforces it (#229), and pnpm check:tokens fails on a var(--x) declared nowhere (#407). The second matters because an undeclared property does not error, it is dropped, so it breaks silently.
  • .server.ts must never be imported from client code; .action.ts is a 'use server' module meant to be. Lint enforces the split (#241).
  • FAL generation status is reconciled via on-demand polling in src/lib/server/generations/check-pending.action.ts. There are no webhooks — the route, the flag and the env vars went in #362, and polling is the only path by which a result reaches the app.
  • The bucket is private, so there are no public object URLs to persist. Images are served by the app at /img/[id], which resolves identity from the cookie and filters the row by user_id. src/lib/image-url.ts is the only place a URL is built, and it returns an app path, never a storage key (#226).
  • Image batches create optimistic cards before preparation starts, then reconcile them with saved generation rows. Preparation and submission failures remain visible instead of silently dropping the request.

Status

Focus: nothing in the Focus column. Open #748 (top of Now) -- Director reordering after Extract or a storyboard write fails as "changed in another tab".

Recent highlights:

  • Phone nav and swipe (#768-#770, #764-#767) -- a green menu button bottom left (Images / Video / More, More flying out right), the plus bottom right. The image viewer swipes with a slide; fixed page scroll freezing after closing it, and blank images after a swipe. Hiding video controls on play was tried and reverted (#771): it stopped iOS playing at all.
  • Mobile viewers (#762) -- Images and Video fill the screen, with title above, navigation below, and confirmation before Trash. Tap empty space to close; image prompts and actions live in a details sheet.
  • Code tidy (#758, #757) -- knip's dead code removed, the S3 client guarded server-only; src/lib/server/ grouped into fal/ claude/ storage/ media/ generations/, single-use files moved to their route.
  • Phone layout (#753, #755) -- below 48rem: a nav (now the corner menu, #768); on Images and Video a floating plus opens a one-tap composer (prompt, chips, Generate with cost), 2-up bare walls, a Photos-first picker; the Images viewer has Generate from this and Animate. Desktop unchanged.
  • Director cuts (#744, #746, #747, #749, #751) -- a run session holds several cuts as tabs. New cut from script remakes the open cut in one pass, clips chained through durable frame handoffs; reference clips can try Veo Fast.
  • Director chat answers end on a last beat (#740, #741) -- a tease, an offer, a fork or a provocation that invites the next question, never a question about the person and never a tidy wrap-up. Its own prompt section.

About

Active personal project for AI image generation and video generation using FAL.ai and Anthropic for agentic assist

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages