DEV Community

Nazma Akter
Nazma Akter

Posted on

Five AI media types, three pipelines: how we built AI Studio in Go and the browser

VeloxonHub Social has an AI Studio that makes five kinds of media for social posts: images, infographics, carousels, slide decks and short vertical videos. From the outside it's one form with five tabs. Underneath, it's three pipelines, because the five outputs fail, cost and finish in different ways.

Here's how each one works, and the decisions that made them reliable.

Pipeline 1: images and infographics (synchronous)

An image or an infographic is a single HTTP request that returns a finished file.

Model routing comes from a table, not code. Every model a user can pick lives in an ai_models table: its OpenRouter id, a display name and which use it is the default for (image, infographic, post, slides…). The model picker in the UI is just that table. A google/… model runs on our Gemini key; anything else goes through OpenRouter. Changing the default image model is a row update, not a deploy.

Infographics get a stricter prompt than images. Image models invent numbers when given space for a chart. The infographic prompt asks for short, legible text and says, in so many words, do not invent statistics that are not given below. The form says the same thing to the user: "Give the exact facts and numbers to include; the AI won't make any up."

Storage is checked before the model is called. Generated files go into the workspace's media library, which has a quota. We check for room (with some headroom) before calling the model, so nobody pays for an image we then can't save.

Credits are held, then settled or refunded. The flow is:

  1. Hold the credits for the generation.
  2. Create a generation row (prompt, options, model, which key).
  3. Call the model, store the file, add it to the library.
  4. Settle the hold on success. On any failure, refund it and mark the row failed with a message a person can read.

A failed generation never costs the user anything, and the history shows why it failed instead of a spinner that never ends.

Errors are split by whose problem they are. If our own provider account runs out of quota, that's our problem: the user sees "AI is temporarily unavailable", and we get a log line telling us to check billing. If the user's own key runs out, they get the provider's message, because they're the one who can fix it. That brings us to keys.

Bring your own key, everywhere

Every workspace can pay with our AI credits or connect its own key. Images run on a Gemini or OpenRouter key, and videos on a Gemini key. The generation row records which key it used, which matters most for the next pipeline.

Pipeline 2: video (asynchronous)

A video takes minutes to render, so the API can't wait for it.

  • Generate starts the render with the video model and stores the provider's operation id on the generation row, then returns at once. The UI shows "rendering".
  • A worker polls the operations that are due, a few at a time. When one finishes, it downloads the file, stores it, adds it to the library and settles the credits.
  • A render that's still running after 30 minutes is marked failed and its credits are refunded automatically.
  • If the user removes the API key a video was started with, the poll fails cleanly with "the API key this video was made with was removed" rather than retrying forever.

The rule that kept this simple: the generation row is the whole state machine. The request handler and the worker never talk to each other; they both read and write that row.

Pipeline 3: carousels and decks (AI writes, the browser draws)

A carousel or a deck is mostly text, and image models are at their weakest with text: words get misspelled, slides in one set don't match, and fixing one word means generating the whole image again.

So carousels and decks are split in two:

The server asks a text model for JSON. A carousel is {title, caption, slides: [{heading, body}]}; a deck is {title, subtitle, slides: [{heading, bullets, notes}]}. The system prompt sets the shape and the limits: headings of at most 8 words, a hook on the first carousel slide and a call to action on the last, speaker notes for decks, and no invented facts. The parser is deliberately forgiving: it takes the outermost {…} even if the model wrapped it in prose, trims every field to what the templates can show, drops empty slides and cuts the list to the slide count the user asked for.

The browser draws the slides on a canvas. Carousel slides are 1080×1350 (Instagram's 4:5) and deck slides are 1920×1080. A small text-fitting routine wraps each block and shrinks the font in steps until it fits its box, so a long heading never overflows. Because the browser draws the text with system fonts, every script those fonts cover works, and a typo fix is just an edit followed by a redraw. There are five colour themes to switch between, plus an optional footer handle.

Exports happen client-side too. Carousel slides are exported as PNGs. Decks are packed into a PDF by about 40 lines of TypeScript that write the PDF objects by hand: one full-page JPEG per page, with no PDF library. The browser then uploads the files to the library, linked to the generation.

The result: the AI does the writing it's good at, and the layout is deterministic and editable.

What we'd tell you if you're building something similar

  • Keep a generation table from day one. History, refunds, async polling and failure messages all hang off it.
  • Hold credits, don't charge them. Settle on success and refund on every failure path, including the boring ones like "the file couldn't be saved".
  • Don't let image models draw text you care about. Ask a text model for structured content and render it yourself.
  • Put model choice in data. New models ship every month, and a table row is easier to change than a release.

Try it

AI Studio is part of VeloxonHub Social. It's included on the Elite plan and is an add-on on Basic and Pro, and every plan starts with a 7-day free trial.

I'm happy to go deeper on the video polling or the canvas text fitting in the comments.

Top comments (0)