Compare commits

...

1 Commits

Author SHA1 Message Date
Kevin Pham
4b98461fe0 feat(whitelabeling): use application_name and application_description for branding
- Replace hardcoded "Onyx" strings with dynamic application_name from enterprise settings
- Add application_description field to customize login page tagline
- Default tagline remains "Your open source AI platform for work" if not set
2025-11-25 17:35:54 -06:00
17 changed files with 108 additions and 30 deletions

View File

@@ -29,6 +29,7 @@ class EnterpriseSettings(BaseModel):
NOTE: don't put anything sensitive in here, as this is accessible without auth."""
application_name: str | None = None
application_description: str | None = None
use_custom_logo: bool = False
use_custom_logotype: bool = False

View File

@@ -67,6 +67,7 @@ export interface NavigationItem {
export interface EnterpriseSettings {
application_name: string | null;
application_description: string | null;
use_custom_logo: boolean;
use_custom_logotype: boolean;

View File

@@ -5,8 +5,11 @@ import SvgImport from "@/icons/import";
import { REGISTRATION_URL } from "@/lib/constants";
import Button from "@/refresh-components/buttons/Button";
import Link from "next/link";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
export default function Page() {
const applicationName = useApplicationName();
return (
<AuthFlowContainer>
<div className="flex flex-col space-y-6">
@@ -14,12 +17,12 @@ export default function Page() {
Account Not Found
</h2>
<p className="text-text-700 max-w-md text-center">
We couldn&apos;t find your account in our records. To access Onyx, you
need to either:
We couldn&apos;t find your account in our records. To access{" "}
{applicationName}, you need to either:
</p>
<ul className="list-disc text-left text-text-600 w-full pl-6 mx-auto">
<li>Be invited to an existing Onyx team</li>
<li>Create a new Onyx team</li>
<li>Be invited to an existing {applicationName} team</li>
<li>Create a new {applicationName} team</li>
</ul>
<div className="flex justify-center">
<Button

View File

@@ -4,8 +4,11 @@ import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
import Button from "@/refresh-components/buttons/Button";
import { NEXT_PUBLIC_CLOUD_ENABLED } from "@/lib/constants";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
const Page = () => {
const applicationName = useApplicationName();
return (
<AuthFlowContainer>
<div className="flex flex-col space-y-6 max-w-md mx-auto">
@@ -44,7 +47,7 @@ const Page = () => {
{NEXT_PUBLIC_CLOUD_ENABLED && (
<span className="block mt-1 text-blue-600">
If you continue to experience problems please reach out to the
Onyx team at{" "}
{applicationName} team at{" "}
<a href="mailto:support@onyx.app" className="text-blue-600">
support@onyx.app
</a>

View File

@@ -1,19 +1,26 @@
"use client";
import React, { useContext } from "react";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import React from "react";
import { useSettingsContext } from "@/components/settings/SettingsProvider";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
import Text from "@/refresh-components/texts/Text";
const DEFAULT_APPLICATION_DESCRIPTION = "Your open source AI platform for work";
export default function LoginText() {
const settings = useContext(SettingsContext);
const settings = useSettingsContext();
const applicationName = useApplicationName();
const applicationDescription =
settings.enterpriseSettings?.application_description ||
DEFAULT_APPLICATION_DESCRIPTION;
return (
<div className="w-full flex flex-col ">
<Text headingH2 text05>
Welcome to{" "}
{(settings && settings?.enterpriseSettings?.application_name) || "Onyx"}
Welcome to {applicationName}
</Text>
<Text text03 mainUiMuted>
Your open source AI platform for work
{applicationDescription}
</Text>
</div>
);

View File

@@ -14,6 +14,8 @@ import ReferralSourceSelector from "./ReferralSourceSelector";
import AuthErrorDisplay from "@/components/auth/AuthErrorDisplay";
import Text from "@/refresh-components/texts/Text";
import { cn } from "@/lib/utils";
import { fetchEnterpriseSettingsSS } from "@/components/settings/lib";
import { EnterpriseSettings } from "@/app/admin/settings/interfaces";
const Page = async (props: {
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
@@ -32,15 +34,23 @@ const Page = async (props: {
// will not render
let authTypeMetadata: AuthTypeMetadata | null = null;
let currentUser: User | null = null;
let enterpriseSettings: EnterpriseSettings | null = null;
try {
[authTypeMetadata, currentUser] = await Promise.all([
getAuthTypeMetadataSS(),
getCurrentUserSS(),
]);
const enterpriseSettingsResponse = await fetchEnterpriseSettingsSS();
if (enterpriseSettingsResponse.ok) {
enterpriseSettings = await enterpriseSettingsResponse.json();
}
} catch (e) {
console.log(`Some fetch failed for the login page - ${e}`);
}
const applicationName = enterpriseSettings?.application_name || "Onyx";
// simply take the user to the home page if Auth is disabled
if (authTypeMetadata?.authType === "disabled") {
return redirect("/chat");
@@ -82,7 +92,7 @@ const Page = async (props: {
<Text headingH2 text05>
{cloud ? "Complete your sign up" : "Create account"}
</Text>
<Text text03>Get started with Onyx</Text>
<Text text03>Get started with {applicationName}</Text>
</div>
{cloud && authUrl && (
<div className="w-full justify-center mt-6">

View File

@@ -1,5 +1,5 @@
"use client";
import React, { useState, useEffect, useRef, useContext } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useUser } from "@/components/user/UserProvider";
import { usePopup } from "@/components/admin/connectors/Popup";
import {
@@ -36,7 +36,8 @@ import { sendSetDefaultNewTabMessage } from "@/lib/extension/utils";
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
import { CHROME_MESSAGE } from "@/lib/extension/constants";
import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
import { useSettingsContext } from "@/components/settings/SettingsProvider";
export default function NRFPage({
requestCookies,
@@ -58,7 +59,8 @@ export default function NRFPage({
const { user, authTypeMetadata } = useUser();
const { ccPairs, documentSets, tags } = useChatContext();
const { llmProviders } = useLLMProviders();
const settings = useContext(SettingsContext);
const settings = useSettingsContext();
const applicationName = useApplicationName();
const { popup, setPopup } = usePopup();
@@ -214,8 +216,8 @@ export default function NRFPage({
}`}
>
{isNight
? "End your day with Onyx"
: "Start your day with Onyx"}
? `End your day with ${applicationName}`
: `Start your day with ${applicationName}`}
</h1>
<SimplifiedChatInputBar
@@ -293,11 +295,12 @@ export default function NRFPage({
<Dialog open={showTurnOffModal} onOpenChange={setShowTurnOffModal}>
<DialogContent className="w-fit max-w-[95%]">
<DialogHeader>
<DialogTitle>Turn off Onyx new tab page?</DialogTitle>
<DialogTitle>Turn off {applicationName} new tab page?</DialogTitle>
<DialogDescription>
You&apos;ll see your browser&apos;s default new tab page instead.
<br />
You can turn it back on anytime in your Onyx settings.
You can turn it back on anytime in your {applicationName}{" "}
settings.
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex gap-2 justify-center">
@@ -321,7 +324,7 @@ export default function NRFPage({
) : (
<div className="flex flex-col items-center">
<h2 className="text-center text-xl text-strong font-bold mb-4">
Welcome to Onyx
Welcome to {applicationName}
</h2>
<Button
className="w-full"

View File

@@ -6,6 +6,7 @@ import {
darkExtensionImages,
lightExtensionImages,
} from "@/lib/extension/constants";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
const SidebarSwitch = ({
checked,
@@ -59,6 +60,7 @@ export const SettingsPanel = ({
toggleSettings: () => void;
handleUseOnyxToggle: (checked: boolean) => void;
}) => {
const applicationName = useApplicationName();
const {
theme,
setTheme,
@@ -109,7 +111,7 @@ export const SettingsPanel = ({
<SidebarSwitch
checked={useOnyxAsNewTab}
onCheckedChange={handleUseOnyxToggle}
label="Use Onyx as new tab page"
label={`Use ${applicationName} as new tab page`}
/>
<SidebarSwitch

View File

@@ -57,6 +57,8 @@ export function WhitelabelingForm() {
initialValues={{
auto_scroll: settings?.settings?.auto_scroll || false,
application_name: enterpriseSettings?.application_name || null,
application_description:
enterpriseSettings?.application_description || null,
use_custom_logo: enterpriseSettings?.use_custom_logo || false,
use_custom_logotype: enterpriseSettings?.use_custom_logotype || false,
two_lines_for_chat_header:
@@ -77,6 +79,7 @@ export function WhitelabelingForm() {
.trim()
.min(1, "Application name cannot be empty")
.nullable(),
application_description: Yup.string().nullable(),
use_custom_logo: Yup.boolean().required(),
use_custom_logotype: Yup.boolean().required(),
custom_header_content: Yup.string().nullable(),
@@ -144,6 +147,13 @@ export function WhitelabelingForm() {
placeholder="Custom name which will replace 'Onyx'"
disabled={isSubmitting}
/>
<TextFormField
label="Application Description"
name="application_description"
subtext={`A short tagline or description shown on the login page. Defaults to "Your open source AI platform for work" if not set.`}
placeholder="Your custom tagline..."
disabled={isSubmitting}
/>
<div>
<Label className="mt-4">Custom Logo</Label>

View File

@@ -1,5 +1,9 @@
"use client";
import Link from "next/link";
import { OnyxIcon } from "../icons/icons";
import { useSettingsContext } from "@/components/settings/SettingsProvider";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
export default function AuthFlowContainer({
children,
@@ -10,17 +14,29 @@ export default function AuthFlowContainer({
authState?: "signup" | "login" | "join";
footerContent?: React.ReactNode;
}) {
const settings = useSettingsContext();
const applicationName = useApplicationName();
const useCustomLogo = settings.enterpriseSettings?.use_custom_logo;
return (
<div className="p-4 flex flex-col items-center justify-center min-h-screen bg-background">
<div className="w-full max-w-md flex items-start flex-col bg-background-tint-00 rounded-16 shadow-lg shadow-02 p-6">
<OnyxIcon size={44} className="text-theme-primary-05" />
{useCustomLogo ? (
<img
src="/api/enterprise-settings/logo"
alt="Logo"
style={{ objectFit: "contain", height: 44, width: 44 }}
/>
) : (
<OnyxIcon size={44} className="text-theme-primary-05" />
)}
<div className="w-full mt-3">{children}</div>
</div>
{authState === "login" && (
<div className="text-sm mt-6 text-center w-full text-text-03 mainUiBody mx-auto">
{footerContent ?? (
<>
New to Onyx?{" "}
New to {applicationName}?{" "}
<Link
href="/auth/signup"
className="text-text-05 mainUiAction underline transition-colors duration-200"

View File

@@ -43,6 +43,8 @@ export const NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN =
export const TOGGLED_CONNECTORS_COOKIE_NAME = "toggled_connectors";
/* Enterprise-only settings */
export const DEFAULT_APPLICATION_NAME = "Onyx";
export const NEXT_PUBLIC_CUSTOM_REFRESH_URL =
process.env.NEXT_PUBLIC_CUSTOM_REFRESH_URL;

View File

@@ -0,0 +1,9 @@
import { DEFAULT_APPLICATION_NAME } from "@/lib/constants";
import { useSettingsContext } from "@/components/settings/SettingsProvider";
export function useApplicationName() {
const settings = useSettingsContext();
return (
settings.enterpriseSettings?.application_name ?? DEFAULT_APPLICATION_NAME
);
}

View File

@@ -5,8 +5,10 @@ import InputTypeIn from "@/refresh-components/inputs/InputTypeIn";
import Button from "@/refresh-components/buttons/Button";
import { updateUserPersonalization } from "@/lib/userSettings";
import { useUser } from "@/components/user/UserProvider";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
export default function NonAdminStep() {
const applicationName = useApplicationName();
const inputRef = useRef<HTMLInputElement>(null);
const [name, setName] = useState("");
const { refreshUser } = useUser();
@@ -22,7 +24,7 @@ export default function NonAdminStep() {
</div>
<div>
<Text text04 mainUiAction>
What should Onyx call you?
What should {applicationName} call you?
</Text>
<Text text03 secondaryBody>
We will display this name in the app.

View File

@@ -14,6 +14,7 @@ import LLMConnectionModal, {
import { cn } from "@/lib/utils";
import SvgCheckCircle from "@/icons/check-circle";
import { useCreateModal } from "@/refresh-components/contexts/ModalContext";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
type LLMStepProps = {
state: OnboardingState;
@@ -90,6 +91,7 @@ const LLMStepInner = ({
llmDescriptors,
disabled,
}: LLMStepProps) => {
const applicationName = useApplicationName();
const isLoading = !llmDescriptors || llmDescriptors.length === 0;
const [llmConnectionModalProps, setLlmConnectionModalProps] =
@@ -117,7 +119,8 @@ const LLMStepInner = ({
Connect your LLM models
</Text>
<Text text03 secondaryBody>
Onyx supports both self-hosted models and popular providers.
{applicationName} supports both self-hosted models and popular
providers.
</Text>
</div>
</div>

View File

@@ -8,6 +8,7 @@ import { cn } from "@/lib/utils";
import SvgCheckCircle from "@/icons/check-circle";
import IconButton from "@/refresh-components/buttons/IconButton";
import SvgEdit from "@/icons/edit";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
type NameStepProps = {
state: OnboardingState;
@@ -18,6 +19,7 @@ const NameStepInner = ({
state: onboardingState,
actions: onboardingActions,
}: NameStepProps) => {
const applicationName = useApplicationName();
const { userName } = onboardingState.data;
const { updateName, goToStep, setButtonActive, nextStep } = onboardingActions;
@@ -46,7 +48,7 @@ const NameStepInner = ({
</div>
<div>
<Text text04 mainUiAction>
What should Onyx call you?
What should {applicationName} call you?
</Text>
<Text text03 secondaryBody>
We will display this name in the app.

View File

@@ -340,7 +340,9 @@ export default function AdminSidebar({
<div className="flex flex-col gap-2">
{settings.webVersion && (
<Text text02 secondaryBody className="px-2">
{`Onyx version: ${settings.webVersion}`}
{`${
settings.enterpriseSettings?.application_name || "Onyx"
} version: ${settings.webVersion}`}
</Text>
)}
<Settings />

View File

@@ -8,6 +8,7 @@ import { usePathname, useRouter } from "next/navigation";
import { usePopup } from "@/components/admin/connectors/Popup";
import { useUser } from "@/components/user/UserProvider";
import { ThemePreference } from "@/lib/types";
import { useApplicationName } from "@/lib/hooks/useApplicationName";
import Switch from "@/refresh-components/inputs/Switch";
import { SubLabel } from "@/components/Field";
import LLMSelector from "@/components/llm/LLMSelector";
@@ -44,6 +45,7 @@ type SettingsSection =
| "tokens";
export default function UserSettings() {
const applicationName = useApplicationName();
const {
refreshUser,
user,
@@ -541,7 +543,7 @@ export default function UserSettings() {
onChange={(event) =>
updatePersonalizationField("name", event.target.value)
}
placeholder="Set how Onyx should refer to you"
placeholder={`Set how ${applicationName} should refer to you`}
className="mt-2"
/>
{personalizationValues.name.length === 0 && (
@@ -568,7 +570,7 @@ export default function UserSettings() {
<div>
<h3 className="text-lg font-medium">Use memories</h3>
<SubLabel>
Allow Onyx to reference stored memories in future chats.
{`Allow ${applicationName} to reference stored memories in future chats.`}
</SubLabel>
</div>
<Switch
@@ -598,7 +600,7 @@ export default function UserSettings() {
<InputTextArea
key={index}
value={memory}
placeholder="Write something Onyx should remember"
placeholder={`Write something ${applicationName} should remember`}
onChange={(event) =>
updateMemoryAtIndex(index, event.target.value)
}