| import React, { useEffect } from 'react'; | |
| import ReactMarkdown from 'react-markdown'; | |
| import TagManager from 'react-gtm-module'; | |
| import { Constants } from 'librechat-data-provider'; | |
| import { useGetStartupConfig } from '~/data-provider'; | |
| import { useLocalize } from '~/hooks'; | |
| export default function Footer({ className }: { className?: string }) { | |
| const { data: config } = useGetStartupConfig(); | |
| const localize = useLocalize(); | |
| const privacyPolicy = config?.interface?.privacyPolicy; | |
| const termsOfService = config?.interface?.termsOfService; | |
| const privacyPolicyRender = privacyPolicy?.externalUrl != null && ( | |
| <a | |
| className="text-text-secondary underline" | |
| href={privacyPolicy.externalUrl} | |
| target={privacyPolicy.openNewTab === true ? '_blank' : undefined} | |
| rel="noreferrer" | |
| > | |
| {localize('com_ui_privacy_policy')} | |
| </a> | |
| ); | |
| const termsOfServiceRender = termsOfService?.externalUrl != null && ( | |
| <a | |
| className="text-text-secondary underline" | |
| href={termsOfService.externalUrl} | |
| target={termsOfService.openNewTab === true ? '_blank' : undefined} | |
| rel="noreferrer" | |
| > | |
| {localize('com_ui_terms_of_service')} | |
| </a> | |
| ); | |
| const mainContentParts = ( | |
| typeof config?.customFooter === 'string' | |
| ? config.customFooter | |
| : '[LibreChat ' + | |
| Constants.VERSION + | |
| '](https://librechat.ai) - ' + | |
| localize('com_ui_latest_footer') | |
| ).split('|'); | |
| useEffect(() => { | |
| if (config?.analyticsGtmId != null && typeof window.google_tag_manager === 'undefined') { | |
| const tagManagerArgs = { | |
| gtmId: config.analyticsGtmId, | |
| }; | |
| TagManager.initialize(tagManagerArgs); | |
| } | |
| }, [config?.analyticsGtmId]); | |
| const mainContentRender = mainContentParts.map((text, index) => ( | |
| <React.Fragment key={`main-content-part-${index}`}> | |
| <ReactMarkdown | |
| components={{ | |
| a: ({ node: _n, href, children, ...otherProps }) => { | |
| return ( | |
| <a | |
| className="text-text-secondary underline" | |
| href={href} | |
| target="_blank" | |
| rel="noreferrer" | |
| {...otherProps} | |
| > | |
| {children} | |
| </a> | |
| ); | |
| }, | |
| p: ({ node: _n, ...props }) => <span {...props} />, | |
| }} | |
| > | |
| {text.trim()} | |
| </ReactMarkdown> | |
| </React.Fragment> | |
| )); | |
| const footerElements = [...mainContentRender, privacyPolicyRender, termsOfServiceRender].filter( | |
| Boolean, | |
| ); | |
| return ( | |
| <div className="relative w-full"> | |
| <div | |
| className={ | |
| className ?? | |
| 'absolute bottom-0 left-0 right-0 hidden items-center justify-center gap-2 px-2 py-2 text-center text-xs text-text-primary sm:flex md:px-[60px]' | |
| } | |
| role="contentinfo" | |
| > | |
| {footerElements.map((contentRender, index) => { | |
| const isLastElement = index === footerElements.length - 1; | |
| return ( | |
| <React.Fragment key={`footer-element-${index}`}> | |
| {contentRender} | |
| {!isLastElement && ( | |
| <div | |
| key={`separator-${index}`} | |
| className="h-2 border-r-[1px] border-border-medium" | |
| /> | |
| )} | |
| </React.Fragment> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| } | |