diff --git a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx index bf173b8b4b..e9ddfcdb06 100644 --- a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx @@ -282,6 +282,109 @@ export default function AppearanceTab() { /> + +
+
+
+ +
+
+

{t("whitelabeling")}

+

{t("whitelabelingDesc")}

+
+
+ +
+
+
+

{t("appName")}

+

{t("appNameDesc")}

+
+ updateSetting("instanceName", e.target.value)} + placeholder="OmniRoute" + maxLength={100} + className="h-10 px-3 rounded-lg bg-surface border border-border text-sm text-text-main focus:outline-none focus:border-primary w-48" + /> +
+ +
+
+

{t("customLogo")}

+

{t("customLogoDesc")}

+
+
+ updateSetting("customLogoUrl", e.target.value)} + className="flex-1 h-10 px-3 rounded-lg bg-surface border border-border text-sm text-text-main focus:outline-none focus:border-primary" + placeholder="https://example.com/logo.png" + maxLength={2000} + /> + {(settings.customLogoUrl || settings.customLogoBase64) && ( + Logo preview { + e.currentTarget.style.display = "none"; + }} + /> + )} +
+
+ +
+

{t("uploadLogo")}

+
+ { + const file = e.target.files?.[0]; + if (file) { + if (file.size > 500 * 1024) { + alert("Logo file must be less than 500KB"); + return; + } + const reader = new FileReader(); + reader.onload = (event) => { + const base64 = event.target?.result as string; + updateSetting("customLogoBase64", base64); + }; + reader.readAsDataURL(file); + } + }} + className="text-sm text-text-muted" + /> + +
+ {(settings.customLogoBase64 || settings.customLogoUrl) && ( +
+

{t("logoPreview")}

+ Logo preview +
+ )} +
+
+
); diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx index 9d0b261022..4da90585ef 100644 --- a/src/app/(dashboard)/dashboard/settings/page.tsx +++ b/src/app/(dashboard)/dashboard/settings/page.tsx @@ -23,6 +23,7 @@ import ResilienceTab from "./components/ResilienceTab"; const tabs = [ { id: "general", labelKey: "general", icon: "settings" }, + { id: "appearance", labelKey: "appearance", icon: "palette" }, { id: "ai", labelKey: "ai", icon: "smart_toy" }, { id: "security", labelKey: "security", icon: "shield" }, { id: "routing", labelKey: "routing", icon: "route" }, @@ -75,14 +76,9 @@ export default function SettingsPage() { role="tabpanel" aria-label={t(tabs.find((t2) => t2.id === activeTab)?.labelKey || "general")} > - {activeTab === "general" && ( - <> -
- - -
- - )} + {activeTab === "general" && } + + {activeTab === "appearance" && } {activeTab === "ai" && (
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 522fa600b6..5afdda66ed 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1789,6 +1789,15 @@ "themeViolet": "Violet", "themeOrange": "Orange", "themeCyan": "Cyan", + "whitelabeling": "Branding", + "whitelabelingDesc": "Customize the application name and logo", + "appName": "Application Name", + "appNameDesc": "Display name shown in sidebar and browser tab", + "customLogo": "Custom Logo URL", + "customLogoDesc": "URL to your custom logo image", + "uploadLogo": "Upload Logo", + "resetLogo": "Reset to Default", + "logoPreview": "Preview", "promptCache": "Prompt Cache", "flushCache": "Flush Cache", "flushing": "Flushing…", diff --git a/src/shared/validation/settingsSchemas.ts b/src/shared/validation/settingsSchemas.ts index ac174837a6..76c86b332d 100644 --- a/src/shared/validation/settingsSchemas.ts +++ b/src/shared/validation/settingsSchemas.ts @@ -16,6 +16,8 @@ export const updateSettingsSchema = z.object({ requireLogin: z.boolean().optional(), enableSocks5Proxy: z.boolean().optional(), instanceName: z.string().max(100).optional(), + customLogoUrl: z.string().max(2000).optional(), + customLogoBase64: z.string().max(100000).optional(), corsOrigins: z.string().max(500).optional(), cloudUrl: z.string().max(500).optional(), baseUrl: z.string().max(500).optional(),