AI demos are everywhere. A single Python script can swap a face in a static photo, and the result looks magical. But building something that real people can actually upload to, walk away from, and come back to? That is an entirely different job. I recently built a web tool that takes an animated GIF and a reference face, then returns the same animation with the face swapped across every frame. The model does the visual heavy lifting, yet the real engineering effort went into the architecture surrounding it: keeping connections alive, surviving page refreshes, and making sure a two-minute inference job did not vanish into a 504 Gateway Timeout.
This is the gap between a prototype and a product. Engineers love to talk about diffusion architectures and inference parameters. But when a user uploads a dense, two-hundred-frame GIF, nobody cares about your model if the browser tab gives up after thirty seconds. The infrastructure around the inference matters just as much as the inference itself.
The Problem with Waiting
Standard web architecture assumes fast responses. A user clicks a button, the server answers, and the page updates. Face swapping across dozens of GIF frames breaks that assumption immediately. The model runs on Replicate's GPUs, not on my server, and a long GIF can easily take a minute or more to process. If you try to hold an HTTP request open that long, you are asking for trouble. Load balancers cut idle connections. Browsers assume the network failed and retry. Users stare at a frozen spinner and assume the app is broken.
I avoided this entirely by decoupling the submission from the result. When a user uploads a GIF and a face image, my Next.js backend starts a prediction on Replicate and instantly returns a job ID. The user gets immediate confirmation. The actual result arrives later through a webhook once the GPU work finishes. This pattern is not exotic, but for long-running media jobs it is absolutely essential. It turns an unpredictable wait into a confident handshake: the job is accepted, and you will be notified when it is done.
Building a State Machine You Can Trust
Once you go asynchronous, you need visibility. Users will refresh the page. They will close the tab and reopen it. They will copy the link and send it to a coworker who checks it two hours later. Without a durable record of what happened, you have chaos.
I used Supabase as the single source of truth. Every upload creates a row with a unique job ID, and that row moves through specific states: queued, processing, succeeded, failed, or expired. When the user first hits submit, the row is queued. The moment Replicate accepts the prediction, it shifts to processing. The webhook pushes it to succeeded or failed. I added expired for jobs that sit too long without a callback, so the system does not chase ghosts indefinitely.
The frontend, built in TypeScript, polls Supabase on a short interval and renders whatever the current state says. This polling looks primitive, but it solves the refresh problem completely. A user can close their laptop, open it tomorrow, and see exactly where things stand because the database—not the browser memory—owns the progress. Supabase also tracks credits, so the accounting stays tied to the same job record that tracks the state. Everything lives in one place.
Handling Files Without Crushing the Browser
GIFs are heavier than people think. A file that is perfectly sized for the AI pipeline might still weigh several megabytes. Rendering that as a looping preview inside the browser would wreck performance, especially on lower-end devices. I needed two entirely separate file pipelines: one for the AI, and one for the user interface.
For previews, I convert large GIFs to animated WebP using FFmpeg compiled to WebAssembly. This runs entirely client-side in the browser. The result is a lightweight preview that keeps the interface snappy without touching the original file. The GIF sent to Replicate stays untouched. That separation matters. You do not want compression artifacts from a preview sneaking into your training data or your final render, and you do not want the user interface stalling on a multi-megabyte blob while the AI is still thinking.
FFmpeg WASM also handles other GIF tasks before the upload ever leaves the browser. I use it to parse frame counts, validate dimensions, and catch corrupted files early. Catching a problem before it consumes GPU credits saves both money and user patience.
Transformando uma Demonstração em um Software em que as Pessoas Confiam
Há uma lição mais ampla aqui que se aplica a quase todas as ferramentas de IA generativa no mercado. O modelo em si pode representar trinta por cento do trabalho. Os outros setenta por cento são a infraestrutura de bastidores sem glamour sobre a qual ninguém posta no Twitter: recuperação de estado, assinaturas de webhook, conversão de arquivos, rastreamento de créditos e falhas controladas.
Minha stack é simples e deliberada. Next.js e TypeScript cuidam da interface e das rotas de API. O Replicate executa os modelos. O Supabase gerencia estados, dados e créditos. O FFmpeg WASM cuida do processamento de mídia no lado do cliente. O WebP animado mantém a UI rápida. Cada peça tem uma única função, e elas se conectam por meio de transições de estado explícitas, em vez de requisições frágeis e de longa duração.
Quando um usuário troca créditos por um face swap, ele espera confiabilidade, não um artigo acadêmico. Se uma previsão falhar, o sistema deve saber e informar isso. Se o usuário tiver que esperar, ele deve ter uma prévia leve para visualizar e um status que sobreviva ao reinício do navegador. Esses detalhes são invisíveis quando funcionam, e fatais quando não funcionam.
O face swap em si é um truque interessante. Mas a ferramenta só parece real porque o usuário pode fazer o upload, sair e voltar sem perder o progresso. É isso que transforma uma chamada de API em um software em que as pessoas realmente confiam.
