AI-демо всюди. Один Python-скрипт може замінити обличчя на статичному фото, і результат виглядає магічно. Але створити щось, куди реальні люди зможуть завантажувати файли, спокійно відходити від комп'ютера і повертатися до результату? Це зовсім інша робота. Нещодавно я створив вебінструмент, який бере анімований GIF та референтне обличчя, а потім повертає ту саму анімацію із заміненим обличчям у кожному кадрі. Модель бере на себе основне візуальне навантаження, проте справжні інженерні зусилля пішли на архітектуру навколо неї: підтримку з'єднань, виживання після оновлення сторінки та гарантування того, що двохвилинне завдання інференсу не зникне через помилку 504 Gateway Timeout.

Це і є розрив між прототипом і продуктом. Інженери люблять говорити про дифузійні архітектури та параметри інференсу. Але коли користувач завантажує щільний GIF на двісті кадрів, нікому не буде діла до вашої моделі, якщо вкладка браузера «здасться» вже через тридцять секунд. Інфраструктура навколо інференсу має таке ж велике значення, як і сам інференс.

Проблема очікування

Стандартна веб-архітектура передбачає швидкі відповіді. Користувач натискає кнопку, сервер відповідає, і сторінка оновлюється. Заміна обличчя в десятках кадрів GIF миттєво руйнує це припущення. Модель працює на GPU від Replicate, а не на моєму сервері, і обробка довгого GIF може легко зайняти хвилину або більше. Якщо ви спробуєте тримати HTTP-запит відкритим так довго, ви накликаєте на себе неприємності. Балансувальники навантаження розривають неактивні з'єднання. Браузери вважають, що мережа дала збій, і намагаються повторити запит. Користувачі дивляться на завмерлий спіннер і думають, що додаток зламався.

Я повністю уникнув цього, розділивши процес відправки та отримання результату. Коли користувач завантажує GIF та зображення обличчя, мій бекенд на Next.js запускає передбачення на Replicate і миттєво повертає ID завдання. Користувач отримує негайне підтвердження. Самий результат приходить пізніше через webhook, щойно завершиться робота GPU. Цей патерн не є екзотичним, але для тривалих завдань із медіафайлами він є абсолютно необхідним. Він перетворює непередбачуване очікування на впевнене «рукостискання»: завдання прийнято, і ви отримаєте сповіщення, коли воно буде виконано.

Побудова надійної машини станів

Як тільки ви переходите на асинхронність, вам потрібна видимість процесів. Користувачі будуть оновлювати сторінку. Вони закриватимуть вкладку і відкриватимуть її знову. Вони копіюватимуть посилання і надсилатимуть його колезі, який перевірить його через дві години. Без надійного запису того, що сталося, ви отримаєте хаос.

Я використав Supabase як єдине джерело істини. Кожне завантаження створює рядок із унікальним ID завдання, і цей рядок проходить через певні стани: у черзі (queued), в обробці (processing), успішно (succeeded), помилка (failed) або прострочено (expired). Коли користувач вперше натискає «відправити», рядок стає «у черзі». Щойно Replicate приймає передбачення, статус змінюється на «в обробці». Webhook переводить його в «успішно» або «помилка». Я додав статус «прострочено» для завдань, які занадто довго чекають на callback, щоб система не переслідувала «привидів» нескінченно.

Фронтенд, побудований на TypeScript, виконує опитування (polling) Supabase через короткі проміжки часу та відображає поточний стан. Таке опитування виглядає примітивно, але воно повністю вирішує проблему оновлення сторінки. Користувач може закрити ноутбук, відкрити його завтра і побачити, на якому етапі все перебуває, тому що прогрес належить базі даних, а не пам'яті браузера. Supabase також відстежує кредити, тому облік залишається прив'язаним до того самого запису завдання, який відстежує стан. Усе зберігається в одному місці.

Робота з файлами без перевантаження браузера

GIF-файли важчі, ніж здається. Файл, який ідеально підходить для AI-пайплайну, все одно може важити кілька мегабайтів. Рендеринг такого файлу у вигляді циклічного прев'ю всередині браузера зіпсував би продуктивність, особливо на слабких пристроях. Мені знадобилися два повністю окремі конвеєри файлів: один для AI, інший — для інтерфейсу користувача.

Для прев'ю я конвертую великі GIF в анімовані WebP за допомогою FFmpeg, скомпільованого у WebAssembly. Це працює повністю на стороні клієнта в браузері. Результатом є легке прев'ю, яке робить інтерфейс чуйним, не чіпаючи оригінальний файл. GIF, що надсилається на Replicate, залишається недоторканим. Таке розділення має значення. Ви не захочете, щоб артефакти стиснення з прев'ю потрапили у ваші дані для навчання або у фінальний рендер, і ви не захочете, щоб інтерфейс зависав на багатомегабайтному об'єкті, поки AI ще думає.

FFmpeg WASM також виконує інші завдання з GIF ще до того, як файл покине браузер. Я використовую його для аналізу кількості кадрів, перевірки розмірів і раннього виявлення пошкоджених файлів. Виявлення проблеми до того, як вона споживе кредити GPU, заощаджує і гроші, і терпіння користувачів.

Turning a Demo Into Software People Trust

There is a broader lesson here that applies to nearly every generative AI tool on the market. The model itself might be thirty percent of the work. The other seventy percent is the unglamorous plumbing that nobody tweets about: state recovery, webhook signatures, file conversion, credit tracking, and graceful failure.

My stack is simple and deliberate. Next.js and TypeScript handle the interface and API routes. Replicate runs the models. Supabase manages states, data, and credits. FFmpeg WASM handles client-side media work. Animated WebP keeps the UI fast. Each piece has one job, and they connect through explicit state transitions rather than fragile, long-lived requests.

When a user trades credits for a face swap, they expect reliability, not a research paper. If a prediction fails, the system should know and say so. If the user waits, they should have a lightweight preview to look at and a status that survives a browser restart. These details are invisible when they work, and fatal when they do not.

The face swap itself is a neat trick. But the tool only feels real because a user can upload, leave, and return without losing their place. That is what turns an API call into software people actually trust.