mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🚫👤feat: delete user from UI (#1526)
* initial commit * fix: UserController bugs; fix: lint errors * fix: delete files * language support * style(DeleteAccount): update to the latest style * style: fix after merge main * chore: Add canDeleteAccount middleware for user deletion endpoint * chore: renamed to ALLOW_ACCOUNT_DELETION * fix(canDeleteAccount): use uppercase admin role * chore: imports order * chore: Enable account deletion by default if omitted/commented out * chore: Add logging for user account deletion * chore: Bump data-provider package version to 0.6.6 * chore: Import Transaction model in UserController * chore: Update CONFIG_VERSION to 1.1.4 * chore: Update user account deletion logging * chore: Refactor user account deletion logic --------- Co-authored-by: Berry-13 <root@Berry> Co-authored-by: Danny Avila <messagedaniel@protonmail.com> Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
f69b317171
commit
a7f5b57272
19 changed files with 348 additions and 17 deletions
|
|
@ -319,6 +319,7 @@ ALLOW_EMAIL_LOGIN=true
|
||||||
ALLOW_REGISTRATION=true
|
ALLOW_REGISTRATION=true
|
||||||
ALLOW_SOCIAL_LOGIN=false
|
ALLOW_SOCIAL_LOGIN=false
|
||||||
ALLOW_SOCIAL_REGISTRATION=false
|
ALLOW_SOCIAL_REGISTRATION=false
|
||||||
|
# ALLOW_ACCOUNT_DELETION=true # note: enabled by default if omitted/commented out
|
||||||
|
|
||||||
SESSION_EXPIRY=1000 * 60 * 15
|
SESSION_EXPIRY=1000 * 60 * 15
|
||||||
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
REFRESH_TOKEN_EXPIRY=(1000 * 60 * 60 * 24) * 7
|
||||||
|
|
|
||||||
|
|
@ -97,8 +97,12 @@ const deleteFileByFilter = async (filter) => {
|
||||||
* @param {Array<string>} file_ids - The unique identifiers of the files to delete.
|
* @param {Array<string>} file_ids - The unique identifiers of the files to delete.
|
||||||
* @returns {Promise<Object>} A promise that resolves to the result of the deletion operation.
|
* @returns {Promise<Object>} A promise that resolves to the result of the deletion operation.
|
||||||
*/
|
*/
|
||||||
const deleteFiles = async (file_ids) => {
|
const deleteFiles = async (file_ids, user) => {
|
||||||
return await File.deleteMany({ file_id: { $in: file_ids } });
|
let deleteQuery = { file_id: { $in: file_ids } };
|
||||||
|
if (user) {
|
||||||
|
deleteQuery = { user: user };
|
||||||
|
}
|
||||||
|
return await File.deleteMany(deleteQuery);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
const { updateUserPluginsService } = require('~/server/services/UserService');
|
const {
|
||||||
|
User,
|
||||||
|
Session,
|
||||||
|
Balance,
|
||||||
|
deleteFiles,
|
||||||
|
deleteConvos,
|
||||||
|
deletePresets,
|
||||||
|
deleteMessages,
|
||||||
|
} = require('~/models');
|
||||||
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
|
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
|
||||||
|
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
|
||||||
|
const { Transaction } = require('~/models/Transaction');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const getUserController = async (req, res) => {
|
const getUserController = async (req, res) => {
|
||||||
|
|
@ -53,7 +63,30 @@ const updateUserPluginsController = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteUserController = async (req, res) => {
|
||||||
|
const { user } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteMessages({ user: user.id }); // delete user messages
|
||||||
|
await Session.deleteMany({ user: user.id }); // delete user sessions
|
||||||
|
await Transaction.deleteMany({ user: user.id }); // delete user transactions
|
||||||
|
await deleteUserKey({ userId: user.id, all: true }); // delete user keys
|
||||||
|
await Balance.deleteMany({ user: user._id }); // delete user balances
|
||||||
|
await deletePresets(user.id); // delete user presets
|
||||||
|
await deleteConvos(user.id); // delete user convos
|
||||||
|
await deleteUserPluginAuth(user.id, null, true); // delete user plugin auth
|
||||||
|
await User.deleteOne({ _id: user.id }); // delete user
|
||||||
|
await deleteFiles(null, user.id); // delete user files
|
||||||
|
logger.info(`User deleted account. Email: ${user.email} ID: ${user.id}`);
|
||||||
|
res.status(200).send({ message: 'User deleted' });
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[deleteUserController]', err);
|
||||||
|
res.status(500).send({ message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getUserController,
|
getUserController,
|
||||||
updateUserPluginsController,
|
updateUserPluginsController,
|
||||||
|
deleteUserController,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
27
api/server/middleware/canDeleteAccount.js
Normal file
27
api/server/middleware/canDeleteAccount.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
const { isEnabled } = require('~/server/utils');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user can delete their account
|
||||||
|
*
|
||||||
|
* @async
|
||||||
|
* @function
|
||||||
|
* @param {Object} req - Express request object
|
||||||
|
* @param {Object} res - Express response object
|
||||||
|
* @param {Function} next - Next middleware function
|
||||||
|
*
|
||||||
|
* @returns {Promise<function|Object>} - Returns a Promise which when resolved calls next middleware if the user can delete their account
|
||||||
|
*/
|
||||||
|
|
||||||
|
const canDeleteAccount = async (req, res, next = () => {}) => {
|
||||||
|
const { user } = req;
|
||||||
|
const { ALLOW_ACCOUNT_DELETION = true } = process.env;
|
||||||
|
if (user?.role === 'ADMIN' || isEnabled(ALLOW_ACCOUNT_DELETION)) {
|
||||||
|
return next();
|
||||||
|
} else {
|
||||||
|
logger.error(`[User] [Delete Account] [User cannot delete account] [User: ${user?.id}]`);
|
||||||
|
return res.status(403).send({ message: 'You do not have permission to delete this account' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = canDeleteAccount;
|
||||||
|
|
@ -20,6 +20,7 @@ const validateImageRequest = require('./validateImageRequest');
|
||||||
const moderateText = require('./moderateText');
|
const moderateText = require('./moderateText');
|
||||||
const noIndex = require('./noIndex');
|
const noIndex = require('./noIndex');
|
||||||
const importLimiters = require('./importLimiters');
|
const importLimiters = require('./importLimiters');
|
||||||
|
const canDeleteAccount = require('./canDeleteAccount');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...uploadLimiters,
|
...uploadLimiters,
|
||||||
|
|
@ -44,4 +45,5 @@ module.exports = {
|
||||||
noIndex,
|
noIndex,
|
||||||
...importLimiters,
|
...importLimiters,
|
||||||
checkDomainAllowed,
|
checkDomainAllowed,
|
||||||
|
canDeleteAccount,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const requireJwtAuth = require('../middleware/requireJwtAuth');
|
const requireJwtAuth = require('../middleware/requireJwtAuth');
|
||||||
const { getUserController, updateUserPluginsController } = require('../controllers/UserController');
|
const canDeleteAccount = require('../middleware/canDeleteAccount');
|
||||||
|
const {
|
||||||
|
getUserController,
|
||||||
|
updateUserPluginsController,
|
||||||
|
deleteUserController,
|
||||||
|
} = require('../controllers/UserController');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get('/', requireJwtAuth, getUserController);
|
router.get('/', requireJwtAuth, getUserController);
|
||||||
router.post('/plugins', requireJwtAuth, updateUserPluginsController);
|
router.post('/plugins', requireJwtAuth, updateUserPluginsController);
|
||||||
|
router.delete('/delete', requireJwtAuth, canDeleteAccount, deleteUserController);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,17 @@ const updateUserPluginAuth = async (userId, authField, pluginKey, value) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUserPluginAuth = async (userId, authField) => {
|
const deleteUserPluginAuth = async (userId, authField, all = false) => {
|
||||||
|
if (all) {
|
||||||
|
try {
|
||||||
|
const response = await PluginAuth.deleteMany({ userId });
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[deleteUserPluginAuth]', err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await PluginAuth.deleteOne({ userId, authField });
|
return await PluginAuth.deleteOne({ userId, authField });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) =>
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
if (isPressed) {setIsPressed(false);}
|
if (isPressed) {
|
||||||
|
setIsPressed(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = () => {
|
const handleMouseDown = () => {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ const ClearConvos = ({ open, onOpenChange }) => {
|
||||||
// Clear all conversations
|
// Clear all conversations
|
||||||
const clearConvos = () => {
|
const clearConvos = () => {
|
||||||
if (confirmClear) {
|
if (confirmClear) {
|
||||||
console.log('Clearing conversations...');
|
|
||||||
clearConvosMutation.mutate(
|
clearConvosMutation.mutate(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import * as Tabs from '@radix-ui/react-tabs';
|
import * as Tabs from '@radix-ui/react-tabs';
|
||||||
import { SettingsTabValues } from 'librechat-data-provider';
|
import { SettingsTabValues } from 'librechat-data-provider';
|
||||||
|
import DeleteAccount from './DeleteAccount';
|
||||||
import { Switch } from '~/components/ui';
|
import { Switch } from '~/components/ui';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
|
|
@ -28,6 +29,9 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
|
||||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||||
<Avatar />
|
<Avatar />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||||
|
<DeleteAccount />
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div> {localize('com_nav_user_name_display')} </div>
|
<div> {localize('com_nav_user_name_display')} </div>
|
||||||
<Switch
|
<Switch
|
||||||
|
|
@ -39,7 +43,6 @@ function Account({ onCheckedChange }: { onCheckedChange?: (value: boolean) => vo
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600"></div>
|
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx
Normal file
174
client/src/components/Nav/SettingsTabs/Account/DeleteAccount.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogButton,
|
||||||
|
Input,
|
||||||
|
} from '~/components/ui';
|
||||||
|
import { cn, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||||
|
import { useDeleteUserMutation } from '~/data-provider';
|
||||||
|
import { Spinner, LockIcon } from '~/components/svg';
|
||||||
|
import { useAuthContext } from '~/hooks/AuthContext';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
const DeleteAccount = ({ disabled = false }: { title?: string; disabled?: boolean }) => {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { user, logout } = useAuthContext();
|
||||||
|
const { mutate: deleteUser, isLoading: isDeleting } = useDeleteUserMutation({
|
||||||
|
onSuccess: () => logout(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isDialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||||
|
const [deleteInput, setDeleteInput] = useState('');
|
||||||
|
const [emailInput, setEmailInput] = useState('');
|
||||||
|
const [isLocked, setIsLocked] = useState(true);
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
setDialogOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDeleteUser = () => {
|
||||||
|
if (!isLocked) {
|
||||||
|
deleteUser(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = useCallback(
|
||||||
|
(newEmailInput: string, newDeleteInput: string) => {
|
||||||
|
const isEmailCorrect =
|
||||||
|
newEmailInput.trim().toLowerCase() === user?.email?.trim().toLowerCase();
|
||||||
|
const isDeleteInputCorrect = newDeleteInput === 'DELETE';
|
||||||
|
setIsLocked(!(isEmailCorrect && isDeleteInputCorrect));
|
||||||
|
},
|
||||||
|
[user?.email],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span>{localize('com_nav_delete_account')}</span>
|
||||||
|
<label>
|
||||||
|
<DialogButton
|
||||||
|
id={'delete-user-account'}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
className={cn(
|
||||||
|
'btn btn-danger relative border-none bg-red-700 text-white hover:bg-red-800 dark:hover:bg-red-800',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{localize('com_ui_delete')}
|
||||||
|
</DialogButton>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Dialog open={isDialogOpen} onOpenChange={() => setDialogOpen(false)}>
|
||||||
|
<DialogContent
|
||||||
|
className={cn('shadow-2xl md:h-[500px] md:w-[450px]')}
|
||||||
|
style={{ borderRadius: '12px', padding: '20px' }}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-lg font-medium leading-6">
|
||||||
|
{localize('com_nav_delete_account_confirm')}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="mb-20 text-sm text-black dark:text-white">
|
||||||
|
<ul>
|
||||||
|
<li>{localize('com_nav_delete_warning')}</li>
|
||||||
|
<li>{localize('com_nav_delete_data_info')}</li>
|
||||||
|
<li>{localize('com_nav_delete_help_center')}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="flex-col items-center justify-center">
|
||||||
|
<div className="mb-4">
|
||||||
|
{renderInput(
|
||||||
|
localize('com_nav_delete_account_email_placeholder'),
|
||||||
|
'email-confirm-input',
|
||||||
|
user?.email || '',
|
||||||
|
(e) => {
|
||||||
|
setEmailInput(e.target.value);
|
||||||
|
handleInputChange(e.target.value, deleteInput);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
{renderInput(
|
||||||
|
localize('com_nav_delete_account_confirm_placeholder'),
|
||||||
|
'delete-confirm-input',
|
||||||
|
'',
|
||||||
|
(e) => {
|
||||||
|
setDeleteInput(e.target.value);
|
||||||
|
handleInputChange(emailInput, e.target.value);
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{renderDeleteButton(handleDeleteUser, isDeleting, isLocked, localize)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderInput = (
|
||||||
|
label: string,
|
||||||
|
id: string,
|
||||||
|
value: string,
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
|
||||||
|
) => (
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="mb-1 block text-sm font-medium text-black dark:text-white">{label}</label>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={value}
|
||||||
|
className={cn(
|
||||||
|
defaultTextProps,
|
||||||
|
'h-10 max-h-10 w-full max-w-full rounded-md bg-white px-3 py-2',
|
||||||
|
removeFocusOutlines,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderDeleteButton = (
|
||||||
|
handleDeleteUser: () => void,
|
||||||
|
isDeleting: boolean,
|
||||||
|
isLocked: boolean,
|
||||||
|
localize: (key: string) => string,
|
||||||
|
) => (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'mt-4 flex w-full items-center justify-center rounded-lg px-4 py-2 transition-colors duration-200',
|
||||||
|
isLocked
|
||||||
|
? 'cursor-not-allowed bg-gray-200 text-gray-300 dark:bg-gray-500 dark:text-gray-600'
|
||||||
|
: isDeleting
|
||||||
|
? 'cursor-not-allowed bg-gray-100 text-gray-700 dark:bg-gray-400 dark:text-gray-700'
|
||||||
|
: 'bg-red-700 text-white hover:bg-red-800 ',
|
||||||
|
)}
|
||||||
|
onClick={handleDeleteUser}
|
||||||
|
disabled={isDeleting || isLocked}
|
||||||
|
>
|
||||||
|
{isDeleting ? (
|
||||||
|
<div className="flex h-6 justify-center">
|
||||||
|
<Spinner className="icon-sm m-auto" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{isLocked ? (
|
||||||
|
<>
|
||||||
|
<LockIcon />
|
||||||
|
<span className="ml-2">{localize('com_ui_locked')}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LockIcon />
|
||||||
|
<span className="ml-2">{localize('com_nav_delete_account_button')}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default DeleteAccount;
|
||||||
19
client/src/components/svg/LockIcon.tsx
Normal file
19
client/src/components/svg/LockIcon.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
export default function LockIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className="lucide lucide-lock"
|
||||||
|
>
|
||||||
|
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,7 @@ export { default as VolumeIcon } from './VolumeIcon';
|
||||||
export { default as VolumeMuteIcon } from './VolumeMuteIcon';
|
export { default as VolumeMuteIcon } from './VolumeMuteIcon';
|
||||||
export { default as SendMessageIcon } from './SendMessageIcon';
|
export { default as SendMessageIcon } from './SendMessageIcon';
|
||||||
export { default as UserIcon } from './UserIcon';
|
export { default as UserIcon } from './UserIcon';
|
||||||
|
export { default as LockIcon } from './LockIcon';
|
||||||
export { default as NewChatIcon } from './NewChatIcon';
|
export { default as NewChatIcon } from './NewChatIcon';
|
||||||
export { default as ExperimentIcon } from './ExperimentIcon';
|
export { default as ExperimentIcon } from './ExperimentIcon';
|
||||||
export { default as GoogleIconChat } from './GoogleIconChat';
|
export { default as GoogleIconChat } from './GoogleIconChat';
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,22 @@ import {
|
||||||
LocalStorageKeys,
|
LocalStorageKeys,
|
||||||
defaultAssistantsVersion,
|
defaultAssistantsVersion,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
|
||||||
import type { UseMutationResult } from '@tanstack/react-query';
|
import type { UseMutationResult } from '@tanstack/react-query';
|
||||||
import type t from 'librechat-data-provider';
|
import type t from 'librechat-data-provider';
|
||||||
import {
|
import {
|
||||||
|
addSharedLink,
|
||||||
addConversation,
|
addConversation,
|
||||||
|
deleteSharedLink,
|
||||||
|
updateConvoFields,
|
||||||
updateConversation,
|
updateConversation,
|
||||||
deleteConversation,
|
deleteConversation,
|
||||||
updateConvoFields,
|
|
||||||
deleteSharedLink,
|
|
||||||
addSharedLink,
|
|
||||||
} from '~/utils';
|
} from '~/utils';
|
||||||
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import store from '~/store';
|
|
||||||
import { normalizeData } from '~/utils/collection';
|
|
||||||
import { useConversationsInfiniteQuery, useSharedLinksInfiniteQuery } from './queries';
|
import { useConversationsInfiniteQuery, useSharedLinksInfiniteQuery } from './queries';
|
||||||
|
import { normalizeData } from '~/utils/collection';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
/** Conversations */
|
/** Conversations */
|
||||||
export const useGenTitleMutation = (): UseMutationResult<
|
export const useGenTitleMutation = (): UseMutationResult<
|
||||||
|
|
@ -609,6 +609,30 @@ export const useUploadAvatarMutation = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDeleteUserMutation = (
|
||||||
|
options?: t.MutationOptions<unknown, undefined>,
|
||||||
|
): UseMutationResult<unknown, unknown, undefined, unknown> => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const setDefaultPreset = useSetRecoilState(store.defaultPreset);
|
||||||
|
return useMutation([MutationKeys.deleteUser], {
|
||||||
|
mutationFn: () => dataService.deleteUser(),
|
||||||
|
|
||||||
|
...(options || {}),
|
||||||
|
onSuccess: (...args) => {
|
||||||
|
options?.onSuccess?.(...args);
|
||||||
|
},
|
||||||
|
onMutate: (...args) => {
|
||||||
|
setDefaultPreset(null);
|
||||||
|
queryClient.removeQueries();
|
||||||
|
localStorage.removeItem(LocalStorageKeys.LAST_CONVO_SETUP);
|
||||||
|
localStorage.removeItem(LocalStorageKeys.LAST_MODEL);
|
||||||
|
localStorage.removeItem(LocalStorageKeys.LAST_TOOLS);
|
||||||
|
localStorage.removeItem(LocalStorageKeys.FILES_TO_DELETE);
|
||||||
|
options?.onMutate?.(...args);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/* Speech to text */
|
/* Speech to text */
|
||||||
export const useSpeechToTextMutation = (
|
export const useSpeechToTextMutation = (
|
||||||
options?: t.SpeechToTextOptions,
|
options?: t.SpeechToTextOptions,
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ 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_locked: 'Locked',
|
||||||
com_ui_upload_delay:
|
com_ui_upload_delay:
|
||||||
'Uploading "{0}" is taking more time than anticipated. Please wait while the file finishes indexing for retrieval.',
|
'Uploading "{0}" is taking more time than anticipated. Please wait while the file finishes indexing for retrieval.',
|
||||||
com_ui_privacy_policy: 'Privacy policy',
|
com_ui_privacy_policy: 'Privacy policy',
|
||||||
|
|
@ -546,6 +547,14 @@ export default {
|
||||||
com_nav_help_faq: 'Help & FAQ',
|
com_nav_help_faq: 'Help & FAQ',
|
||||||
com_nav_settings: 'Settings',
|
com_nav_settings: 'Settings',
|
||||||
com_nav_search_placeholder: 'Search messages',
|
com_nav_search_placeholder: 'Search messages',
|
||||||
|
com_nav_delete_account: 'Delete account',
|
||||||
|
com_nav_delete_account_confirm: 'Delete account - are you sure?',
|
||||||
|
com_nav_delete_account_button: 'Permanently delete my account',
|
||||||
|
com_nav_delete_account_email_placeholder: 'Please enter your account email',
|
||||||
|
com_nav_delete_account_confirm_placeholder: 'To proceed, type "DELETE" in the input field below',
|
||||||
|
com_nav_delete_warning: 'WARNING: This will permanently delete your account.',
|
||||||
|
com_nav_delete_data_info: 'All your data will be deleted.',
|
||||||
|
com_nav_delete_help_center: 'For more information, please visit our Help Center.',
|
||||||
com_nav_conversation_mode: 'Conversation Mode',
|
com_nav_conversation_mode: 'Conversation Mode',
|
||||||
com_nav_auto_send_text: 'Auto send text (after 3 sec)',
|
com_nav_auto_send_text: 'Auto send text (after 3 sec)',
|
||||||
com_nav_auto_transcribe_audio: 'Auto transcribe audio',
|
com_nav_auto_transcribe_audio: 'Auto transcribe audio',
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ export default {
|
||||||
com_ui_preview: 'Anteprima',
|
com_ui_preview: 'Anteprima',
|
||||||
com_ui_upload: 'Carica',
|
com_ui_upload: 'Carica',
|
||||||
com_ui_connect: 'Connetti',
|
com_ui_connect: 'Connetti',
|
||||||
|
com_ui_locked: 'Bloccato',
|
||||||
com_ui_upload_delay:
|
com_ui_upload_delay:
|
||||||
'Il caricamento di "{0}" sta richiedendo più tempo del previsto. Attendi il completamento dell\'indicizzazione per il recupero.',
|
'Il caricamento di "{0}" sta richiedendo più tempo del previsto. Attendi il completamento dell\'indicizzazione per il recupero.',
|
||||||
com_ui_privacy_policy: 'Informativa sulla privacy',
|
com_ui_privacy_policy: 'Informativa sulla privacy',
|
||||||
|
|
@ -520,8 +521,17 @@ export default {
|
||||||
com_nav_help_faq: 'Guida e FAQ',
|
com_nav_help_faq: 'Guida e FAQ',
|
||||||
com_nav_settings: 'Impostazioni',
|
com_nav_settings: 'Impostazioni',
|
||||||
com_nav_search_placeholder: 'Cerca messaggi',
|
com_nav_search_placeholder: 'Cerca messaggi',
|
||||||
com_nav_setting_general: 'Generali',
|
com_nav_delete_account: 'Elimina account',
|
||||||
com_nav_setting_beta: 'Funzionalità beta',
|
com_nav_delete_account_confirm: 'Sei sicuro di voler eliminare il tuo account?',
|
||||||
|
com_nav_delete_account_button: 'Elimina permanentemente il mio account',
|
||||||
|
com_nav_delete_account_email_placeholder: 'Inserisci la tua email',
|
||||||
|
com_nav_delete_account_confirm_placeholder:
|
||||||
|
'Per procedere, digita "DELETE" nel campo di input sottostante',
|
||||||
|
com_dialog_delete_warning: 'ATTENZIONE: Questo cancellerà permanentemente il tuo account.',
|
||||||
|
com_dialog_delete_data_info: 'Tutti i tuoi dati verranno eliminati.',
|
||||||
|
com_dialog_delete_help_center: 'Per più informazioni, visita il nostro centro assistenza.',
|
||||||
|
com_nav_setting_general: 'Generale',
|
||||||
|
com_nav_setting_beta: 'Funzioni Beta',
|
||||||
com_nav_setting_data: 'Controlli dati',
|
com_nav_setting_data: 'Controlli dati',
|
||||||
com_nav_setting_speech: 'Voce',
|
com_nav_setting_speech: 'Voce',
|
||||||
com_nav_setting_account: 'Account',
|
com_nav_setting_account: 'Account',
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ export const balance = () => '/api/balance';
|
||||||
|
|
||||||
export const userPlugins = () => '/api/user/plugins';
|
export const userPlugins = () => '/api/user/plugins';
|
||||||
|
|
||||||
|
export const deleteUser = () => '/api/user/delete';
|
||||||
|
|
||||||
export const messages = (conversationId: string, messageId?: string) =>
|
export const messages = (conversationId: string, messageId?: string) =>
|
||||||
`/api/messages/${conversationId}${messageId ? `/${messageId}` : ''}`;
|
`/api/messages/${conversationId}${messageId ? `/${messageId}` : ''}`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ export function revokeAllUserKeys(): Promise<unknown> {
|
||||||
return request.delete(endpoints.revokeAllUserKeys());
|
return request.delete(endpoints.revokeAllUserKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteUser(): Promise<s.TPreset> {
|
||||||
|
return request.delete(endpoints.deleteUser());
|
||||||
|
}
|
||||||
|
|
||||||
export function getMessagesByConvoId(conversationId: string): Promise<s.TMessage[]> {
|
export function getMessagesByConvoId(conversationId: string): Promise<s.TMessage[]> {
|
||||||
if (conversationId === 'new') {
|
if (conversationId === 'new') {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,5 @@ export enum MutationKeys {
|
||||||
assistantAvatarUpload = 'assistantAvatarUpload',
|
assistantAvatarUpload = 'assistantAvatarUpload',
|
||||||
updateAction = 'updateAction',
|
updateAction = 'updateAction',
|
||||||
deleteAction = 'deleteAction',
|
deleteAction = 'deleteAction',
|
||||||
|
deleteUser = 'deleteUser',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue