Привет, Хабр! Меня зовут Андрей, и я фулл-стек-разработчик. Недавно я выпустил свой 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, он реально упрощает жизнь.
Спасибо за внимание!
