Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Storyboard Board

A collaborative storyboard: planners, artists, and a director open the same board at the same time, break a script into cuts, generate the art, mark up the drawings with feedback, and sign off.

Runs on AWS. The drawings are generated by an open-weight model on our own GPU, not by a managed AWS model.

  • Try it: Run locally needs no AWS account. Deploy is one cdk deploy plus one script for the accounts.
  • The accounts that script creates: u1 Kim Hana (planner) · u2 Lee Dohyun (artist) · u3 Park Seojun (director) · u4 Choi Yujin (reviewer) · u5 Jung Mina (admin). You choose the password at deploy time — it is never stored in this repository.
  • Screens: walkthrough/

What it does

Area Capability
Concurrent editing Many people edit one board. Merging is per field, so nobody overwrites your sentence. You see other people's cursors, avatars, and "editing now" markers
Planning Write the story from one prompt (Amazon Bedrock, Claude Sonnet): it invents the characters, the synopsis and the cut-by-cut flow. Options: new story / next episode / spin-off, genre, tone, runtime, cut count, how many characters to invent, whether to reuse the board's cast. You read the outline first, then it lands on the board. Or paste a script you already have and split it into cuts mechanically
Episodes One board holds the main story plus its next episodes and spin-offs. Cuts belong to an episode; characters are shared across all of them
Characters One character, many poses (front, 3/4, side, back, full body, expression). Pick one as the reference and the other poses follow that face
Art Generate from a prompt, upload a hand sketch, or transform an existing drawing. Pick one of three open-weight models per shot. Versions stack up, can be compared side by side, and can be deleted
Roles Five: planner, artist, director, reviewer, admin. Making art — generate, sketch upload, model swap — is for artists and planners. Story planning is for planners and directors. Approval is the director's, and so is the 관리 tab (assignment table, team load). Changing someone's role is the admin's. Three of those are enforced on the server (generation, planning, role changes); everything else is UI only (see the disclaimer)
Review Pin notes and circle marks on the drawing, tags (framing, light, background, wardrobe, expression, proportion), @mentions, replies, resolve
Approval Draft → in progress → in review → approved / changes requested. Buttons are locked for roles that don't own the action
Notifications A director's change request notifies the assignee and hands the cut back to them
Output Timeline (duration per scene), print / PDF

How it works

Browser ──┬─ CloudFront ─ S3               static files, no build step
          ├─ Cognito                       login; name and role come from token claims
          ├─ AppSync ─ DynamoDB            op log + realtime subscription (WebSocket)
          ├─ AppSync ─ Bedrock Converse    story planning — the one call that skips the op log
          └─ CloudFront /gen ─ ALB ─ EC2 g6e (L40S) ─ open-weight model ─ S3

Tech: plain ES modules in the browser (no framework, no bundler) · Amazon Cognito user pool (HTTP calls, no SDK) · AWS AppSync GraphQL with JS resolvers · DynamoDB as an append-only log · Amazon Bedrock (Claude Sonnet, called straight from an AppSync HTTP data source — no Lambda) · CloudFront + S3 · one EC2 g6e.2xlarge running FastAPI + PyTorch + 🤗 diffusers · Amazon Translate for Korean prompts · AWS CDK (JavaScript) for all of it.

Story planning is two calls, not one. The outline (title, logline, synopsis, characters, beats) comes back first and a person reads it; only then are the beats expanded into cuts, four beats per call, in parallel. One call for a 24-cut board would sit near the resolver's time limit and lose everything if it broke. The prompts live in the browser (demo/story.js) so changing the wording isn't a resolver redeploy, and the model's JSON is clipped and whitelisted by normalizePlan() before any of it becomes an op — the model is a second trust boundary, not a trusted author.

Three models, one GPU. Pick per shot. All three are Apache-2.0, so an organization can deploy them.

Model Good for Reference image
chroma Chroma1-Flash 8.9B the default: 12 steps, ~15 s, the best pencil-storyboard texture img2img — keeps the layout, redraws the face
klein FLUX.2 klein 4B same character in a new shot; 8 steps, ~4 s condition — keeps the face
hd Chroma1-HD 8.9B Flash before distillation: 26 steps, for a final pass img2img — keeps the layout, redraws the face

The reference-image column is the reason there are three. chroma/hd paint over the image with noise, so the layout survives and the face does not. klein takes it as a condition, so the face survives — that's what makes "one character, many shots" work.

Only one of them fits in the card's 44 GB at a time, so picking a model swaps it: the server loads the new weights in the background and refuses generation until they're resident (~1 min from disk). The picker shows the wait. chroma is what a fresh deploy starts with.

The only truth on the board is the op log. One edit is one immutable op; on boot the client replays the log to build the screen. There is no separate stored state, which is why two people's edits can always be merged.

  • Ordering uses fractional indexing, so simultaneous reorders don't corrupt the sequence.
  • Same field: last write wins, per field. Different fields: both survive.
  • Ops from other people must pass scrub() in demo/core.js before they touch state. That is the trust boundary.

Code structure

demo/                browser. no framework, no build. upload this directory to S3 and you're done
  index.html           markup and all CSS
  app.js               every screen and interaction: board, detail, viewer, markup, timeline, print
  core.js              state rules — status transitions, role permissions, field merge, op validation,
                       plan normalization. pure functions, testable without a browser
  story.js             story planning: the option set, the prompts, and the two-phase Bedrock call.
                       falls back to splitting the prompt when there is no model (local mode)
  net.js               transport. AppSync when configured, BroadcastChannel between tabs when not.
                       app.js cannot tell which one it got
  auth.js             Cognito login and token refresh — two HTTP calls, no SDK
  login.js             login screen
  art.js               canvas-drawn placeholder art for local mode
  dom.js               the only place that writes HTML into the DOM, plus escaping
  seed-art.js          generated by scripts/seed-art.mjs; the starter board's images
  aws-config.js        `null` in the repo (that's what selects local mode); CDK overwrites it at deploy
  test.html            core.js self-check — open it in a browser and it prints PASS/FAIL
infra/
  bin/app.js           CDK entry point
  lib/storyboard-stack.js   the whole stack, one file
  schema.graphql       Op and Presence types, four queries/mutations, two subscriptions
  resolvers/           AppSync JS resolvers — putOp, listOps, presence, plan (Bedrock Converse)
  gpu/server.py        FastAPI generation server: the three-model registry, one-resident-at-a-time
                       VRAM swapping, prompt assembly per model family, upload to S3
  gpu/user-data.sh     GPU boot script — pinned Python deps, systemd unit, model pre-download
  scripts/users.sh     create the five demo accounts
  scripts/seed-art.mjs generate the starter board's images with the real model

Run locally

python3 -m http.server 8000 --directory demo
# http://localhost:8000 — no login, and multiple tabs sync with each other live

With an empty demo/aws-config.js the app runs in local mode (BroadcastChannel + localStorage) and draws placeholder art instead of calling a GPU. Good enough to see concurrent editing work.

demo/seed-art.js ships empty, so the starter board draws canvas placeholders — the same locally and on a fresh deployment. scripts/seed-art.mjs fills it with real generated art once a GPU is up, but those /img/<hash>.png paths only resolve in the account that generated them. That is why the file is committed empty: any account gets the same board on the first boot.

Deploy

Two commands: cdk deploy builds everything, scripts/users.sh creates the accounts. Four things have to be true before the first one — none of them are things CDK can do for you.

  1. Region us-east-1. Not a preference. The stack is pinned there in three places: the CloudFront origin-facing prefix list ID (pl-3b927c52 — different in every region), the g6e AZ list, and the us.anthropic.claude-sonnet-5 inference profile (US regions only). Elsewhere means editing infra/lib/storyboard-stack.js.
  2. A default VPC in that region. The stack looks one up rather than creating one.
  3. Bedrock model access for Anthropic Claude, enabled once per account on the Bedrock console's Model access page. Story planning returns AccessDeniedException until it is. Check with aws bedrock get-foundation-model-availability --region us-east-1 --model-id anthropic.claude-sonnet-5 — you want "authorizationStatus": "AUTHORIZED".
  4. At least 8 G-instance vCPUs. g6e.2xlarge needs 8 under Running On-Demand G and VT instances (L-DB2E81BA); a new account can start at 0. The stack still deploys — the ASG just records a failed scaling activity and no GPU ever appears. Request the increase first.

Then npx cdk bootstrap once per account and region. cdk.context.json is not committed, so the VPC lookup runs again on your first synth.

cd infra && npm ci
npx cdk deploy --outputs-file /tmp/sb-out.json    # prints Url, UserPoolId, ClientId, GpuAsg

POOL=$(node -p "require('/tmp/sb-out.json').StoryboardDemo.UserPoolId")
SB_PW='<pick a password>' bash scripts/users.sh "$POOL"  # the five demo accounts
SB_PW='<the same password>' node scripts/seed-art.mjs    # optional: real art for the starter board
npx cdk deploy                                           # only if you ran seed-art: it writes a local file

The password only ever arrives through SB_PW, never as an argument and never in a file — arguments show up in shell history and ps. Leave SB_DEMO_PW unset: it prefills the login form for a private demo, and setting it writes the password into aws-config.js, which anyone can read over CloudFront.

The GPU is only warm after it has pulled the model (tens of GB). When the generation-server chip in the header changes from 모델 올리는 중 to the model name, it's ready. First boot takes a few minutes.

Shut the GPU down when you're not using it. That is where the hourly bill comes from.

ASG=$(aws cloudformation describe-stacks --stack-name StoryboardDemo \
  --query "Stacks[0].Outputs[?OutputKey=='GpuAsg'].OutputValue" --output text)
aws autoscaling set-desired-capacity --auto-scaling-group-name "$ASG" --desired-capacity 0   # off
aws autoscaling set-desired-capacity --auto-scaling-group-name "$ASG" --desired-capacity 1   # on

Tear everything down: npx cdk destroy

Using it

Open the site in two windows and log in as different people — a new tab starts a fresh session, so u2 in one window and u3 in the other. Both are looking at the same board.

Example: prompt → story → art → review → approval

  1. Plan (u1). Press + 이야기 기획 in the sidebar. Type what the film is about, pick genre, tone, runtime, cut count and how many characters to invent, then press 개요 만들기 (~10 s). Read the outline — title, logline, synopsis, the characters it invented, the beat list — and press 이대로 컷 만들기. Characters, their pose sheets and the cuts all land on the board at once. Assign a cut to u2. With a story already on the board, the same dialog offers 다음 회차 and 스핀오프: those land as a new episode in the sidebar and leave the existing cuts alone. (Already have a script? 시나리오 → 컷으로 분해 still splits it mechanically, no model involved.)
  2. Character (u2). Open 인물, add a character, write a short physical description, generate the pose sheet. Mark one pose as the reference — later cuts with that character start from it.
  3. Art (u2). Open the cut, edit 작업 지시, press 생성. ~15 s. Or upload a hand sketch and press 생성 with 구도 유지 to ink it. Every attempt becomes a version; the version strip compares them.
  4. Review (u3). Open the drawing, click on it to drop a pin, drag to circle, tag it 조명, and press 수정 요청 with a memo. u2 gets a notification and the cut comes back to them.
  5. Fix and approve. u2 regenerates, presses 리뷰 요청, u3 presses 승인. The cut locks — nobody can edit approved content until it's reopened.
  6. Ship. Open 타임라인 for per-scene durations, or 인쇄 for a PDF.

While you do this, watch the other window: everything lands there within a second, without a refresh.

Cost

Item Roughly
EC2 g6e.2xlarge (L40S 48GB) $2.24/hour · ~$54 for a full day (us-east-1 on-demand)
EBS 200GB gp3 @ 500 MB/s ~$27/month (the extra throughput is what makes a model swap ~1 min instead of ~4)
ALB ~$0.025/hour + traffic
CloudFront · S3 · Cognito · AppSync · DynamoDB a few dollars at demo scale

GPU uptime is essentially the entire bill. Nothing shuts it down for you.

Known limits

  • Pins and circle marks don't appear in print/PDF — they're screen-only.
  • The op log has a 30-day TTL. The log is the board, so edits older than 30 days disappear. If you want to keep a board, remove the TTL first.
  • Deleting an image version hides it from the board — the log records a tombstone, so v-numbers never shift and older notes keep pointing at the right drawing. The S3 object stays: the images bucket has no lifecycle rule and /img/* is cached for a year.
  • Generation is one image at a time (one GPU, one server). Simultaneous requests queue.
  • One model is resident at a time. Two people who want different models take turns, and each switch costs a load. If that becomes the bottleneck, the fix is a queue plus one worker per model.
  • Edits made while your browser was closed are backfilled from the log on reopen. But your own ops that failed to send are retried four times and then dropped silently.
  • Story planning is the one call with no retry and no outbox. A 10-second paid call must not be re-sent behind your back, so a failure is shown and you press the button again.
  • A landed plan has no undo. Once you press 이대로 컷 만들기, the cuts and characters are ops on everyone's board — delete them one by one. Reading the outline first is the safety net.
  • Nothing rate-limits planning. Any planner or director can press the button as often as they like.
  • Local mode has no model. The plan dialog still works, but it splits your prompt into a skeleton and says so — the real thing needs the deployed board.
  • The first person to open an empty board seeds the starter board from the browser. A real deployment should move that seeding to deploy time.

Disclaimer

This is a demo built to show a concept. It is not a product and is not in a state to put into production. Read all of the below before you deploy it yourself. The author takes no responsibility for damages, costs, or leaks resulting from using it as-is. This is not an official AWS product or sample and has no affiliation with AWS.

Cost — the default configuration keeps the GPU instance running. About $2.24/hour, over $1,600 if you leave it up for a month. There is no budget alarm and no auto-shutdown. Deploying starts the meter.

Security — fix these before deploying to an organization

The demo prioritized showing the idea, and the price was leaving the following open. It assumes a demo environment that only trusted people can reach.

  1. The server does not verify the author of an op. Any logged-in user can record an edit or approval under someone else's name.
  2. Roles cannot be self-assigned — the app client's write list is the display name only, so custom:role changes take an admin API call. The display name is self-writable, though: a name in the roster is not proof of who someone is.
  3. Role checks for approve, delete (cuts and image versions), and board reset exist only in the UI. The server does not enforce them. (Three exceptions check the token claim server-side: generation on the GPU server, roster role changes in putOp.js, and story planning in plan.js.)
  4. The five demo accounts share one password and it never expires. Reissue per account.
  5. The CloudFront → GPU leg is plain HTTP (user → CloudFront is HTTPS). Attach a domain and terminate TLS, or use a VPC origin.
  6. AppSync logging, CloudFront access logs, and DynamoDB point-in-time recovery are all off. There is no record to trace an incident with.
  7. The generation server runs as root and there is no upper bound on the step count in a request. A logged-in user can hold the GPU for a long time.
  8. Deploying with SB_DEMO_PW set writes the password into a public file (aws-config.js). Never set it for a public demo.
  9. Story planning has no rate limit and no spend cap. The resolver checks the role and clips the token count per call, but a planner or director can call Bedrock in a loop. Add a per-user quota, or an AWS Budgets alarm, before letting a wider group in.

Image generation — art is produced by open-weight models (Chroma1-Flash, FLUX.2 klein 4B, Chroma1-HD) running on our own EC2 instance. You are responsible for checking the license of each model and its weights and what you may do with the output. Korean prompts are translated to English with Amazon Translate — prompts leave for AWS Translate. Output may not match the prompt, and the same seed produces different images when the model or library versions change.

Data — board contents, uploaded sketches, and generated images are stored in this account's S3 and DynamoDB. Encryption is S3 default (AES256) only, with no separate KMS key. Do not put real production material or personal data in it. cdk destroy deletes the buckets and the table with it — that is irreversible.

Characters — the starter board's script, characters, and names (Yeorum Studio, Kim Hana, Lee Dohyun, Park Seojun, Choi Yujin) are fictional and exist only for the demo.


Security

See CONTRIBUTING for more information.

License

This library is licensed under the MIT-0 License. See the LICENSE file.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages