📜 feat: Customize Privacy Policy & Terms of Service (#2091)

This commit is contained in:
Flynn 2024-03-14 16:43:18 -04:00 committed by GitHub
parent d4190c9320
commit 1b243c6f8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 155 additions and 45 deletions

View file

@ -44,6 +44,7 @@ router.get('/', async function (req, res) {
isEnabled(process.env.SHOW_BIRTHDAY_ICON) || isEnabled(process.env.SHOW_BIRTHDAY_ICON) ||
process.env.SHOW_BIRTHDAY_ICON === '', process.env.SHOW_BIRTHDAY_ICON === '',
helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai', helpAndFaqURL: process.env.HELP_AND_FAQ_URL || 'https://librechat.ai',
interface: req.app.locals.interface,
}; };
if (typeof process.env.CUSTOM_FOOTER === 'string') { if (typeof process.env.CUSTOM_FOOTER === 'string') {

View file

@ -135,6 +135,7 @@ const AppService = async (app) => {
availableTools, availableTools,
fileStrategy, fileStrategy,
fileConfig: config?.fileConfig, fileConfig: config?.fileConfig,
interface: config?.interface,
paths, paths,
...endpointLocals, ...endpointLocals,
}; };

View file

@ -91,6 +91,29 @@ function Login() {
), ),
}; };
const privacyPolicy = startupConfig.interface?.privacyPolicy;
const termsOfService = startupConfig.interface?.termsOfService;
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className="text-xs font-medium text-gray-500"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined} rel="noreferrer"
>
{localize('com_ui_privacy_policy')}
</a>
);
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className="text-xs font-medium text-gray-500"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined} rel="noreferrer"
>
{localize('com_ui_terms_of_service')}
</a>
);
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0"> <div className="flex min-h-screen flex-col items-center justify-center bg-white pt-6 dark:bg-gray-900 sm:pt-0">
<div className="absolute bottom-0 left-0 m-4"> <div className="absolute bottom-0 left-0 m-4">
@ -139,6 +162,13 @@ function Login() {
</> </>
)} )}
</div> </div>
<div className="flex justify-center gap-4 align-middle">
{privacyPolicyRender}
{privacyPolicyRender && termsOfServiceRender && (
<div className="border-r-[1px] border-gray-300" />
)}
{termsOfServiceRender}
</div>
</div> </div>
); );
} }

View file

@ -4,20 +4,61 @@ import { useLocalize } from '~/hooks';
export default function Footer() { export default function Footer() {
const { data: config } = useGetStartupConfig(); const { data: config } = useGetStartupConfig();
const localize = useLocalize(); const localize = useLocalize();
const privacyPolicy = config?.interface?.privacyPolicy;
const termsOfService = config?.interface?.termsOfService;
const privacyPolicyRender = privacyPolicy?.externalUrl && (
<a
className=" text-gray-500 underline"
href={privacyPolicy.externalUrl}
target={privacyPolicy.openNewTab ? '_blank' : undefined} rel="noreferrer"
>
{localize('com_ui_privacy_policy')}
</a>
);
const termsOfServiceRender = termsOfService?.externalUrl && (
<a
className=" text-gray-500 underline"
href={termsOfService.externalUrl}
target={termsOfService.openNewTab ? '_blank' : undefined} rel="noreferrer"
>
{localize('com_ui_terms_of_service')}
</a>
);
const mainContentRender = (
<span>
{typeof config?.customFooter === 'string' ? (
config.customFooter
) : (
<>
<a href="https://librechat.ai" target="_blank" rel="noreferrer" className="underline">
{config?.appTitle || 'LibreChat'} v0.6.10
</a>
{' - '} {localize('com_ui_new_footer')}
</>
)}
</span>
);
const footerElements = [mainContentRender, privacyPolicyRender, termsOfServiceRender].filter(
Boolean,
);
return ( return (
<div className="relative px-2 py-2 text-center text-xs text-gray-600 dark:text-gray-300 md:px-[60px]"> <div className="relative flex items-center justify-center gap-2 px-2 py-2 text-xs text-gray-600 dark:text-gray-300 md:px-[60px]">
<span> {footerElements.map((contentRender, index) => {
{typeof config?.customFooter === 'string' ? ( const isLastElement = index === footerElements.length - 1;
config.customFooter
) : ( return (
<> <>
<a href="https://librechat.ai" target="_blank" rel="noreferrer" className="underline"> {contentRender}
{config?.appTitle || 'LibreChat'} v0.6.10 {!isLastElement && <div className="h-2 border-r-[1px] border-gray-300" />}
</a>
{' - '} {localize('com_ui_new_footer')}
</> </>
)} );
</span> })}
</div> </div>
); );
} }

View file

@ -99,6 +99,8 @@ export default {
com_ui_preview: 'Preview', com_ui_preview: 'Preview',
com_ui_upload: 'Upload', com_ui_upload: 'Upload',
com_ui_connect: 'Connect', com_ui_connect: 'Connect',
com_ui_privacy_policy: 'Privacy policy',
com_ui_terms_of_service: 'Terms of service',
com_auth_error_login: com_auth_error_login:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
com_auth_error_login_rl: com_auth_error_login_rl:

View file

@ -7,11 +7,23 @@ version: 1.0.4
# Cache settings: Set to true to enable caching # Cache settings: Set to true to enable caching
cache: true 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) # Example Registration Object Structure (optional)
registration: registration:
socialLogins: ["github", "google", "discord", "openid", "facebook"] socialLogins: ['github', 'google', 'discord', 'openid', 'facebook']
# allowedDomains: # allowedDomains:
# - "gmail.com" # - "gmail.com"
# fileConfig: # fileConfig:
# endpoints: # endpoints:
@ -49,44 +61,40 @@ endpoints:
# # excludedIds: ["asst_excludedAssistantId"] # # excludedIds: ["asst_excludedAssistantId"]
custom: custom:
# Groq Example # Groq Example
- name: "groq" - name: 'groq'
apiKey: "${GROQ_API_KEY}" apiKey: '${GROQ_API_KEY}'
baseURL: "https://api.groq.com/openai/v1/" baseURL: 'https://api.groq.com/openai/v1/'
models: models:
default: [ default: ['llama2-70b-4096', 'mixtral-8x7b-32768', 'gemma-7b-it']
"llama2-70b-4096",
"mixtral-8x7b-32768",
"gemma-7b-it"
]
fetch: false fetch: false
titleConvo: true titleConvo: true
titleModel: "mixtral-8x7b-32768" titleModel: 'mixtral-8x7b-32768'
modelDisplayLabel: "groq" modelDisplayLabel: 'groq'
# Mistral AI Example # 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. # For `apiKey` and `baseURL`, you can use environment variables that you define.
# recommended environment variables: # recommended environment variables:
apiKey: "${MISTRAL_API_KEY}" apiKey: '${MISTRAL_API_KEY}'
baseURL: "https://api.mistral.ai/v1" baseURL: 'https://api.mistral.ai/v1'
# Models configuration # Models configuration
models: models:
# List of default models to use. At least one value is required. # 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 option: Set to true to fetch models from API.
fetch: true # Defaults to false. fetch: true # Defaults to false.
# Optional configurations # Optional configurations
# Title Conversation setting # 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". # Title Method: Choose between "completion" or "functions".
# titleMethod: "completion" # Defaults to "completion" if omitted. # titleMethod: "completion" # Defaults to "completion" if omitted.
# Title Model: Specify the model to use for titles. # 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 setting: Set to true to enable summarization.
# summarize: false # summarize: false
@ -98,31 +106,30 @@ endpoints:
# forcePrompt: false # forcePrompt: false
# The label displayed for the AI model in messages. # 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. # Add additional parameters to the request. Default params will be overwritten.
# addParams: # 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. # 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: # 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 # OpenRouter Example
- name: "OpenRouter" - name: 'OpenRouter'
# For `apiKey` and `baseURL`, you can use environment variables that you define. # For `apiKey` and `baseURL`, you can use environment variables that you define.
# recommended environment variables: # 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. # 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}" apiKey: '${OPENROUTER_KEY}'
baseURL: "https://openrouter.ai/api/v1" baseURL: 'https://openrouter.ai/api/v1'
models: models:
default: ["gpt-3.5-turbo"] default: ['gpt-3.5-turbo']
fetch: true fetch: true
titleConvo: 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. # Recommended: Drop the stop parameter from the request as Openrouter models use a variety of stop tokens.
dropParams: ["stop"] dropParams: ['stop']
modelDisplayLabel: "OpenRouter" modelDisplayLabel: 'OpenRouter'
# See the Custom Configuration Guide for more information: # See the Custom Configuration Guide for more information:
# https://docs.librechat.ai/install/configuration/custom_config.html # https://docs.librechat.ai/install/configuration/custom_config.html

2
package-lock.json generated
View file

@ -27994,7 +27994,7 @@
}, },
"packages/data-provider": { "packages/data-provider": {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.4.6", "version": "0.4.7",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",

View file

@ -147,6 +147,22 @@ export const rateLimitSchema = z.object({
export const configSchema = z.object({ export const configSchema = z.object({
version: z.string(), version: z.string(),
cache: z.boolean(), 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(), fileStrategy: fileSourceSchema.optional(),
registration: z registration: z
.object({ .object({

View file

@ -193,9 +193,21 @@ export type TResetPassword = {
confirm_password?: string; confirm_password?: string;
}; };
export type TInterfaceConfig = {
privacyPolicy?: {
externalUrl?: string;
openNewTab?: boolean;
};
termsOfService?: {
externalUrl?: string;
openNewTab?: boolean;
};
};
export type TStartupConfig = { export type TStartupConfig = {
appTitle: string; appTitle: string;
socialLogins?: string[]; socialLogins?: string[];
interface?: TInterfaceConfig;
discordLoginEnabled: boolean; discordLoginEnabled: boolean;
facebookLoginEnabled: boolean; facebookLoginEnabled: boolean;
githubLoginEnabled: boolean; githubLoginEnabled: boolean;