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 deployplus one script for the accounts. - The accounts that script creates:
u1Kim Hana (planner) ·u2Lee Dohyun (artist) ·u3Park Seojun (director) ·u4Choi Yujin (reviewer) ·u5Jung Mina (admin). You choose the password at deploy time · it is never stored in this repository. - Screens:
app-walkthrough/screens/
| 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 |
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 ─ Lambda ─ Bedrock story planning · async job, result polled from DynamoDB
└─ 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 from a Lambda as an async job · see below) ·
CloudFront + S3 · one EC2 g6e.2xlarge running FastAPI + PyTorch + 🤗 diffusers ·
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 (app/story/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.
Planning is an async job, because AppSync stops at 30 seconds. The AppSync quota "request
execution time" is 30 seconds and is not adjustable, so a slow model cannot be awaited inside the
request. The plan mutation invokes the Lambda with invocationType: 'Event' and returns a
jobId immediately; the Lambda calls Bedrock, then writes {status, text, usage, stop} into the
same DynamoDB table under PLAN#<jobId> with a one-hour TTL; the browser polls planResult(jobId)
every 1.5 s for up to 120 s. net.plan() hides all of it, so story.js still just awaits a
{text, usage, stop}. Only the account that started the job can read its result, and the Lambda
writes a result on failure too · otherwise the browser would wait out the full 120 s for nothing.
Several models, one GPU. Pick per shot. Chroma and klein are Apache-2.0; Krea 2 Turbo and SD 3.5 are gated repos with their own licenses, so the Hugging Face key's account has to accept them before the box can download the weights.
| Model | Good for | Reference image | |
|---|---|---|---|
krea |
Krea 2 Turbo | the default: 8 steps, realistic, text only | none · a shot with references is routed to klein |
chroma |
Chroma1-Flash 8.9B | 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, takes several images |
klein9 |
FLUX.2 klein 9B (gated) | the bigger klein: stills and reference shots when its weights are on disk (/health → ref) |
condition · same pipeline, several images |
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 several. 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. krea takes no image at all, so the
board sends any shot that has a reference to klein instead of silently drawing from text.
Assets (assets.html). A fifth tab, after the storyboard. Characters, locations and props live
here in three categories, plus the stills made from them. Three ways in: when a director approves a
cut, the approver's browser asks klein to redraw the approved image three ways · each cast member
alone on a plain background, the location with every person removed, the key prop by itself
(server.py's ISOLATE); hand-drawn or photographed references uploaded from the page; and pictures
made from a prompt per category. Prompted pictures come out as candidates (1, 2 or 4 per prompt,
photoreal) and only the ones you keep become assets · everything kept is an asset.* op on the same
log the board reads. Pick several assets ("a swim cap", "a happy family", "a wide clear sea") and the
page makes photoreal stills in 1, 2 or 4 versions (1920×1088, style: real): the references go
to klein as a list, so the same faces, place and prop come back in one new composition with a film
look instead of pencil. Versions land in the same candidate tray; a viewer opens them at 1:1 and 2×
with drag-pan so you can compare before keeping. Candidates and the queue of pending versions live in
the browser (localStorage), so a planner can walk over to the storyboard and come back to find the
work still there; a request cut off mid-flight is recovered from the GPU server, which keeps each
job's result for two hours (/gen/result/{job}). Uploaded references go to S3 through the connector
Lambda (putImage) and stay until deleted, like everything else you keep. The storyboard offers the kept assets as reference
assets when generating a cut; a cut's cast and its scene's background are preselected, props are
picked by hand.
Only one of them fits in the card's 48 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. krea is what a fresh deploy starts with (MODEL in the stack).
GPU power. The box used to be switched off by a schedule at 20:00 KST; that left demos stranded
in the evening. Now only the morning start (09:00) is scheduled and anyone but a reviewer can press
GPU 끄기 / GPU 켜기 in the storyboard's model menu or on the assets page (gpuPower, via the
connector Lambda and EC2). A running g6e.2xlarge costs about $2 an hour, so switch it off when the
day is done.
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()inapp/story/core.jsbefore they touch state. That is the trust boundary.
Three directories, and that is the whole repository: app/ is the product, app-walkthrough/ is
everything that exists only for the guided example, infra/ is the AWS side. Anyone changing a
feature reads app/ and infra/ and can ignore app-walkthrough/ entirely.
Inside app/, files are grouped by who reads them. platform/ has no product opinion, chrome/ is
what every screen shares, story/ is the state rules and the ontology graph, and screens/ is one
file per screen. The four .html files stay at the top because that is where their URLs are.
app/ browser. no framework, no build. this directory becomes the bucket root
index.html home · the four steps in story order, and the login gate
board.html storyboard screen (markup and all CSS)
story-graph.html story develop + script-to-graph screen (markup and CSS)
key-visual.html key visual: script → one image per scene
screens/ one file per screen. each one imports from the folders below
home.js home: the step list, the project cards, the example button
board.js board, detail, viewer, markup, timeline, print
story-graph.js script → graph → seeds → branches → cuts → script
key-visual.js script → scenes → prompts → one image per scene
platform/ no product opinion. swap any of these and the screens do not notice
net.js transport. AppSync when configured, BroadcastChannel between tabs when not
auth.js Cognito login and token refresh · two HTTP calls, no SDK
login.js login screen
dom.js the only place that writes HTML into the DOM, plus escaping
chrome/ what every screen shares
nav-tabs.js the top tab bar, plus NAV_TABS (the step order)
theme.css the one set of colours and fonts (--sb-*) all four screens read
history.js "what happened" list · who did what, and continue-from-here
projects.js the project picker and the cards on home
coach.js coachmarks · the veil, the bubble, the keyboard handling
empty-panel.js the "처음 오셨나요?" panel every screen shows while empty
story/ the rules. no DOM in here
core.js 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
graph-*.js the ontology graph: schema, layout engine, canvas view, probe queries
art/
art.js canvas-drawn placeholder art for local mode
seed-art.js generated by infra/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 self-check · open it in a browser and it prints PASS/FAIL. not deployed
app-walkthrough/ example only. nothing under app/ is imported from here, and cannot be:
deploy puts app/* at the bucket root and this folder under its own prefix, so
a path from here back into app/ resolves on disk and 404s in production. The
screens inject what the example needs instead (tour.js `wire`). Delete this
folder and the example goes away; the product still runs.
tour.js the one walkthrough across screens · ?demo=1, which screen is next
guide.js the guide engine: the veil with a hole, the bubble, wait/done, the note lane
steps/ what each screen's example actually does, step by step
develop.js synopsis → graph → seeds → one branch
key-visual.js the sample script, its prompts, and browser-drawn stand-in art
board.js replays the seed ops one stage at a time, not all at once
data/ the fixtures the example replays · graph, seeds, stories, a synopsis
data/build.mjs rebuilds extracted-graph.json from the raw model response
screens/ screenshots and the key-visual mockup. not deployed
infra/
bin/app.js CDK entry point
lib/storyboard-stack.js the whole stack, one file
schema.graphql Op, Presence and PlanJob types, the queries/mutations, two subscriptions
resolvers/ AppSync JS resolvers · putOp, listOps, presence, plan (async job) + planResult,
and the four graph resolvers
graph/index.js the one Lambda: Neptune Gremlin queries + the Bedrock plan job
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
scripts/stop.sh stop the Neptune cluster (it bills by the hour while it runs)
scripts/start.sh start it again · same endpoint, same data
node infra/scripts/serve-local.mjs
# http://localhost:8000 · no login, and multiple tabs sync with each other liveUse that script rather than python3 -m http.server: the deployed layout is not the repository
layout. CDK uploads app/* to the bucket root and app-walkthrough/* to a folder of that name, so
serving the repository directly gives 404 for / and /board.html. serve-local.mjs reproduces the
deployed paths, and it returns 403 for directory URLs exactly like CloudFront does · otherwise a
broken link works locally and only breaks in production.
With an empty app/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.
app/art/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.
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.
- Region
ap-northeast-2. Not a preference. The stack is pinned there in four places: the CloudFront origin-facing prefix list ID (pl-22a6434b· different in every region), theg6eAZ list (only2aand2bsell it here), theglobal.anthropic.claude-sonnet-4-6inference profile ininfra/resolvers/plan.js, andinfra/scripts/seed-art.mjs. Elsewhere means editing all four. Two cautions learned the hard way. Model prefix:us.*profiles exist only in US regions; outside them useglobal.*, or the plan resolver returns a Bedrock error. Capacity vs. availability:run-instances --dry-rundoes not verify either. It reports "would have succeeded" for a type the region does not even sell, so useaws ec2 describe-instance-types --region <r> --instance-types g6e.2xlargeto check that a region offers the card at all, and expect capacity itself to be provable only by launching. This wasus-east-1, thenus-west-2;g6e.2xlargeran dry in every AZ of both.ap-southeast-1is not a fallback · it has nog6eat all (T4/T4g/A100 only). - A default VPC in that region. The stack looks one up rather than creating one.
- Bedrock model access for Anthropic Claude, enabled once per account on the Bedrock console's
Model access page. Story planning returns
AccessDeniedExceptionuntil it is. Check withaws bedrock get-foundation-model-availability --region ap-northeast-2 --model-id anthropic.claude-sonnet-4-6· you want"authorizationStatus": "AUTHORIZED". - At least 8 G-instance vCPUs.
g6e.2xlargeneeds 8 under Running On-Demand G and VT instances (L-DB2E81BA); a new account can start at 0. Without it the instance fails to launch and the stack rolls back. 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, GpuInstance
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 fileThe 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.
The GPU is one EC2 instance, and two EventBridge Scheduler rules stop and start it · weekdays
09:00–20:00 KST (GPU_HOURS in infra/lib/storyboard-stack.js, written in UTC). Outside those
hours the instance is stopped and the hourly charge is not running. About $370/month instead of
$1,600.
Say what this is precisely: it is a schedule, not idle detection. The GPU is on at 09:00 whether anyone is using it or not, and it goes down at 20:00 whether they are or not. Nothing wakes it up when a request arrives · outside the window, generation fails until someone starts it.
Editing GPU_HOURS is the whole configuration. The rules call ec2:StartInstances /
ec2:StopInstances directly through Scheduler's universal target, so there is no Lambda to read.
The root volume survives a stop. deleteOnTermination: false, so the ~67 GB of model weights on
/opt/hf are still there in the morning and nothing is re-downloaded · that is the only reason a
stop/start schedule is worth having here. Two consequences:
- The instance is a single
ec2.Instance, not an Auto Scaling group. An ASG terminates on scale-in, so a retained volume would be left attached to nothing, billed monthly, and unusable by whatever launched next. cdk destroydoes not delete the volume. Delete it yourself afterwards or it keeps costing ~$27/month:aws ec2 describe-volumes --filters Name=status,Values=available \ --query "Volumes[?Size==\`200\`].[VolumeId,CreateTime]" --output table aws ec2 delete-volume --volume-id vol-…
On and off by hand, when the schedule isn't what you want:
GPU=$(aws cloudformation describe-stacks --stack-name StoryboardDemo \
--query "Stacks[0].Outputs[?OutputKey=='GpuInstance'].OutputValue" --output text)
aws ec2 stop-instances --instance-ids "$GPU" # off
aws ec2 start-instances --instance-ids "$GPU" # onA manual start does not disable the schedule: the next 20:00 rule still stops it. To keep it up
overnight, disable the GpuOff schedule as well.
Stop the graph database too. Neptune runs on a provisioned instance (db.t4g.medium by default),
so it bills by the hour whether or not anyone queries it. Stopping keeps the data and the endpoint:
start.sh brings back the same cluster, and neither the graph Lambda nor the AppSync resolvers need
redeploying.
bash scripts/stop.sh # stop · takes a few minutes
bash scripts/start.sh # start · the graph Lambda errors until the status is `available`Both default to cluster storyboarddemo-graph in ap-northeast-2; override with SB_NEPTUNE_CLUSTER
and AWS_REGION if you deployed the stack under another name or region.
Neptune restarts a stopped cluster by itself after 7 days. That is an AWS limit, not something the scripts can opt out of. If you are done with the demo for more than a week, either re-run
stop.sheach week ornpx cdk destroy· otherwise the cluster quietly comes back and bills.
A bigger instance takes no code change:
npx cdk deploy --context neptuneInstance=db.r6g.largeTear everything down: npx cdk destroy · then delete the volume above.
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
- 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 tou2. 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.) - 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. - 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. - Review (
u3). Open the drawing, click on it to drop a pin, drag to circle, tag it 조명, and press 수정 요청 with a memo.u2gets a notification and the cut comes back to them. - Fix and approve.
u2regenerates, presses 리뷰 요청,u3presses 승인. The cut locks; nobody can edit approved content until it's reopened. - 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.
| 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 |
Neptune db.t4g.medium |
~$0.09/hour while running · $0 stopped, storage aside (scripts/stop.sh) |
| CloudFront · S3 · Cognito · AppSync · DynamoDB | a few dollars at demo scale |
GPU uptime is essentially the entire bill. Nothing shuts it down for you, and nothing shuts Neptune
down either · and Neptune wakes itself back up 7 days after you stop it.
| EC2 g6e.2xlarge (L40S 48GB) | $2.24/hour · ~$370/month on the default 09–20 weekday schedule · over $1,600 if it never stops |
| EBS 200GB gp3 @ 500 MB/s | ~$27/month, including the stopped hours · it is retained on purpose, and cdk destroy leaves it behind |
| ALB | ~$0.025/hour + traffic, ~$19/month · it does not stop with the GPU |
| CloudFront · S3 · Cognito · AppSync · DynamoDB | a few dollars at demo scale |
GPU uptime is essentially the entire bill, and the schedule is what bounds it. Everything else keeps running while the GPU is stopped: the ALB, the retained volume, and the board itself.
- 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 paid call must not be re-sent behind
your back, so a failure is shown and you press the button again. The Lambda's async retries are
off (
retryAttempts: 0) for the same reason. - A plan job outlives the page. Close the tab mid-plan and the Lambda still finishes and pays for the tokens; nothing reads the result and the TTL sweeps it an hour later.
- 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.
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 GPU is $2.24/hour while it runs. The default schedule holds that to weekday
09:00–20:00 KST, about $370/month, but it is a fixed schedule and not a safety net: nothing notices
that nobody is using the instance, and a manual start stays up until the next 20:00 rule. The ALB and
the retained 200 GB volume are billed around the clock, and the volume outlives cdk destroy. There
is no budget alarm. 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.
- 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.
- Roles cannot be self-assigned · the app client's write list is the display name only, so
custom:rolechanges take an admin API call. The display name is self-writable, though: a name in the roster is not proof of who someone is. - 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 inplan.js.) - The five demo accounts share one password and it never expires. Reissue per account.
- The CloudFront → GPU leg is plain HTTP (user → CloudFront is HTTPS). Attach a domain and terminate TLS, or use a VPC origin.
- AppSync logging, CloudFront access logs, and DynamoDB point-in-time recovery are all off. There is no record to trace an incident with.
- 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.
- Deploying with
SB_DEMO_PWset writes the password into a public file (aws-config.js). Never set it for a public demo. - 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 (Krea 2 Turbo, Chroma1-Flash,
FLUX.2 klein 4B, Chroma1-HD, SD 3.5 Large) 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. Bedrock writes the image prompts in
English, so they reach the GPU untouched. A prompt line someone typed Korean into by hand is
translated on the way in, and that call goes to Bedrock too (en() in infra/gpu/server.py, which
skips any ASCII string): every model call in the project, planning and translation alike, is a
Bedrock call. 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,
and 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.
See CONTRIBUTING for more information.
This library is licensed under the MIT-0 License. See the LICENSE file.