Files
lobehub__lobehub/plugins/vite/customBrandingLoadingScreen.ts
LiJian c5caffe684 💄 style: brand the loading screen and LobeHub provider card via the business slot (#18638)
* ✨ feat: brand the static loading screen for custom-branding deployments

The SPA entry html hardcodes the LobeHub wordmark SVG in #loading-brand,
so white-label deployments flash the LobeHub logo before the bundle
boots. Add a transformIndexHtml plugin that swaps it for BRANDING_NAME
(from the business-const slot) when custom branding is active; no-op for
default branding.

* 🐛 fix: expose dependency-free ./branding subpath for config-time imports

Importing the business-const package root from vite.config context fails
under Node's type-stripping loader (the barrel uses extensionless
relative imports). Expose branding.ts — a dependency-free leaf — as a
'./branding' subpath and deep-import it from the loading-screen plugin.
branding.ts must stay import-free for this to keep working.

* 💄 style: brand the LobeHub provider card via the business branding slot

Custom-branding distributions (BRANDING_NAME override) get their own
provider display name and drop the LobeHub marketing copy/links; OSS and
cloud keep the exact previous card.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* 🐛 fix: address codex review on the branding slot PR

- Import BRANDING_NAME from the business-const package root instead of
  the ./branding subpath in modelProviders/lobehub.ts: this file runs at
  normal app runtime, not vite-config time, so it never needed the
  dependency-free leaf — and the root export is what every override
  (including apps/desktop/stubs/business-const) already provides, so no
  companion change is needed there.
- Guard every `t(id.description, { defaultValue: description })` call
  against a falsy description before invoking t() at all, across Header,
  RelatedModels/Item, Related/Item, RelatedProviders/Item, ActionButton
  (share meta), and ProviderGrid/Card. Without the guard, a provider whose
  description was removed (LobeHub under custom branding) still displayed
  the old text for any locale whose already-translated providers.json
  hadn't been regenerated, since i18next only falls back to defaultValue
  when the key is missing, not when it's stale.
- Wire openSettings into ProviderConfig's fallback Button so the primary
  Config CTA still does something when both url and modelsUrl are empty
  (custom branding drops both) — it silently had no onClick before.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-24 15:43:18 +08:00

39 lines
1.4 KiB
TypeScript

// Deep import on purpose: the package root barrel uses extensionless relative
// imports that Node's type-stripping loader (which evaluates externalized
// config-time imports) cannot resolve. branding.ts is a dependency-free leaf
// exposed via the './branding' subpath — keep it import-free.
import { BRANDING_NAME } from '@lobechat/business-const/branding';
import type { Plugin } from 'vite';
const LOADING_BRAND_BLOCK = /<div id="loading-brand"[^>]*>[\S\s]*?<\/div>/;
const escapeHtml = (value: string) =>
value
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;');
/**
* Replace the hardcoded LobeHub wordmark in the static loading screen with the
* custom brand name, so white-label deployments (BRANDING_NAME !== 'LobeHub')
* never flash the LobeHub logo before the SPA boots. No-op for the default
* branding.
*/
export const customBrandingLoadingScreen = (): Plugin => ({
name: 'custom-branding-loading-screen',
transformIndexHtml: {
handler(html) {
if (BRANDING_NAME === 'LobeHub') return html;
return html.replace(
LOADING_BRAND_BLOCK,
`<div id="loading-brand" aria-label="Loading" role="status" style="font-size: 26px; font-weight: 700; letter-spacing: 0.02em;">${escapeHtml(
BRANDING_NAME,
)}</div>`,
);
},
order: 'pre',
},
});