Как я сделал Roomify — AI-визуализатор интерьеров на React и Puter
Привет, Хабр! Меня зовут Андрей, и я фулл-стек-разработчик. Недавно я выпустил свой pet-проект Roomify — веб-приложение, которое превращает обычный план помещения в фотореалистичный 3D-рендер за несколько секунд. В этой статье я хочу рассказать, как всё устроено под капотом: от выбора технологий до интеграции с AI и облачной платформой Puter.
Зачем вообще это нужно?
Представьте: вы архитектор или дизайнер интерьеров. Клиент приносит чертёж и просит «показать, как это будет выглядеть в реальности». Обычно вы запускаете 3ds Max, Blender или SketchUp, часами выставляете свет, текстуры, мебель… А что, если нейросеть сделает это за пару секунд? Roomify — попытка ответить на этот вопрос.
Кроме того, я хотел:
-
Изучить интеграцию с AI-моделями (Claude, Gemini) без разворачивания своего бэкенда.
-
Попробовать платформу Puter как альтернативу классическому серверу.
-
Сделать удобный и красивый интерфейс на React.
Получилось то, чем я горжусь, и теперь я делюсь опытом.

Выбор стека
Сначала определился с инструментами:
-
React 19 + TypeScript — современный фронтенд со статической типизацией.
-
React Router 7 — маршрутизация для страниц проекта и визуализатора.
-
Vite — быстрая сборка и HMR.
-
Tailwind CSS — утилитарные классы для стилизации, чтобы не писать тонны CSS.
-
Lucide React — красивые иконки.
-
Puter.js — SDK для взаимодействия с платформой Puter.
Почему Puter? Это «интернет-ОС», которая предоставляет serverless workers, KV-хранилище, постоянное файловое хранилище и доступ к AI-моделям прямо из браузера. Мне не пришлось поднимать собственный бэкенд, что сильно упростило разработку.
Как работает Puter в проекте
Вся логика хранения проектов и вызова AI построена на Puter. Я использую несколько ключевых возможностей:
-
Файловое хранилище — загруженные планы и сгенерированные рендеры сохраняются как файлы. Puter сам генерирует публичные URL, которые потом можно использовать в
<img>. -
KV-хранилище — для метаданных проекта: имя, время создания, владелец, приватность. Это быстрый key-value доступ, идеально для таких данных.
-
Serverless Workers — я создал несколько worker-функций, которые вызываются через
puter.action. Они обрабатывают загрузку, взаимодействие с AI и возвращают результат.
В коде это выглядит примерно так:
// lib/puter.action.ts
export async function createProject({ item, visibility }) {
const result = await puter.action('project.create', {
item,
visibility,
});
return result;
}
export async function getProjects() {
return await puter.action('project.list');
}
export async function getProjectById({ id }) {
return await puter.action('project.get', { id });
}
Все worker-функции написаны на JavaScript и загружены в Puter через их CLI. В результате я получаю полноценный API, не занимаясь DevOps.
AI-генерация: превращаем 2D-план в 3D-рендер
Сердце приложения — функция generate3DView. Она отправляет изображение плана на AI-модель (Claude или Gemini) и получает обратно сгенерированное изображение интерьера.
Вот упрощённая версия:
Все worker-функции написаны на JavaScript и загружены в Puter через их CLI. В результате я получаю полноценный API, не занимаясь DevOps.
AI-генерация: превращаем 2D-план в 3D-рендер
Сердце приложения — функция generate3DView. Она отправляет изображение плана на AI-модель (Claude или Gemini) и получает обратно сгенерированное изображение интерьера.
Вот упрощённая версия:
На стороне worker я формирую промпт, который описывает, что нужно сделать:
«Преобразуй данный план помещения в фотореалистичное 3D-изображение интерьера. Учти расположение стен, окон, дверей. Добавь текстуры, мебель, освещение в современном стиле.»
Модель генерирует изображение, и worker сохраняет его в файловое хранилище, возвращая мне base64 и публичную ссылку.

Проблемы, с которыми столкнулся:
-
Ограничение размера входного изображения. Puter и модели не любят слишком большие файлы. Пришлось добавить валидацию на фронте: до 10 МБ, JPG/PNG.
-
Время генерации. Иногда модель думает 10-15 секунд. На фронте я добавил лоадер с анимацией, чтобы пользователь не дёргался.
-
Качество результата. Не всегда получается идеально, особенно если на плане много деталей. Планирую дорабатывать промпты и добавлять выбор стиля.
Фронтенд: компоненты и логика
Приложение состоит из двух основных страниц: главная (Home) и визуализатор (VisualizerId).
Главная страница
Здесь пользователь видит ленту своих проектов и может загрузить новый план. Компонент Upload отвечает за чтение файла и преобразование в base64.
// components/Upload.tsx
const handleFileChange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const base64 = await toBase64(file);
onComplete(base64);
};
После успешной загрузки вызывается handleUploadComplete из Home. Он создаёт новый проект через createProject, сохраняет его в Puter и перенаправляет на страницу визуализатора с передачей начального изображения.
Особенность: я добавил isCreatingProjectRef.current, чтобы предотвратить повторный вызов, если пользователь быстро кликнет несколько раз.
Страница визуализатора
Здесь происходит магия. Компонент получает id из URL, загружает проект через getProjectById и, если у него ещё нет рендера, автоматически запускает генерацию.
useEffect(() => {
if (isProjectLoading || hasInitialGenerated.current || !project?.sourceImage) return;
if (project.renderedImage) {
setCurrentImage(project.renderedImage);
hasInitialGenerated.current = true;
return;
}
hasInitialGenerated.current = true;
void runGeneration(project);
}, [project, isProjectLoading]);
Важно: я использую useRef для флага hasInitialGenerated, чтобы генерация запускалась ровно один раз, даже если эффект сработает повторно.
Для сравнения «до и после» используется библиотека react-compare-slider. Она позволяет перетаскивать ползунок и видеть разницу между исходным планом и рендером.
<ReactCompareSlider
itemOne={<ReactCompareSliderImage src={sourceImage} />}
itemTwo={<ReactCompareSliderImage src={renderedImage} />}
/>
Визуализатор также содержит кнопки экспорта (скачать рендер) и «Поделиться» (пока не реализована, но будет).
Управление состоянием
Состояние проектов на главной странице хранится в локальном стейте, обновляется после загрузки и создания. Никакого Redux не понадобилось — React + хуки справляются отлично.
Проблемы, с которыми пришлось столкнуться
-
CORS при работе с Puter
Puter SDK требует правильной настройки origin. В dev-режиме я использовал прокси Vite, чтобы избежать проблем. -
Обработка base64
При сохранении проекта нужно передавать base64 изображения, но Puter имеет ограничение на размер передаваемых данных. Я решил это тем, что сначала сохраняю файл отдельно, а в KV кладу только ссылку. В worker это сделано прозрачно. -
Двойная генерация
Из-за эффектов React и строгого режима в dev-сборке генерация могла запускаться дважды. Исправил с помощьюuseRefи проверки на наличие уже сгенерированного изображения. -
Асинхронная загрузка
При быстром переходе на страницу проекта данные могли ещё не подтянуться. Добавил состояниеisProjectLoadingи показываю скелетон (не в текущем коде, но планирую). -
UI на мобильных
Tailwind помог сделать адаптив, но с react-compare-slider на телефонах пришлось повозиться. В итоге уменьшил высоту слайдера и добавил touch-события.
Результат
Roomify полностью работает. Вы можете попробовать демо по ссылке: https://home-room-weld.vercel.app (если ещё не убили мои бесплатные квоты). Исходный код открыт на GitHub. Лицензия позволяет форкать, использовать и дорабатывать.

Что дальше?
-
Выбор стиля интерьера. Пользователь сможет указать «минимализм», «лофт», «скандинавский» и т.д.
-
Редактирование после генерации. Хочется дать возможность изменять материалы, добавлять/убирать объекты.
-
Поддержка векторных форматов (SVG, DXF) для более точного распознавания.
-
Мобильное приложение на React Native.
-
Интеграция с BIM-системами (например, экспорт в IFC).
Заключение
Roomify показал, что современные технологии позволяют решать сложные творческие задачи с минимальными усилиями. Благодаря React и Puter я создал полноценный сервис всего за пару недель. Главное — не бояться экспериментировать и делиться результатами.
Буду рад, если вы заглянете в репозиторий, поставите звезду или откроете issue с предложениями. А если сами захотите сделать что-то подобное — смело используйте Puter, он реально упрощает жизнь.
Спасибо за внимание!
Автор: Kurganov1993

