diff --git a/api/server/routes/config.js b/api/server/routes/config.js index 01f2e4b7ea..a9f6772dee 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -44,6 +44,7 @@ router.get('/', async function (req, res) { isEnabled(process.env.SHOW_BIRTHDAY_ICON) || process.env.SHOW_BIRTHDAY_ICON === '', helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', + interface: req.app.locals.interface, }; if (typeof process.env.CUSTOM_FOOTER === 'string') { diff --git a/api/server/services/AppService.js b/api/server/services/AppService.js index 15e19428da..3b24cee646 100644 --- a/api/server/services/AppService.js +++ b/api/server/services/AppService.js @@ -135,6 +135,7 @@ const AppService = async (app) => { availableTools, fileStrategy, fileConfig: config?.fileConfig, + interface: config?.interface, paths, ...endpointLocals, }; diff --git a/client/src/components/Auth/Login.tsx b/client/src/components/Auth/Login.tsx index a9bb90d8fd..3d9322a23f 100644 --- a/client/src/components/Auth/Login.tsx +++ b/client/src/components/Auth/Login.tsx @@ -91,6 +91,29 @@ function Login() { ), }; + const privacyPolicy = startupConfig.interface?.privacyPolicy; + const termsOfService = startupConfig.interface?.termsOfService; + + const privacyPolicyRender = privacyPolicy?.externalUrl && ( + + {localize('com_ui_privacy_policy')} + + ); + + const termsOfServiceRender = termsOfService?.externalUrl && ( + + {localize('com_ui_terms_of_service')} + + ); + return (
@@ -139,6 +162,13 @@ function Login() { )}
+
+ {privacyPolicyRender} + {privacyPolicyRender && termsOfServiceRender && ( +
+ )} + {termsOfServiceRender} +
); } diff --git a/client/src/components/Chat/Footer.tsx b/client/src/components/Chat/Footer.tsx index de78515159..7cd03d074e 100644 --- a/client/src/components/Chat/Footer.tsx +++ b/client/src/components/Chat/Footer.tsx @@ -4,20 +4,61 @@ import { useLocalize } from '~/hooks'; export default function Footer() { const { data: config } = useGetStartupConfig(); const localize = useLocalize(); + + const privacyPolicy = config?.interface?.privacyPolicy; + const termsOfService = config?.interface?.termsOfService; + + const privacyPolicyRender = privacyPolicy?.externalUrl && ( + + {localize('com_ui_privacy_policy')} + + ); + + const termsOfServiceRender = termsOfService?.externalUrl && ( + + {localize('com_ui_terms_of_service')} + + ); + + const mainContentRender = ( + + {typeof config?.customFooter === 'string' ? ( + config.customFooter + ) : ( + <> + + {config?.appTitle || 'LibreChat'} v0.6.10 + + {' - '} {localize('com_ui_new_footer')} + + )} + + ); + + const footerElements = [mainContentRender, privacyPolicyRender, termsOfServiceRender].filter( + Boolean, + ); + return ( -
- - {typeof config?.customFooter === 'string' ? ( - config.customFooter - ) : ( +
+ {footerElements.map((contentRender, index) => { + const isLastElement = index === footerElements.length - 1; + + return ( <> - - {config?.appTitle || 'LibreChat'} v0.6.10 - - {' - '} {localize('com_ui_new_footer')} + {contentRender} + {!isLastElement &&
} - )} - + ); + })}
); } diff --git a/client/src/localization/languages/Eng.tsx b/client/src/localization/languages/Eng.tsx index c21fc9e740..aaf6ef67ac 100644 --- a/client/src/localization/languages/Eng.tsx +++ b/client/src/localization/languages/Eng.tsx @@ -99,6 +99,8 @@ export default { com_ui_preview: 'Preview', com_ui_upload: 'Upload', com_ui_connect: 'Connect', + com_ui_privacy_policy: 'Privacy policy', + com_ui_terms_of_service: 'Terms of service', com_auth_error_login: 'Unable to login with the information provided. Please check your credentials and try again.', com_auth_error_login_rl: diff --git a/librechat.example.yaml b/librechat.example.yaml index f0bf6088b0..038f571944 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -7,11 +7,23 @@ version: 1.0.4 # Cache settings: Set to true to enable caching cache: true +# Custom nterface configuration +interface: + # Privacy policy settings + privacyPolicy: + externalUrl: 'https://librechat.ai/privacy-policy' + openNewTab: true + + # Terms of service + termsOfService: + externalUrl: 'https://librechat.ai/tos' + openNewTab: true + # Example Registration Object Structure (optional) registration: - socialLogins: ["github", "google", "discord", "openid", "facebook"] + socialLogins: ['github', 'google', 'discord', 'openid', 'facebook'] # allowedDomains: - # - "gmail.com" + # - "gmail.com" # fileConfig: # endpoints: @@ -49,44 +61,40 @@ endpoints: # # excludedIds: ["asst_excludedAssistantId"] custom: # Groq Example - - name: "groq" - apiKey: "${GROQ_API_KEY}" - baseURL: "https://api.groq.com/openai/v1/" + - name: 'groq' + apiKey: '${GROQ_API_KEY}' + baseURL: 'https://api.groq.com/openai/v1/' models: - default: [ - "llama2-70b-4096", - "mixtral-8x7b-32768", - "gemma-7b-it" - ] + default: ['llama2-70b-4096', 'mixtral-8x7b-32768', 'gemma-7b-it'] fetch: false titleConvo: true - titleModel: "mixtral-8x7b-32768" - modelDisplayLabel: "groq" + titleModel: 'mixtral-8x7b-32768' + modelDisplayLabel: 'groq' # Mistral AI Example - - name: "Mistral" # Unique name for the endpoint + - name: 'Mistral' # Unique name for the endpoint # For `apiKey` and `baseURL`, you can use environment variables that you define. # recommended environment variables: - apiKey: "${MISTRAL_API_KEY}" - baseURL: "https://api.mistral.ai/v1" + apiKey: '${MISTRAL_API_KEY}' + baseURL: 'https://api.mistral.ai/v1' # Models configuration - models: + models: # List of default models to use. At least one value is required. - default: ["mistral-tiny", "mistral-small", "mistral-medium"] + default: ['mistral-tiny', 'mistral-small', 'mistral-medium'] # Fetch option: Set to true to fetch models from API. - fetch: true # Defaults to false. + fetch: true # Defaults to false. # Optional configurations - + # Title Conversation setting - titleConvo: true # Set to true to enable title conversation + titleConvo: true # Set to true to enable title conversation # Title Method: Choose between "completion" or "functions". # titleMethod: "completion" # Defaults to "completion" if omitted. # Title Model: Specify the model to use for titles. - titleModel: "mistral-tiny" # Defaults to "gpt-3.5-turbo" if omitted. + titleModel: 'mistral-tiny' # Defaults to "gpt-3.5-turbo" if omitted. # Summarize setting: Set to true to enable summarization. # summarize: false @@ -98,31 +106,30 @@ endpoints: # forcePrompt: false # The label displayed for the AI model in messages. - modelDisplayLabel: "Mistral" # Default is "AI" when not set. + modelDisplayLabel: 'Mistral' # Default is "AI" when not set. # Add additional parameters to the request. Default params will be overwritten. # addParams: - # safe_prompt: true # This field is specific to Mistral AI: https://docs.mistral.ai/api/ - + # safe_prompt: true # This field is specific to Mistral AI: https://docs.mistral.ai/api/ + # Drop Default params parameters from the request. See default params in guide linked below. # NOTE: For Mistral, it is necessary to drop the following parameters or you will encounter a 422 Error: - dropParams: ["stop", "user", "frequency_penalty", "presence_penalty"] + dropParams: ['stop', 'user', 'frequency_penalty', 'presence_penalty'] # OpenRouter Example - - name: "OpenRouter" + - name: 'OpenRouter' # For `apiKey` and `baseURL`, you can use environment variables that you define. # recommended environment variables: # Known issue: you should not use `OPENROUTER_API_KEY` as it will then override the `openAI` endpoint to use OpenRouter as well. - apiKey: "${OPENROUTER_KEY}" - baseURL: "https://openrouter.ai/api/v1" + apiKey: '${OPENROUTER_KEY}' + baseURL: 'https://openrouter.ai/api/v1' models: - default: ["gpt-3.5-turbo"] + default: ['gpt-3.5-turbo'] fetch: true titleConvo: true - titleModel: "gpt-3.5-turbo" + titleModel: 'gpt-3.5-turbo' # Recommended: Drop the stop parameter from the request as Openrouter models use a variety of stop tokens. - dropParams: ["stop"] - modelDisplayLabel: "OpenRouter" - + dropParams: ['stop'] + modelDisplayLabel: 'OpenRouter' # See the Custom Configuration Guide for more information: # https://docs.librechat.ai/install/configuration/custom_config.html diff --git a/package-lock.json b/package-lock.json index a8e5b37ce0..75c9b241fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27994,7 +27994,7 @@ }, "packages/data-provider": { "name": "librechat-data-provider", - "version": "0.4.6", + "version": "0.4.7", "license": "ISC", "dependencies": { "@types/js-yaml": "^4.0.9", diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 3df0c94914..21e78ff130 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -147,6 +147,22 @@ export const rateLimitSchema = z.object({ export const configSchema = z.object({ version: z.string(), cache: z.boolean(), + interface: z + .object({ + privacyPolicy: z + .object({ + externalUrl: z.string().optional(), + openNewTab: z.boolean().optional(), + }) + .optional(), + termsOfService: z + .object({ + externalUrl: z.string().optional(), + openNewTab: z.boolean().optional(), + }) + .optional(), + }) + .optional(), fileStrategy: fileSourceSchema.optional(), registration: z .object({ diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 765471a550..467f1bc461 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -193,9 +193,21 @@ export type TResetPassword = { confirm_password?: string; }; +export type TInterfaceConfig = { + privacyPolicy?: { + externalUrl?: string; + openNewTab?: boolean; + }; + termsOfService?: { + externalUrl?: string; + openNewTab?: boolean; + }; +}; + export type TStartupConfig = { appTitle: string; socialLogins?: string[]; + interface?: TInterfaceConfig; discordLoginEnabled: boolean; facebookLoginEnabled: boolean; githubLoginEnabled: boolean;