mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
c5caffe684
* ✨ 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>
55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const SAMPLE_HTML = `<body>
|
|
<div id="loading-screen">
|
|
<div id="loading-brand" aria-label="Loading" role="status">
|
|
<svg fill="currentColor" height="40" viewBox="0 0 940 320" xmlns="http://www.w3.org/2000/svg">
|
|
<title>LobeHub</title>
|
|
<path d="M15 240.035V87.172h39.24V205.75h66.192v34.285H15z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div id="root" style="height: 100%"></div>
|
|
</body>`;
|
|
|
|
const loadHandler = async () => {
|
|
const { customBrandingLoadingScreen } = await import('./customBrandingLoadingScreen');
|
|
const plugin = customBrandingLoadingScreen();
|
|
return (plugin.transformIndexHtml as { handler: (html: string) => string }).handler;
|
|
};
|
|
|
|
describe('customBrandingLoadingScreen', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
});
|
|
|
|
it('keeps the default LobeHub wordmark untouched', async () => {
|
|
vi.doMock('@lobechat/business-const/branding', () => ({ BRANDING_NAME: 'LobeHub' }));
|
|
const handler = await loadHandler();
|
|
|
|
expect(handler(SAMPLE_HTML)).toBe(SAMPLE_HTML);
|
|
});
|
|
|
|
it('replaces the wordmark with the custom brand name', async () => {
|
|
vi.doMock('@lobechat/business-const/branding', () => ({ BRANDING_NAME: 'AI Workstation' }));
|
|
const handler = await loadHandler();
|
|
|
|
const result = handler(SAMPLE_HTML);
|
|
expect(result).not.toContain('<svg');
|
|
expect(result).not.toContain('LobeHub');
|
|
expect(result).toContain('AI Workstation');
|
|
expect(result).toContain('id="loading-brand"');
|
|
// the rest of the document is preserved
|
|
expect(result).toContain('<div id="root" style="height: 100%"></div>');
|
|
});
|
|
|
|
it('escapes HTML-sensitive characters in the brand name', async () => {
|
|
vi.doMock('@lobechat/business-const/branding', () => ({ BRANDING_NAME: 'A<B>&"C' }));
|
|
const handler = await loadHandler();
|
|
|
|
const result = handler(SAMPLE_HTML);
|
|
expect(result).toContain('A<B>&"C');
|
|
expect(result).not.toContain('A<B>');
|
|
});
|
|
});
|