mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
⚖️ feat: Terms and Conditions Dialog (#3712)
* 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)
This commit is contained in:
parent
79f9cd5a4d
commit
618be4bf2b
35 changed files with 393 additions and 8 deletions
|
@ -122,7 +122,12 @@ const userSchema = mongoose.Schema(
|
|||
type: Date,
|
||||
expires: 604800, // 7 days in seconds
|
||||
},
|
||||
termsAccepted: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
{ timestamps: true },
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
92
client/src/components/ui/TermsAndConditionsModal.tsx
Normal file
92
client/src/components/ui/TermsAndConditionsModal.tsx
Normal file
|
@ -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 (
|
||||
<OGDialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTemplate
|
||||
title={title ?? localize('com_ui_terms_and_conditions')}
|
||||
className="w-11/12 max-w-3xl sm:w-3/4 md:w-1/2 lg:w-2/5"
|
||||
showCloseButton={false}
|
||||
showCancelButton={false}
|
||||
main={
|
||||
<div className="max-h-[60vh] overflow-y-auto p-4">
|
||||
<div className="prose dark:prose-invert w-full max-w-none !text-black dark:!text-white">
|
||||
{modalContent ? (
|
||||
<Markdown content={modalContent} isLatestMessage={false} />
|
||||
) : (
|
||||
<p>{localize('com_ui_no_terms_content')}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
buttons={
|
||||
<>
|
||||
<button
|
||||
onClick={handleDecline}
|
||||
className="border-border-none bg-surface-500 dark:hover:bg-surface-600 inline-flex h-10 items-center justify-center rounded-lg px-4 py-2 text-sm text-white hover:bg-gray-600"
|
||||
>
|
||||
{localize('com_ui_decline')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="border-border-none bg-surface-500 inline-flex h-10 items-center justify-center rounded-lg px-4 py-2 text-sm text-white hover:bg-green-600 dark:hover:bg-green-600"
|
||||
>
|
||||
{localize('com_ui_accept')}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</OGDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsAndConditionsModal;
|
|
@ -1128,3 +1128,19 @@ export const useResendVerificationEmail = (
|
|||
...(options || {}),
|
||||
});
|
||||
};
|
||||
|
||||
export const useAcceptTermsMutation = (
|
||||
options?: t.AcceptTermsMutationOptions,
|
||||
): UseMutationResult<t.TAcceptTermsResponse, unknown, void, unknown> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(() => dataService.acceptTerms(), {
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.setQueryData<t.TUserTermsResponse>([QueryKeys.userTerms], {
|
||||
termsAccepted: true,
|
||||
});
|
||||
options?.onSuccess?.(data, variables, context);
|
||||
},
|
||||
onError: options?.onError,
|
||||
onMutate: options?.onMutate,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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<t.TUserTermsResponse>,
|
||||
): QueryObserverResult<t.TUserTermsResponse> => {
|
||||
return useQuery<t.TUserTermsResponse>([QueryKeys.userTerms], () => dataService.getUserTerms(), {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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: 'عدّل رسالتك أو أعد إنشاءها.',
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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: 'החשבון שלך נחסם באופן זמני עקב הפרות של השירות שלנו.',
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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: '가입하기',
|
||||
|
|
|
@ -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.',
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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<><78>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:
|
||||
|
|
|
@ -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: '根据我们的服务规则,您的帐号被暂时禁用。',
|
||||
|
|
|
@ -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: '由於違反我們的服務條款,您的帳號已被暫時停用。',
|
||||
|
|
|
@ -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<boolean>(() => {
|
||||
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() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{config?.interface?.termsOfService?.modalAcceptance && (
|
||||
<TermsAndConditionsModal
|
||||
open={showTerms}
|
||||
onOpenChange={setShowTerms}
|
||||
onAccept={handleAcceptTerms}
|
||||
onDecline={handleDeclineTerms}
|
||||
title={config.interface.termsOfService.modalTitle}
|
||||
modalContent={config.interface.termsOfService.modalContent}
|
||||
/>
|
||||
)}
|
||||
</AssistantsMapContext.Provider>
|
||||
</FileMapContext.Provider>
|
||||
</SearchContext.Provider>
|
||||
|
|
44
config/reset-terms.js
Normal file
44
config/reset-terms.js
Normal file
|
@ -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);
|
||||
}
|
||||
});
|
|
@ -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']
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -581,3 +581,11 @@ export function rebuildConversationTags(): Promise<t.TConversationTagsResponse>
|
|||
export function healthCheck(): Promise<string> {
|
||||
return request.get(endpoints.health());
|
||||
}
|
||||
|
||||
export function getUserTerms(): Promise<t.TUserTermsResponse> {
|
||||
return request.get(endpoints.userTerms());
|
||||
}
|
||||
|
||||
export function acceptTerms(): Promise<t.TAcceptTermsResponse> {
|
||||
return request.post(endpoints.acceptUserTerms());
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ export enum QueryKeys {
|
|||
roles = 'roles',
|
||||
conversationTags = 'conversationTags',
|
||||
health = 'health',
|
||||
userTerms = 'userTerms',
|
||||
}
|
||||
|
||||
export enum MutationKeys {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue