| import { useMemo, useCallback, useState, useEffect, useRef } from 'react'; |
| import { easings } from '@react-spring/web'; |
| import { EModelEndpoint } from 'librechat-data-provider'; |
| import { BirthdayIcon, TooltipAnchor, SplitText } from '@librechat/client'; |
| import { useChatContext, useAgentsMapContext, useAssistantsMapContext } from '~/Providers'; |
| import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider'; |
| import ConvoIcon from '~/components/Endpoints/ConvoIcon'; |
| import { useLocalize, useAuthContext } from '~/hooks'; |
| import { getIconEndpoint, getEntity } from '~/utils'; |
|
|
| const containerClassName = |
| 'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white dark:bg-presentation dark:text-white text-black dark:after:shadow-none '; |
|
|
| function getTextSizeClass(text: string | undefined | null) { |
| if (!text) { |
| return 'text-xl sm:text-2xl'; |
| } |
|
|
| if (text.length < 40) { |
| return 'text-2xl sm:text-4xl'; |
| } |
|
|
| if (text.length < 70) { |
| return 'text-xl sm:text-2xl'; |
| } |
|
|
| return 'text-lg sm:text-md'; |
| } |
|
|
| export default function Landing({ centerFormOnLanding }: { centerFormOnLanding: boolean }) { |
| const { conversation } = useChatContext(); |
| const agentsMap = useAgentsMapContext(); |
| const assistantMap = useAssistantsMapContext(); |
| const { data: startupConfig } = useGetStartupConfig(); |
| const { data: endpointsConfig } = useGetEndpointsQuery(); |
| const { user } = useAuthContext(); |
| const localize = useLocalize(); |
|
|
| const [textHasMultipleLines, setTextHasMultipleLines] = useState(false); |
| const [lineCount, setLineCount] = useState(1); |
| const [contentHeight, setContentHeight] = useState(0); |
| const contentRef = useRef<HTMLDivElement>(null); |
|
|
| const endpointType = useMemo(() => { |
| let ep = conversation?.endpoint ?? ''; |
| if ( |
| [ |
| EModelEndpoint.chatGPTBrowser, |
| EModelEndpoint.azureOpenAI, |
| EModelEndpoint.gptPlugins, |
| ].includes(ep as EModelEndpoint) |
| ) { |
| ep = EModelEndpoint.openAI; |
| } |
| return getIconEndpoint({ |
| endpointsConfig, |
| iconURL: conversation?.iconURL, |
| endpoint: ep, |
| }); |
| }, [conversation?.endpoint, conversation?.iconURL, endpointsConfig]); |
|
|
| const { entity, isAgent, isAssistant } = getEntity({ |
| endpoint: endpointType, |
| agentsMap, |
| assistantMap, |
| agent_id: conversation?.agent_id, |
| assistant_id: conversation?.assistant_id, |
| }); |
|
|
| const name = entity?.name ?? ''; |
| const description = (entity?.description || conversation?.greeting) ?? ''; |
|
|
| const getGreeting = useCallback(() => { |
| if (typeof startupConfig?.interface?.customWelcome === 'string') { |
| const customWelcome = startupConfig.interface.customWelcome; |
| |
| if (user?.name && customWelcome.includes('{{user.name}}')) { |
| return customWelcome.replace(/{{user.name}}/g, user.name); |
| } |
| return customWelcome; |
| } |
|
|
| const now = new Date(); |
| const hours = now.getHours(); |
|
|
| const dayOfWeek = now.getDay(); |
| const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; |
|
|
| |
| if (hours >= 0 && hours < 5) { |
| return localize('com_ui_late_night'); |
| } |
| |
| else if (hours < 12) { |
| if (isWeekend) { |
| return localize('com_ui_weekend_morning'); |
| } |
| return localize('com_ui_good_morning'); |
| } |
| |
| else if (hours < 17) { |
| return localize('com_ui_good_afternoon'); |
| } |
| |
| else { |
| return localize('com_ui_good_evening'); |
| } |
| }, [localize, startupConfig?.interface?.customWelcome, user?.name]); |
|
|
| const handleLineCountChange = useCallback((count: number) => { |
| setTextHasMultipleLines(count > 1); |
| setLineCount(count); |
| }, []); |
|
|
| useEffect(() => { |
| if (contentRef.current) { |
| setContentHeight(contentRef.current.offsetHeight); |
| } |
| }, [lineCount, description]); |
|
|
| const getDynamicMargin = useMemo(() => { |
| let margin = 'mb-0'; |
|
|
| if (lineCount > 2 || (description && description.length > 100)) { |
| margin = 'mb-10'; |
| } else if (lineCount > 1 || (description && description.length > 0)) { |
| margin = 'mb-6'; |
| } else if (textHasMultipleLines) { |
| margin = 'mb-4'; |
| } |
|
|
| if (contentHeight > 200) { |
| margin = 'mb-16'; |
| } else if (contentHeight > 150) { |
| margin = 'mb-12'; |
| } |
|
|
| return margin; |
| }, [lineCount, description, textHasMultipleLines, contentHeight]); |
|
|
| const greetingText = |
| typeof startupConfig?.interface?.customWelcome === 'string' |
| ? getGreeting() |
| : getGreeting() + (user?.name ? ', ' + user.name : ''); |
|
|
| return ( |
| <div |
| className={`flex h-full transform-gpu flex-col items-center justify-center pb-16 transition-all duration-200 ${centerFormOnLanding ? 'max-h-full sm:max-h-0' : 'max-h-full'} ${getDynamicMargin}`} |
| > |
| <div ref={contentRef} className="flex flex-col items-center gap-0 p-2"> |
| <div |
| className={`flex ${textHasMultipleLines ? 'flex-col' : 'flex-col md:flex-row'} items-center justify-center gap-2`} |
| > |
| <div className={`relative size-10 justify-center ${textHasMultipleLines ? 'mb-2' : ''}`}> |
| <ConvoIcon |
| agentsMap={agentsMap} |
| assistantMap={assistantMap} |
| conversation={conversation} |
| endpointsConfig={endpointsConfig} |
| containerClassName={containerClassName} |
| context="landing" |
| className="h-2/3 w-2/3 text-black dark:text-white" |
| size={41} |
| /> |
| {startupConfig?.showBirthdayIcon && ( |
| <TooltipAnchor |
| className="absolute bottom-[27px] right-2" |
| description={localize('com_ui_happy_birthday')} |
| aria-label={localize('com_ui_happy_birthday')} |
| > |
| <BirthdayIcon /> |
| </TooltipAnchor> |
| )} |
| </div> |
| {((isAgent || isAssistant) && name) || name ? ( |
| <div className="flex flex-col items-center gap-0 p-2"> |
| <SplitText |
| key={`split-text-${name}`} |
| text={name} |
| className={`${getTextSizeClass(name)} font-medium text-text-primary`} |
| delay={50} |
| textAlign="center" |
| animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }} |
| animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }} |
| easing={easings.easeOutCubic} |
| threshold={0} |
| rootMargin="0px" |
| onLineCountChange={handleLineCountChange} |
| /> |
| </div> |
| ) : ( |
| <SplitText |
| key={`split-text-${greetingText}${user?.name ? '-user' : ''}`} |
| text={greetingText} |
| className={`${getTextSizeClass(greetingText)} font-medium text-text-primary`} |
| delay={50} |
| textAlign="center" |
| animationFrom={{ opacity: 0, transform: 'translate3d(0,50px,0)' }} |
| animationTo={{ opacity: 1, transform: 'translate3d(0,0,0)' }} |
| easing={easings.easeOutCubic} |
| threshold={0} |
| rootMargin="0px" |
| onLineCountChange={handleLineCountChange} |
| /> |
| )} |
| </div> |
| {description && ( |
| <div className="animate-fadeIn mt-4 max-w-md text-center text-sm font-normal text-text-primary"> |
| {description} |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| } |
|
|