Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions client/src/components/Chat/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { easings } from '@react-spring/web';
import { EModelEndpoint } from 'librechat-data-provider';
import { BirthdayIcon, TooltipAnchor, SplitText, useRemScale } from '@librechat/client';
import { useChatContext, useAgentsMapContext, useAssistantsMapContext } from '~/Providers';
import { useLocalize, useAuthContext, useGreeting, useLocalizedConfig } from '~/hooks';
import Description, { isHtmlDescription } from '~/components/ui/Description';
import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
import { getIconEndpoint, getEntity, getModelSpec } from '~/utils';
import { useLocalize, useAuthContext, useGreeting } from '~/hooks';
import { useChatSettings } from '~/Providers/ChatSettingsContext';
import AgentContact from '~/components/Agents/AgentContact';
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
Expand Down Expand Up @@ -44,6 +44,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
const { data: endpointsConfig } = useGetEndpointsQuery();
const { user } = useAuthContext();
const localize = useLocalize();
const getLocalizedValue = useLocalizedConfig();
const remScale = useRemScale();
const { isTemporary } = useChatSettings();

Expand Down Expand Up @@ -90,10 +91,9 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
const selectedAgent =
isAgent && conversation?.agent_id != null ? agentsMap?.[conversation.agent_id] : undefined;

const configuredWelcome = startupConfig?.interface?.customWelcome;
const customWelcome =
typeof startupConfig?.interface?.customWelcome === 'string'
? startupConfig.interface.customWelcome
: undefined;
configuredWelcome != null ? getLocalizedValue(configuredWelcome, '') : undefined;

const scheduledGreeting = useGreeting(user?.name);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RecoilRoot } from 'recoil';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ChatSettingsContext, defaultChatSettings } from '~/Providers/ChatSettingsContext';
import store from '~/store/language';
import Landing from '../Landing';

let mockConversation: Record<string, unknown> | null = null;
Expand Down Expand Up @@ -36,6 +37,11 @@ jest.mock('~/Providers', () => ({
useAssistantsMapContext: () => mockAssistantMap,
}));

jest.mock('~/store', () => ({
__esModule: true,
default: jest.requireActual('~/store/language').default,
}));

jest.mock('~/data-provider', () => ({
useGetStartupConfig: () => ({ data: { interface: mockInterfaceConfig } }),
useGetEndpointsQuery: () => ({ data: {} }),
Expand All @@ -44,6 +50,7 @@ jest.mock('~/data-provider', () => ({
jest.mock('~/hooks', () => ({
useAuthContext: () => ({ user: undefined }),
useGreeting: () => 'Welcome',
useLocalizedConfig: jest.requireActual('~/hooks/useLocalizedConfig').default,
useLocalize: () => (key: string) => {
const translations: Record<string, string> = {
com_agents_contact: 'Contact',
Expand Down Expand Up @@ -88,9 +95,12 @@ jest.mock('~/utils', () => ({

jest.mock('~/components/Endpoints/ConvoIcon', () => () => <span data-testid="convo-icon" />);

function renderLanding({ isTemporary = false }: { isTemporary?: boolean } = {}) {
function renderLanding({
isTemporary = false,
lang,
}: { isTemporary?: boolean; lang?: string } = {}) {
return render(
<RecoilRoot>
<RecoilRoot initializeState={({ set }) => lang != null && set(store.lang, lang)}>
<ChatSettingsContext.Provider value={{ ...defaultChatSettings, isTemporary }}>
<Landing centerFormOnLanding={false} />
</ChatSettingsContext.Provider>
Expand Down Expand Up @@ -270,4 +280,19 @@ describe('Landing heading', () => {
expect(screen.getByText('Hello there')).toBeInTheDocument();
expect(screen.queryByText('Data Analyst')).not.toBeInTheDocument();
});

it.each([
['es-AR', 'Hola'],
['en-US', 'Hello'],
['fr', 'Hello'],
])('picks the configured welcome for the %s interface language', (lang, expected) => {
mockInterfaceConfig = {
landingHeading: 'greeting',
customWelcome: { en: 'Hello', es: 'Hola' },
};

renderLanding({ lang });

expect(screen.getByText(expected)).toBeInTheDocument();
});
});
6 changes: 6 additions & 0 deletions librechat.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ projects:
# Custom interface configuration
interface:
customWelcome: 'Welcome to LibreChat! Enjoy your experience.'
# Or one message per language, picked by the user's UI language (exact locale, then base
# language, then 'en', then the first entry):
# customWelcome:
# en: 'Welcome, {{user.name}}!'
# es: '¡Bienvenido, {{user.name}}!'
# pt-BR: 'Boas-vindas, {{user.name}}!'
# What heads the welcome screen when an agent, assistant or model spec is selected (default: entity).
# 'entity' shows its name; 'greeting' shows the time-of-day greeting (or customWelcome) with the
# user's name, for deployments where the header selector already names the agent.
Expand Down
2 changes: 1 addition & 1 deletion packages/data-provider/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2882,7 +2882,7 @@ export const interfaceSchema = z
})
.optional(),
termsOfService: termsOfServiceSchema.optional(),
customWelcome: z.string().optional(),
customWelcome: localizedStringSchema.optional(),
/** What heads the welcome screen when an agent, assistant or branded spec is
* selected: its name (`entity`), or the time-of-day greeting with the user's
* name (`greeting`) for deployments whose header selector already names it. */
Expand Down