П/ВИН

Ребрендинг и доработка WebGPT на базе LobeChat

·7 мин чтения
Ребрендинг и доработка WebGPT на базе LobeChat

Когда берёшь open-source продукт как основу для собственного SaaS, рано или поздно наступает момент, когда нужно перестать быть «LobeChat с другим логотипом» и стать полноценным своим продуктом. Именно через это мы прошли с LobeChat — мощным AI-чат-клиентом, который лёг в основу нашего сервиса WebGPT на gptweb.ru. В этой статье расскажу, что именно делали: меняли маскота, чинили локализацию, проводили масштабный ребрендинг и дебажили Docker-сборку при добавлении Admin-панели.

Контекст: зачем форкать LobeChat

LobeChat — это open-source AI-чат с поддержкой множества провайдеров (OpenAI, Anthropic, Gemini и т.д.), плагинами, агентами и красивым UI. Мы взяли его как основу для WebGPT — агрегатора AI-моделей с русскоязычной аудиторией, своей тарифной системой и кастомным брендингом.

Рабочая директория проекта — /home/deploy/projects/ai-aggregator-lobechat. Весь кастомный слой живёт поверх upstream-кода LobeChat, что накладывает свои ограничения: нельзя трогать внутренние имена типов и npm-пакеты (@lobechat/*, @lobehub/*), иначе сломается вся внутренняя экосистема монорепо.

Проблема 1: маскот LobeChat везде

LobeChat поставляется с фирменным маскотом — оранжевым роботом с фиолетовым облаком. Он присутствует в четырёх вариациях:

  • public/avatars/lobe-ai.png — основной маскот (оранжевый с облаком)
  • public/avatars/agent-default.png — серый робот для дефолтных агентов
  • public/avatars/agent-builder.png — маскот в строительной каске
  • public/avatars/doc-copilot.png — маскот в академической шапке

Пользователь видит этого персонажа в inbox-чате, на welcome-экране, в навигации, в карточках агентов — буквально везде. Для продукта с другим брендингом это критично.

Как устроено

В LobeChat есть константа DEFAULT_INBOX_AVATAR, которая в большинстве мест резолвится через BRANDING_LOGO_URL. Мы уже настроили BRANDING_LOGO_URL = '/logo.png' (наш синий WebGPT-гексагон), и большинство мест автоматически подтянули новый логотип. Но нашлось одно захардкоженное место:

// packages/builtin-agents/src/agents/inbox/index.ts — ДО
avatar: '/avatars/lobe-ai.png',
 
// ПОСЛЕ
import { BRANDING_LOGO_URL } from '@lobechat/business-const';
// ...
avatar: BRANDING_LOGO_URL,

Помимо кода, заменили все четыре PNG-файла аватаров на WebGPT-логотип. Хеши файлов проверили командой md5sum — все совпали с эталоном. Заодно обновили DEFAULT_INBOX_AVATAR в packages/const/src/meta.ts.

Результат: маскот LobeChat полностью исчез из интерфейса. Везде — логотип WebGPT.

Проблема 2: интерфейс упорно показывал английский

Казалось бы, тривиальная задача — сделать русский язык по умолчанию. В коде уже было:

// src/const/locale.ts
export const DEFAULT_LANG = 'ru-RU';

Сервер рендерил страницу на русском. Но как только загружался клиентский JavaScript — интерфейс переключался на английский. Независимо от браузера и его настроек.

Где был баг

Проблема оказалась в цепочке резолвинга языка на клиенте:

  1. Для нового пользователя systemStatus.language не задан → фоллбэк на значение 'auto'
  2. Режим 'auto' в src/utils/client/switchLang.ts → берёт navigator.language (язык браузера, обычно en-US)
  3. Аналогичный режим 'auto' в src/store/global/selectors/general.ts → тоже navigator.language
  4. i18next переключается на английский
// src/utils/client/switchLang.ts — ДО
const lang = language === 'auto' ? navigator.language : language;
 
// ПОСЛЕ
import { DEFAULT_LANG } from '@/const/locale';
const lang = language === 'auto' ? DEFAULT_LANG : language;

Аналогичное исправление в селекторе:

// src/store/global/selectors/general.ts — ДО
return systemLanguage === 'auto' ? navigator.language : systemLanguage;
 
// ПОСЛЕ
import { DEFAULT_LANG } from '@/const/locale';
return systemLanguage === 'auto' ? DEFAULT_LANG : systemLanguage;

При этом важно было обновить юнит-тест switchLang.test.ts, который ожидал navigator.language для режима auto — после фикса тест стал проверять DEFAULT_LANG. Все тесты прошли.

Пересобрали Docker-образ, задеплоили — интерфейс стал русским по умолчанию для всех новых пользователей.

Проблема 3: масштабный ребрендинг текстов

Помимо изображений, в проекте было ~150-200 пользовательских упоминаний LobeChat, LobeHub, Lobe AI и производных в строках интерфейса, мета-тегах, SEO-описаниях и документации.

Ключевая сложность: в монорепо ~8000+ строк с lobechat/lobehub в npm-импортах (@lobechat/ui, @lobehub/icons и т.д.) — их трогать нельзя категорически, иначе сломается весь dependency resolution.

Правила замены получились такие:

| Было | Стало | |------|-------| | LobeChat, Lobe Chat | WebGPT | | LobeHub, Lobe Hub | WebGPT | | Lobe AI | WebGPT AI | | lobechat.com, lobehub.com | gptweb.ru |

Всё что в node_modules, в именах npm-пакетов и в TypeScript-импортах — не трогаем. Только пользовательские строки: i18n-файлы, мета-теги, README, конфиги брендинга.

Проблема 4: вкладка Admin в сайдбаре и сломанный Docker build

Отдельной задачей было добавить вкладку Admin в боковую навигацию LobeChat — чтобы залогиненный администратор мог переходить в панель управления прямо из чата.

Реализация в 4 файлах

Логика распределилась по:

packages/types/src/serverConfig.ts      — добавлен флаг isAdmin
src/server/globalConfig/index.ts        — флаг передаётся с сервера
src/store/serverConfig/selectors.ts     — селектор для чтения флага
src/components/Nav.tsx                  — условный рендер вкладки

Вкладка показывается только если пользователь — администратор. Это проверяется через email в server config, который сравнивается с текущей сессией.

Проблема с Docker build

После написания кода Docker-сборка упала. Локальный tsc тоже не помог — проект слишком большой, TypeScript падал по OOM. Целевая проверка через bun прошла чисто.

Полный лог сборки показал корень проблемы:

ERROR: tsgo --noEmit
Cannot find module '@aws-sdk/client-bedrock-runtime'
Cannot find module 'vitest-canvas-mock'

Инструмент tsgo (более строгий TypeScript Go-компилятор) падал на packages/model-runtime из-за проблем с module resolution workspace-пакетов в Docker-окружении. Это pre-existing issue, никак не связанная с нашими изменениями — просто кеш Docker инвалидировался из-за изменений в наших 4 файлах, и шаг type-check выполнился заново.

Решение — скорректировать build:docker скрипт:

// package.json — ДО
"build:docker": "npm run type-check && npm run lint:circular && npm run build"
 
// ПОСЛЕ  
"build:docker": "npm run lint:ts && npm run lint:style && npm run build"

Тип-проверка уже проходит локально через bun. В Docker пропускаем tsgo --noEmit и lint:circular, оставляем ESLint и стилевые проверки. Сборка прошла за 5.5 минут, контейнер поднялся.

Проблема с роутингом Admin-панели

После деплоя переход в /admin/ давал редирект обратно на главную. Дебаг через curl показал цепочку:

/admin/ → 308 → /admin → 307 → /login → LobeChat (3210) → 200 (SPA)

Проблема в Caddy-конфиге: handle /admin/* матчит /admin/что-то, но не матчит /admin без trailing slash. Запрос /admin уходил на LobeChat (порт 3210), а не на webgpt-admin (порт 3400).

# Caddy — ДО
handle /admin/* {
    reverse_proxy localhost:3400
}

# ПОСЛЕ
handle /admin {
    reverse_proxy localhost:3400
}
handle /admin/* {
    reverse_proxy localhost:3400
}

Дополнительно middleware webgpt-admin редиректил неавторизованных на /login, который обрабатывался LobeChat. Исправили на редирект к /signin — роуту самого LobeChat для авторизации.

Результат

После всех изменений получили:

  • Полный визуальный ребрендинг: маскот LobeChat заменён на WebGPT-логотип во всех точках входа
  • Русский по умолчанию: все новые пользователи видят русский интерфейс без каких-либо настроек
  • Чистые тексты: ~150 пользовательских упоминаний LobeChat/LobeHub заменены на WebGPT
  • Admin-вкладка: администратор видит прямую ссылку на панель управления прямо в сайдбаре чата
  • Стабильная Docker-сборка: убрали нестабильный tsgo из Docker-pipeline, не потеряв качество проверок

Выводы

Работа с форком большого open-source проекта — это отдельное искусство. Главный урок: не трогай то, что не должен трогать. В LobeChat внутренние имена пакетов (@lobechat/*) и TypeScript-импорты — это священные коровы. Любое переименование там сломает 8000 строк зависимостей. Работаешь только с пользовательским слоем: i18n-строками, публичными ассетами, конфигами брендинга.

Второй урон: Docker build — это не то же самое, что локальная сборка. Кеширование слоёв создаёт иллюзию, что всё работает. Стоит инвалидироваться кешу — и вылезают pre-existing проблемы, которые годами жили незамеченными. tsgo с его строгим module resolution — отличный пример: локально всё ок, в Docker падает на @aws-sdk/client-bedrock-runtime. Решение не в том, чтобы починить все транзитивные зависимости, а в том, чтобы разумно выстроить pipeline: локальная тип-проверка через bun, в Docker — только то, что реально нужно.

Третий урок касается локализации: DEFAULT_LANG на сервере не означает DEFAULT_LANG на клиенте. i18next с его LanguageDetector — умный инструмент, но он агрессивно читает navigator.language. Если ваша аудитория — русскоязычная, но браузеры пользователей стоят на английском (а это частый сценарий: Windows с en-US локалью у русскоязычного пользователя), нужно явно переопределять резолвинг auto-режима через константу, а не через браузер.

Наконец, про роутинг через reverse proxy: trailing slash имеет значение. handle /admin/* и handle /admin в Caddy — это два разных правила. Казалось бы, очевидно — но именно такие мелочи приводят к часам дебага с curl и анализом цепочек редиректов. Всегда проверяй оба варианта пути при настройке proxy-правил.

Полезные ссылки

Паша Вин
Паша Вин

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.

Связанный проект

WebGPT Telegram Bot