From 618be4bf2b7e4c81e9ca36e5098ecd427bfff118 Mon Sep 17 00:00:00 2001 From: Max Sanna Date: Sat, 31 Aug 2024 22:08:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=96=EF=B8=8F=20feat:=20Terms=20and=20Cond?= =?UTF-8?q?itions=20Dialog=20(#3712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added UI for Terms and Conditions Modal Dialogue * Handled the logout on not accepting * Added logic for terms acceptance * Add terms and conditions modal * Fixed bug on terms and conditions modal, clicking out of it won't close it now * Added acceptance of Terms to Database * Removed unnecessary api endpoints from index.js * Added NPM script to reset terms acceptance * Added translations, markdown terms and samples * Merged terms and conditions modal feature * feat/Modal Terms and Conditions Dialog * Amendments as requested by maintainers * Reset package-lock (again) --- api/models/schema/userSchema.js | 5 + api/server/controllers/UserController.js | 29 ++++++ api/server/routes/user.js | 4 + .../components/ui/TermsAndConditionsModal.tsx | 92 +++++++++++++++++++ client/src/data-provider/mutations.ts | 16 ++++ client/src/data-provider/queries.ts | 13 +++ client/src/localization/languages/Ar.ts | 4 + client/src/localization/languages/Br.ts | 4 + client/src/localization/languages/De.ts | 4 + client/src/localization/languages/Eng.ts | 4 + client/src/localization/languages/Es.ts | 4 + client/src/localization/languages/Fi.ts | 10 +- client/src/localization/languages/Fr.ts | 4 + client/src/localization/languages/He.ts | 4 + client/src/localization/languages/Id.ts | 4 + client/src/localization/languages/It.ts | 4 + client/src/localization/languages/Jp.ts | 4 + client/src/localization/languages/Ko.ts | 4 + client/src/localization/languages/Nl.ts | 4 + client/src/localization/languages/Pl.ts | 4 + client/src/localization/languages/Ru.ts | 4 + client/src/localization/languages/Sv.ts | 4 + client/src/localization/languages/Tr.ts | 4 + client/src/localization/languages/Vi.ts | 4 + client/src/localization/languages/Zh.ts | 4 + .../localization/languages/ZhTraditional.ts | 4 + client/src/routes/Root.tsx | 45 ++++++++- config/reset-terms.js | 44 +++++++++ librechat.example.yaml | 38 ++++++++ package.json | 3 +- packages/data-provider/src/api-endpoints.ts | 3 + packages/data-provider/src/config.ts | 3 + packages/data-provider/src/data-service.ts | 8 ++ packages/data-provider/src/keys.ts | 1 + packages/data-provider/src/types.ts | 11 +++ 35 files changed, 393 insertions(+), 8 deletions(-) create mode 100644 client/src/components/ui/TermsAndConditionsModal.tsx create mode 100644 config/reset-terms.js diff --git a/api/models/schema/userSchema.js b/api/models/schema/userSchema.js index 715d82351..57b96342d 100644 --- a/api/models/schema/userSchema.js +++ b/api/models/schema/userSchema.js @@ -122,7 +122,12 @@ const userSchema = mongoose.Schema( type: Date, expires: 604800, // 7 days in seconds }, + termsAccepted: { + type: Boolean, + default: false, + }, }, + { timestamps: true }, ); diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index 099ba68d8..abe89d2b0 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -8,6 +8,7 @@ const { deleteMessages, deleteUserById, } = require('~/models'); +const User = require('~/models/User'); const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService'); const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService'); const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService'); @@ -20,6 +21,32 @@ const getUserController = async (req, res) => { res.status(200).send(req.user); }; +const getTermsStatusController = async (req, res) => { + try { + const user = await User.findById(req.user.id); + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } + res.status(200).json({ termsAccepted: !!user.termsAccepted }); + } catch (error) { + logger.error('Error fetching terms acceptance status:', error); + res.status(500).json({ message: 'Error fetching terms acceptance status' }); + } +}; + +const acceptTermsController = async (req, res) => { + try { + const user = await User.findByIdAndUpdate(req.user.id, { termsAccepted: true }, { new: true }); + if (!user) { + return res.status(404).json({ message: 'User not found' }); + } + res.status(200).json({ message: 'Terms accepted successfully' }); + } catch (error) { + logger.error('Error accepting terms:', error); + res.status(500).json({ message: 'Error accepting terms' }); + } +}; + const deleteUserFiles = async (req) => { try { const userFiles = await getFiles({ user: req.user.id }); @@ -135,6 +162,8 @@ const resendVerificationController = async (req, res) => { module.exports = { getUserController, + getTermsStatusController, + acceptTermsController, deleteUserController, verifyEmailController, updateUserPluginsController, diff --git a/api/server/routes/user.js b/api/server/routes/user.js index 5f260d076..34d28fd93 100644 --- a/api/server/routes/user.js +++ b/api/server/routes/user.js @@ -6,11 +6,15 @@ const { verifyEmailController, updateUserPluginsController, resendVerificationController, + getTermsStatusController, + acceptTermsController, } = require('~/server/controllers/UserController'); const router = express.Router(); router.get('/', requireJwtAuth, getUserController); +router.get('/terms', requireJwtAuth, getTermsStatusController); +router.post('/terms/accept', requireJwtAuth, acceptTermsController); router.post('/plugins', requireJwtAuth, updateUserPluginsController); router.delete('/delete', requireJwtAuth, canDeleteAccount, deleteUserController); router.post('/verify', verifyEmailController); diff --git a/client/src/components/ui/TermsAndConditionsModal.tsx b/client/src/components/ui/TermsAndConditionsModal.tsx new file mode 100644 index 000000000..c9bc79126 --- /dev/null +++ b/client/src/components/ui/TermsAndConditionsModal.tsx @@ -0,0 +1,92 @@ +import { useLocalize } from '~/hooks'; +import { OGDialog } from '~/components/ui'; +import DialogTemplate from '~/components/ui/DialogTemplate'; +import { useAuthContext } from '~/hooks'; +import Markdown from '~/components/Chat/Messages/Content/Markdown'; +import { useToastContext } from '~/Providers'; +import { useAcceptTermsMutation } from '~/data-provider'; + +const TermsAndConditionsModal = ({ + open, + onOpenChange, + onAccept, + onDecline, + title, + modalContent, +}: { + open: boolean; + onOpenChange: (isOpen: boolean) => void; + onAccept: () => void; + onDecline: () => void; + title?: string; + contentUrl?: string; + modalContent?: string; +}) => { + const localize = useLocalize(); + const { showToast } = useToastContext(); + const acceptTermsMutation = useAcceptTermsMutation({ + onSuccess: () => { + onAccept(); + onOpenChange(false); + }, + onError: () => { + showToast({ message: 'Failed to accept terms' }); + }, + }); + + const handleAccept = () => { + acceptTermsMutation.mutate(); + }; + + const handleDecline = () => { + onDecline(); + onOpenChange(false); + }; + + const handleOpenChange = (isOpen: boolean) => { + if (open && !isOpen) { + return; + } + onOpenChange(isOpen); + }; + + return ( + + +
+ {modalContent ? ( + + ) : ( +

{localize('com_ui_no_terms_content')}

+ )} +
+ + } + buttons={ + <> + + + + } + /> +
+ ); +}; + +export default TermsAndConditionsModal; diff --git a/client/src/data-provider/mutations.ts b/client/src/data-provider/mutations.ts index 738f36e35..2f5ae546c 100644 --- a/client/src/data-provider/mutations.ts +++ b/client/src/data-provider/mutations.ts @@ -1128,3 +1128,19 @@ export const useResendVerificationEmail = ( ...(options || {}), }); }; + +export const useAcceptTermsMutation = ( + options?: t.AcceptTermsMutationOptions, +): UseMutationResult => { + const queryClient = useQueryClient(); + return useMutation(() => dataService.acceptTerms(), { + onSuccess: (data, variables, context) => { + queryClient.setQueryData([QueryKeys.userTerms], { + termsAccepted: true, + }); + options?.onSuccess?.(data, variables, context); + }, + onError: options?.onError, + onMutate: options?.onMutate, + }); +}; diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index bef55959d..f25d141f7 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -28,6 +28,8 @@ import type { TCheckUserKeyResponse, SharedLinkListParams, SharedLinksResponse, + TUserTermsResponse, + TAcceptTermsResponse, } from 'librechat-data-provider'; import { findPageForConversation, addFileToCache } from '~/utils'; @@ -564,3 +566,14 @@ export const useGetRandomPrompts = ( }, ); }; + +export const useUserTermsQuery = ( + config?: UseQueryOptions, +): QueryObserverResult => { + return useQuery([QueryKeys.userTerms], () => dataService.getUserTerms(), { + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + ...config, + }); +}; diff --git a/client/src/localization/languages/Ar.ts b/client/src/localization/languages/Ar.ts index e2b33ebdf..68336d393 100644 --- a/client/src/localization/languages/Ar.ts +++ b/client/src/localization/languages/Ar.ts @@ -485,6 +485,10 @@ export default { com_ui_terms_of_service: 'شروط الخدمة', com_ui_min_tags: 'لا يمكن إزالة المزيد من القيم، الحد الأدنى المطلوب هو {0}.', com_ui_max_tags: 'الحد الأقصى المسموح به هو {0}، باستخدام أحدث القيم.', + com_ui_accept: 'أوافق', + com_ui_decline: 'لا أوافق', + com_ui_terms_and_conditions: 'شروط الخدمة', + com_ui_no_terms_content: 'لا يوجد محتوى لشروط الخدمة', com_auth_back_to_login: 'العودة إلى تسجيل الدخول', com_endpoint_message: 'رسالة', com_endpoint_message_not_appendable: 'عدّل رسالتك أو أعد إنشاءها.', diff --git a/client/src/localization/languages/Br.ts b/client/src/localization/languages/Br.ts index 31cbcb326..011ac2598 100644 --- a/client/src/localization/languages/Br.ts +++ b/client/src/localization/languages/Br.ts @@ -183,6 +183,10 @@ export default { com_ui_bookmarks_update_error: 'Houve um erro ao atualizar o favorito', com_ui_bookmarks_delete_error: 'Houve um erro ao excluir o favorito', com_ui_bookmarks_add_to_conversation: 'Adicionar à conversa atual', + com_ui_accept: 'Eu aceito', + com_ui_decline: 'Eu não aceito', + com_ui_terms_and_conditions: 'Termos e Condições', + com_ui_no_terms_content: 'Nenhum conteúdo de termos e condições para exibir', com_auth_error_login: 'Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/De.ts b/client/src/localization/languages/De.ts index 3734c5393..d87cd0c87 100644 --- a/client/src/localization/languages/De.ts +++ b/client/src/localization/languages/De.ts @@ -326,6 +326,10 @@ export default { com_ui_bookmarks_delete_error: 'Beim Löschen des Lesezeichens ist ein Fehler aufgetreten', com_ui_bookmarks_add_to_conversation: 'Zur aktuellen Konversation hinzufügen', com_ui_bookmarks_filter: 'Lesezeichen filtern...', + com_ui_accept: 'Ich akzeptiere', + com_ui_decline: 'Ich akzeptiere nicht', + com_ui_terms_and_conditions: 'Allgemeine Geschäftsbedingungen', + com_ui_no_terms_content: 'Keine Inhalte der Allgemeinen Geschäftsbedingungen zum Anzeigen', com_auth_error_login: 'Anmeldung mit den angegebenen Informationen nicht möglich. Bitte überprüfe deine Anmeldedaten und versuche es erneut.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Eng.ts b/client/src/localization/languages/Eng.ts index a693cee1b..a368e72ef 100644 --- a/client/src/localization/languages/Eng.ts +++ b/client/src/localization/languages/Eng.ts @@ -771,4 +771,8 @@ export default { com_nav_lang_indonesia: 'Indonesia', com_nav_lang_hebrew: 'עברית', com_nav_lang_finnish: 'Suomi', + com_ui_accept: 'I accept', + com_ui_decline: 'I do not accept', + com_ui_terms_and_conditions: 'Terms and Conditions', + com_ui_no_terms_content: 'No terms and conditions content to display', }; diff --git a/client/src/localization/languages/Es.ts b/client/src/localization/languages/Es.ts index 816ff83a0..375e2cfde 100644 --- a/client/src/localization/languages/Es.ts +++ b/client/src/localization/languages/Es.ts @@ -185,6 +185,10 @@ export default { com_ui_bookmarks_update_error: 'Hubo un error al actualizar el marcador', com_ui_bookmarks_delete_error: 'Hubo un error al eliminar el marcador', com_ui_bookmarks_add_to_conversation: 'Agregar a la conversación actual', + com_ui_accept: 'Acepto', + com_ui_decline: 'No acepto', + com_ui_terms_and_conditions: 'Términos y Condiciones', + com_ui_no_terms_content: 'No hay contenido de términos y condiciones para mostrar', com_auth_error_login: 'No se puede iniciar sesión con la información proporcionada. Verifique sus credenciales y vuelva a intentarlo.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Fi.ts b/client/src/localization/languages/Fi.ts index ea12a0e65..02a8bba63 100644 --- a/client/src/localization/languages/Fi.ts +++ b/client/src/localization/languages/Fi.ts @@ -295,8 +295,7 @@ export default { com_ui_bookmarks: 'Kirjanmerkit', com_ui_bookmarks_rebuild: 'Uudelleenkokoa', com_ui_bookmarks_new: 'Uusi kirjanmerkki', - com_ui_bookmark_delete_confirm: - 'Oletko varma, että haluat poistaa tämän kirjanmerkin?', + com_ui_bookmark_delete_confirm: 'Oletko varma, että haluat poistaa tämän kirjanmerkin?', com_ui_bookmarks_title: 'Otsikko', com_ui_bookmarks_count: 'Määrä', com_ui_bookmarks_description: 'Kuvaus', @@ -307,6 +306,10 @@ export default { com_ui_bookmarks_update_error: 'Virhe kirjanmerkin päivittämisessä', com_ui_bookmarks_delete_error: 'Virhe kirjanmerkin poistamisessa', com_ui_bookmarks_add_to_conversation: 'Lisää nykyiseen keskusteluun', + com_ui_accept: 'Hyväksyn', + com_ui_decline: 'En hyväksy', + com_ui_terms_and_conditions: 'Käyttöehdot', + com_ui_no_terms_content: 'Ei käyttöehtoja näytettäväksi', com_auth_error_login: 'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.', com_auth_error_login_rl: @@ -402,7 +405,8 @@ export default { com_endpoint_token_count: 'Token-määrä', com_endpoint_output: 'Tulos', com_endpoint_context_tokens: 'Konteksti-tokenien maksimimäärä', - com_endpoint_context_info: 'Kontekstia varten käytettävien tokeneiden maksimimäärä. Käytä tätä pyyntökohtaisten token-määrien hallinnointiin. Jos tätä ei määritetä, käytössä ovat järjestelmän oletusarvot perustuen tiedossa olevien mallien konteksti-ikkunoiden kokoon. Korkeamman arvon asettaminen voi aiheuttaa virheitä tai korkeamman token-hinnan.', + com_endpoint_context_info: + 'Kontekstia varten käytettävien tokeneiden maksimimäärä. Käytä tätä pyyntökohtaisten token-määrien hallinnointiin. Jos tätä ei määritetä, käytössä ovat järjestelmän oletusarvot perustuen tiedossa olevien mallien konteksti-ikkunoiden kokoon. Korkeamman arvon asettaminen voi aiheuttaa virheitä tai korkeamman token-hinnan.', com_endpoint_google_temp: 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', com_endpoint_google_topp: diff --git a/client/src/localization/languages/Fr.ts b/client/src/localization/languages/Fr.ts index aa5c7ef21..29a602aef 100644 --- a/client/src/localization/languages/Fr.ts +++ b/client/src/localization/languages/Fr.ts @@ -104,6 +104,10 @@ export default { com_ui_bookmarks_update_error: 'Une erreur est survenue lors de la mise à jour du signet', com_ui_bookmarks_delete_error: 'Une erreur est survenue lors de la suppression du signet', com_ui_bookmarks_add_to_conversation: 'Ajouter à la conversation en cours', + com_ui_accept: 'J\'accepte', + com_ui_decline: 'Je n\'accepte pas', + com_ui_terms_and_conditions: 'Conditions d\'utilisation', + com_ui_no_terms_content: 'Aucun contenu de conditions d\'utilisation à afficher', com_auth_error_login: 'Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/He.ts b/client/src/localization/languages/He.ts index 5a81eca09..45b002b71 100644 --- a/client/src/localization/languages/He.ts +++ b/client/src/localization/languages/He.ts @@ -134,6 +134,10 @@ export default { com_ui_bookmarks_update_error: 'אירעה שגיאה בעת עדכון הסימניה', com_ui_bookmarks_delete_error: 'אירעה שגיאה בעת מחיקת הסימניה', com_ui_bookmarks_add_to_conversation: 'הוסף לשיחה הנוכחית', + com_ui_accept: 'אני מקבל', + com_ui_decline: 'אני לא מקבל', + com_ui_terms_and_conditions: 'תנאים והגבלות', + com_ui_no_terms_content: 'אין תוכן תנאים והגבלות להצגה', com_auth_error_login: 'לא ניתן להתחבר עם המידע שסופק. אנא בדוק את האישורים שלך ונסה שוב.', com_auth_error_login_rl: 'יותר מדי ניסיונות כניסה בזמן קצר. בבקשה נסה שוב מאוחר יותר.', com_auth_error_login_ban: 'החשבון שלך נחסם באופן זמני עקב הפרות של השירות שלנו.', diff --git a/client/src/localization/languages/Id.ts b/client/src/localization/languages/Id.ts index dc216782c..d95fdc4b9 100644 --- a/client/src/localization/languages/Id.ts +++ b/client/src/localization/languages/Id.ts @@ -102,6 +102,10 @@ export default { com_ui_bookmarks_update_error: 'Terjadi kesalahan saat memperbarui penanda', com_ui_bookmarks_delete_error: 'Terjadi kesalahan saat menghapus penanda', com_ui_bookmarks_add_to_conversation: 'Tambahkan ke percakapan saat ini', + com_ui_accept: 'Saya menerima', + com_ui_decline: 'Saya tidak menerima', + com_ui_terms_and_conditions: 'Syarat dan Ketentuan', + com_ui_no_terms_content: 'Tidak ada konten syarat dan ketentuan untuk ditampilkan', com_auth_error_login: 'Tidak dapat masuk dengan informasi yang diberikan. Silakan periksa kredensial Anda dan coba lagi.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/It.ts b/client/src/localization/languages/It.ts index d547ec443..88e5af0fe 100644 --- a/client/src/localization/languages/It.ts +++ b/client/src/localization/languages/It.ts @@ -592,6 +592,10 @@ export default { com_user_message: 'Mostra nome utente nei messaggi', com_ui_fork: 'Duplica', com_ui_mention: 'Menziona un endpoint, assistente o preset per passare rapidamente ad esso', + com_ui_accept: 'Accetto', + com_ui_decline: 'Non accetto', + com_ui_terms_and_conditions: 'Termini d\'uso', + com_ui_no_terms_content: 'Nessun contenuto dei termini d\'uso da visualizzare', com_endpoint_context_tokens: 'Token di Contesto Massimi', com_endpoint_context_info: 'Il numero massimo di token che possono essere utilizzati per il contesto. Usalo per controllare quanti token vengono inviati per richiesta. Se non specificato, verranno utilizzate le impostazioni di sistema predefinite in base alle dimensioni del contesto dei modelli noti. Impostare valori più alti potrebbe causare errori e/o costi di token più elevati.', diff --git a/client/src/localization/languages/Jp.ts b/client/src/localization/languages/Jp.ts index 077eb3bd1..e515ced19 100644 --- a/client/src/localization/languages/Jp.ts +++ b/client/src/localization/languages/Jp.ts @@ -196,6 +196,10 @@ export default { com_ui_bookmarks_update_error: 'ブックマークの更新中にエラーが発生しました', com_ui_bookmarks_delete_error: 'ブックマークの削除中にエラーが発生しました', com_ui_bookmarks_add_to_conversation: '現在の会話に追加', + com_ui_accept: '同意します', + com_ui_decline: '同意しません', + com_ui_terms_and_conditions: '利用規約', + com_ui_no_terms_content: '利用規約の内容がありません', com_auth_error_login: '入力された情報ではログインできませんでした。認証情報を確認した上で再度お試しください。', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Ko.ts b/client/src/localization/languages/Ko.ts index 801966bd1..7b2933ec9 100644 --- a/client/src/localization/languages/Ko.ts +++ b/client/src/localization/languages/Ko.ts @@ -88,6 +88,10 @@ export default { com_ui_bookmarks_update_error: '북마크 업데이트 중 오류가 발생했습니다', com_ui_bookmarks_delete_error: '북마크 삭제 중 오류가 발생했습니다', com_ui_bookmarks_add_to_conversation: '현재 대화에 추가', + com_ui_accept: '동의합니다', + com_ui_decline: '동의하지 않습니다', + com_ui_terms_and_conditions: '이용 약관', + com_ui_no_terms_content: '이용 약관 내용이 없습니다', com_auth_error_login: '제공된 정보로 로그인할 수 없습니다. 자격 증명을 확인하고 다시 시도하세요.', com_auth_no_account: '계정이 없으신가요?', com_auth_sign_up: '가입하기', diff --git a/client/src/localization/languages/Nl.ts b/client/src/localization/languages/Nl.ts index 89689152d..3386d4a74 100644 --- a/client/src/localization/languages/Nl.ts +++ b/client/src/localization/languages/Nl.ts @@ -94,6 +94,10 @@ export default { com_ui_bookmarks_update_error: 'Er is een fout opgetreden bij het bijwerken van de bladwijzer', com_ui_bookmarks_delete_error: 'Er is een fout opgetreden bij het verwijderen van de bladwijzer', com_ui_bookmarks_add_to_conversation: 'Toevoegen aan huidig gesprek', + com_ui_accept: 'Ik accepteer', + com_ui_decline: 'Ik accepteer niet', + com_ui_terms_and_conditions: 'Gebruiksvoorwaarden', + com_ui_no_terms_content: 'Geen gebruiksvoorwaarden-inhoud om weer te geven', com_auth_error_login: 'Kan niet inloggen met de verstrekte informatie. Controleer uw referenties en probeer het opnieuw.', com_auth_error_login_rl: 'Te veel inlogpogingen in een korte tijd. Probeer het later nog eens.', diff --git a/client/src/localization/languages/Pl.ts b/client/src/localization/languages/Pl.ts index 4d1f42402..f0e4cc3de 100644 --- a/client/src/localization/languages/Pl.ts +++ b/client/src/localization/languages/Pl.ts @@ -261,6 +261,10 @@ export default { com_ui_import_conversation_info: 'Importuj konwersacje z pliku JSON', com_ui_import_conversation_success: 'Konwersacje zostały pomyślnie zaimportowane', com_ui_import_conversation_error: 'Wystąpił błąd podczas importowania konwersacji', + com_ui_accept: 'Akceptuję', + com_ui_decline: 'Nie akceptuję', + com_ui_terms_and_conditions: 'Warunki użytkowania', + com_ui_no_terms_content: 'Brak treści warunków użytkowania do wyświetlenia', }; export const comparisons = { diff --git a/client/src/localization/languages/Ru.ts b/client/src/localization/languages/Ru.ts index 3f6d2021f..e11dec0e6 100644 --- a/client/src/localization/languages/Ru.ts +++ b/client/src/localization/languages/Ru.ts @@ -104,6 +104,10 @@ export default { com_ui_bookmarks_update_error: 'Произошла ошибка при обновлении закладки', com_ui_bookmarks_delete_error: 'Произошла ошибка при удалении закладки', com_ui_bookmarks_add_to_conversation: 'Добавить в текущий разговор', + com_ui_accept: 'Принимаю', + com_ui_decline: 'Не принимаю', + com_ui_terms_and_conditions: 'Условия использования', + com_ui_no_terms_content: 'Нет содержания условий использования для отображения', com_auth_error_login: 'Не удалось войти с предоставленной информацией. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Sv.ts b/client/src/localization/languages/Sv.ts index 985f9a4a8..4bf5c33af 100644 --- a/client/src/localization/languages/Sv.ts +++ b/client/src/localization/languages/Sv.ts @@ -90,6 +90,10 @@ export default { com_ui_bookmarks_update_error: 'Ett fel uppstod vid uppdateringen av bokmärket', com_ui_bookmarks_delete_error: 'Ett fel uppstod vid raderingen av bokmärket', com_ui_bookmarks_add_to_conversation: 'Lägg till i nuvarande konversation', + com_ui_accept: 'Jag accepterar', + com_ui_decline: 'Jag accepterar inte', + com_ui_terms_and_conditions: 'Villkor för användning', + com_ui_no_terms_content: 'Ingen innehåll för villkor för användning att visa', com_auth_error_login: 'Kunde inte logga in med den angivna informationen. Kontrollera dina uppgifter och försök igen.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Tr.ts b/client/src/localization/languages/Tr.ts index 122f141d7..df19e0bde 100644 --- a/client/src/localization/languages/Tr.ts +++ b/client/src/localization/languages/Tr.ts @@ -264,6 +264,10 @@ export default { com_ui_bookmarks_update_error: 'Yer imi güncellenirken bir hata oluştu', com_ui_bookmarks_delete_error: 'Yer imi silinirken bir hata oluştu', com_ui_bookmarks_add_to_conversation: 'Mevcut sohbete ekle', + com_ui_accept: 'Kabul ediyorum', + com_ui_decline: 'Kabul etmiyorum', + com_ui_terms_and_conditions: 'Şartlar ve koşullar', + com_ui_no_terms_content: 'Şartlar ve koşullar için içerik bulunmuyor', com_auth_error_login: 'Sağlanan bilgilerle giriş yapılamıyor. Lütfen kimlik bilgilerinizi kontrol edin ve tekrar deneyin.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Vi.ts b/client/src/localization/languages/Vi.ts index 4a0e952e6..97adcf5a4 100644 --- a/client/src/localization/languages/Vi.ts +++ b/client/src/localization/languages/Vi.ts @@ -92,6 +92,10 @@ export default { com_ui_bookmarks_update_error: 'Có lỗi xảy ra khi cập nhật dấu trang', com_ui_bookmarks_delete_error: 'Có lỗi xảy ra khi x��a dấu trang', com_ui_bookmarks_add_to_conversation: 'Thêm vào cuộc hội thoại hiện tại', + com_ui_accept: 'Tôi chấp nhận', + com_ui_decline: 'Tôi không chấp nhận', + com_ui_terms_and_conditions: 'Điều khoản và điều kiện', + com_ui_no_terms_content: 'Không có nội dung điều khoản và điều kiện để hiển thị', com_auth_error_login: 'Không thể đăng nhập với thông tin được cung cấp. Vui lòng kiểm tra thông tin đăng nhập và thử lại.', com_auth_error_login_rl: diff --git a/client/src/localization/languages/Zh.ts b/client/src/localization/languages/Zh.ts index bd6ca1d96..9a0a0e18c 100644 --- a/client/src/localization/languages/Zh.ts +++ b/client/src/localization/languages/Zh.ts @@ -174,6 +174,10 @@ export default { com_ui_bookmarks_update_error: '更新书签时出错', com_ui_bookmarks_delete_error: '删除书签时出错', com_ui_bookmarks_add_to_conversation: '添加到当前对话', + com_ui_accept: '我接受', + com_ui_decline: '我不接受', + com_ui_terms_and_conditions: '条款和条件', + com_ui_no_terms_content: '没有条款和条件内容显示', com_auth_error_login: '无法登录,请确认提供的账户密码正确,并重新尝试。', com_auth_error_login_rl: '尝试登录次数过多,请稍后再试。', com_auth_error_login_ban: '根据我们的服务规则,您的帐号被暂时禁用。', diff --git a/client/src/localization/languages/ZhTraditional.ts b/client/src/localization/languages/ZhTraditional.ts index 1aefec3e0..8049e920b 100644 --- a/client/src/localization/languages/ZhTraditional.ts +++ b/client/src/localization/languages/ZhTraditional.ts @@ -85,6 +85,10 @@ export default { com_ui_bookmarks_update_error: '更新書籤時出錯', com_ui_bookmarks_delete_error: '刪除書籤時出錯', com_ui_bookmarks_add_to_conversation: '添加到當前對話', + com_ui_accept: '我接受', + com_ui_decline: '我不同意', + com_ui_terms_and_conditions: '條款和條件', + com_ui_no_terms_content: '沒有條款和條件內容顯示', com_auth_error_login: '無法使用提供的資訊登入。請檢查您的登入資訊後重試。', com_auth_error_login_rl: '短時間內嘗試登入的次數過多。請稍後再試。', com_auth_error_login_ban: '由於違反我們的服務條款,您的帳號已被暫時停用。', diff --git a/client/src/routes/Root.tsx b/client/src/routes/Root.tsx index 25722b3f7..13ade2590 100644 --- a/client/src/routes/Root.tsx +++ b/client/src/routes/Root.tsx @@ -1,14 +1,18 @@ -import { useState } from 'react'; -import { Outlet } from 'react-router-dom'; +import React, { useState, useEffect } from 'react'; +import { Outlet, useNavigate } from 'react-router-dom'; +import { useGetStartupConfig } from 'librechat-data-provider/react-query'; +import { useUserTermsQuery } from '~/data-provider'; import type { ContextType } from '~/common'; import { useAuthContext, useAssistantsMap, useFileMap, useSearch } from '~/hooks'; import { AssistantsMapContext, FileMapContext, SearchContext } from '~/Providers'; import { Nav, MobileNav } from '~/components/Nav'; +import TermsAndConditionsModal from '~/components/ui/TermsAndConditionsModal'; export default function Root() { - const { isAuthenticated } = useAuthContext(); - const [navVisible, setNavVisible] = useState(() => { + const { isAuthenticated, logout, token } = useAuthContext(); + const navigate = useNavigate(); + const [navVisible, setNavVisible] = useState(() => { const savedNavVisible = localStorage.getItem('navVisible'); return savedNavVisible !== null ? JSON.parse(savedNavVisible) : true; }); @@ -17,6 +21,29 @@ export default function Root() { const fileMap = useFileMap({ isAuthenticated }); const assistantsMap = useAssistantsMap({ isAuthenticated }); + const [showTerms, setShowTerms] = useState(false); + const { data: config } = useGetStartupConfig(); + + const { data: termsData } = useUserTermsQuery({ + enabled: isAuthenticated && !!config?.interface?.termsOfService?.modalAcceptance, + }); + + useEffect(() => { + if (termsData) { + setShowTerms(!termsData.termsAccepted); + } + }, [termsData]); + + const handleAcceptTerms = () => { + setShowTerms(false); + }; + + const handleDeclineTerms = () => { + setShowTerms(false); + logout(); + navigate('/login'); + }; + if (!isAuthenticated) { return null; } @@ -34,6 +61,16 @@ export default function Root() { + {config?.interface?.termsOfService?.modalAcceptance && ( + + )} diff --git a/config/reset-terms.js b/config/reset-terms.js new file mode 100644 index 000000000..5dd621081 --- /dev/null +++ b/config/reset-terms.js @@ -0,0 +1,44 @@ +const path = require('path'); +require('module-alias')({ base: path.resolve(__dirname, '..', 'api') }); +const User = require('~/models/User'); +const connect = require('./connect'); +const { askQuestion, silentExit } = require('./helpers'); + +(async () => { + await connect(); + + console.purple('--------------------------'); + console.purple('Reset terms acceptance'); + console.purple('--------------------------'); + + console.yellow('This will reset the terms acceptance for all users.'); + const confirm = await askQuestion('Are you sure you want to proceed? (y/n): '); + + if (confirm.toLowerCase() !== 'y') { + console.yellow('Operation cancelled.'); + silentExit(0); + } + + try { + const result = await User.updateMany({}, { $set: { termsAccepted: false } }); + console.green(`Updated ${result.modifiedCount} user(s).`); + } catch (error) { + console.red('Error resetting terms acceptance:', error); + silentExit(1); + } + + silentExit(0); +})(); + +process.on('uncaughtException', (err) => { + if (!err.message.includes('fetch failed')) { + console.error('There was an uncaught error:'); + console.error(err); + } + + if (err.message.includes('fetch failed')) { + return; + } else { + process.exit(1); + } +}); diff --git a/librechat.example.yaml b/librechat.example.yaml index c5b69bf9b..4b2419596 100644 --- a/librechat.example.yaml +++ b/librechat.example.yaml @@ -18,7 +18,45 @@ interface: termsOfService: externalUrl: 'https://librechat.ai/tos' openNewTab: true + modalAcceptance: true + modalTitle: "Terms of Service for LibreChat" + modalContent: | + # Terms and Conditions for LibreChat + *Effective Date: February 18, 2024* + + Welcome to LibreChat, the informational website for the open-source AI chat platform, available at https://librechat.ai. These Terms of Service ("Terms") govern your use of our website and the services we offer. By accessing or using the Website, you agree to be bound by these Terms and our Privacy Policy, accessible at https://librechat.ai//privacy. + + ## 1. Ownership + + Upon purchasing a package from LibreChat, you are granted the right to download and use the code for accessing an admin panel for LibreChat. While you own the downloaded code, you are expressly prohibited from reselling, redistributing, or otherwise transferring the code to third parties without explicit permission from LibreChat. + + ## 2. User Data + + We collect personal data, such as your name, email address, and payment information, as described in our Privacy Policy. This information is collected to provide and improve our services, process transactions, and communicate with you. + + ## 3. Non-Personal Data Collection + + The Website uses cookies to enhance user experience, analyze site usage, and facilitate certain functionalities. By using the Website, you consent to the use of cookies in accordance with our Privacy Policy. + + ## 4. Use of the Website + + You agree to use the Website only for lawful purposes and in a manner that does not infringe the rights of, restrict, or inhibit anyone else's use and enjoyment of the Website. Prohibited behavior includes harassing or causing distress or inconvenience to any person, transmitting obscene or offensive content, or disrupting the normal flow of dialogue within the Website. + + ## 5. Governing Law + + These Terms shall be governed by and construed in accordance with the laws of the United States, without giving effect to any principles of conflicts of law. + + ## 6. Changes to the Terms + + We reserve the right to modify these Terms at any time. We will notify users of any changes by email. Your continued use of the Website after such changes have been notified will constitute your consent to such changes. + + ## 7. Contact Information + + If you have any questions about these Terms, please contact us at contact@librechat.ai. + + By using the Website, you acknowledge that you have read these Terms of Service and agree to be bound by them. + # Example Registration Object Structure (optional) registration: socialLogins: ['github', 'google', 'discord', 'openid', 'facebook'] diff --git a/package.json b/package.json index d1f3c8c20..f67533df8 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "b:test:client": "cd client && bun run b:test", "b:test:api": "cd api && bun run b:test", "b:balance": "bun config/add-balance.js", - "b:list-balances": "bun config/list-balances.js" + "b:list-balances": "bun config/list-balances.js", + "reset-terms": "node config/reset-terms.js" }, "repository": { "type": "git", diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 178d15e04..3a9409ee1 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -202,3 +202,6 @@ export const conversationTagsList = (pageNumber: string, sort?: string, order?: export const addTagToConversation = (conversationId: string) => `${conversationTags()}/convo/${conversationId}`; + +export const userTerms = () => '/api/user/terms'; +export const acceptUserTerms = () => '/api/user/terms/accept'; diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 66f358b8f..ae9f8561a 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -407,6 +407,9 @@ export const configSchema = z.object({ .object({ externalUrl: z.string().optional(), openNewTab: z.boolean().optional(), + modalAcceptance: z.boolean().optional(), + modalTitle: z.string().optional(), + modalContent: z.string().or(z.array(z.string())).optional(), }) .optional(), endpointsMenu: z.boolean().optional(), diff --git a/packages/data-provider/src/data-service.ts b/packages/data-provider/src/data-service.ts index b7ee8e5a5..1f6007d28 100644 --- a/packages/data-provider/src/data-service.ts +++ b/packages/data-provider/src/data-service.ts @@ -581,3 +581,11 @@ export function rebuildConversationTags(): Promise export function healthCheck(): Promise { return request.get(endpoints.health()); } + +export function getUserTerms(): Promise { + return request.get(endpoints.userTerms()); +} + +export function acceptTerms(): Promise { + return request.post(endpoints.acceptUserTerms()); +} diff --git a/packages/data-provider/src/keys.ts b/packages/data-provider/src/keys.ts index a446b84d2..cfe9c44a4 100644 --- a/packages/data-provider/src/keys.ts +++ b/packages/data-provider/src/keys.ts @@ -38,6 +38,7 @@ export enum QueryKeys { roles = 'roles', conversationTags = 'conversationTags', health = 'health', + userTerms = 'userTerms', } export enum MutationKeys { diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 05bfb5735..bd49dd913 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -299,6 +299,9 @@ export type TInterfaceConfig = { termsOfService?: { externalUrl?: string; openNewTab?: boolean; + modalAcceptance?: boolean; + modalTitle?: string; + modalContent?: string; }; endpointsMenu: boolean; modelSelect: boolean; @@ -495,3 +498,11 @@ export type TGetRandomPromptsRequest = { }; export type TCustomConfigSpeechResponse = { [key: string]: string }; + +export type TUserTermsResponse = { + termsAccepted: boolean; +}; + +export type TAcceptTermsResponse = { + success: boolean; +};