diff --git a/api/models/Role.js b/api/models/Role.js index 9c160512b7d..ebf7e809a0b 100644 --- a/api/models/Role.js +++ b/api/models/Role.js @@ -8,6 +8,7 @@ const { promptPermissionsSchema, bookmarkPermissionsSchema, multiConvoPermissionsSchema, + deleteConvoPermissionsSchema, } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); const Role = require('~/models/schema/roleSchema'); @@ -77,6 +78,7 @@ const permissionSchemas = { [PermissionTypes.PROMPTS]: promptPermissionsSchema, [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema, + [PermissionTypes.DELETE_CONVO]: deleteConvoPermissionsSchema, }; /** diff --git a/api/models/schema/roleSchema.js b/api/models/schema/roleSchema.js index 36e9d3f7b6e..717a1860baa 100644 --- a/api/models/schema/roleSchema.js +++ b/api/models/schema/roleSchema.js @@ -48,6 +48,12 @@ const roleSchema = new mongoose.Schema({ default: true, }, }, + [PermissionTypes.DELETE_CONVO]: { + [Permissions.USE]: { + type: Boolean, + default: true, + }, + }, }); const Role = mongoose.model('Role', roleSchema); diff --git a/api/server/routes/convos.js b/api/server/routes/convos.js index 104b0616f81..8ca372bab7f 100644 --- a/api/server/routes/convos.js +++ b/api/server/routes/convos.js @@ -1,13 +1,13 @@ const multer = require('multer'); const express = require('express'); -const { CacheKeys } = require('librechat-data-provider'); +const { CacheKeys, PermissionTypes, Permissions } = require('librechat-data-provider'); const { initializeClient } = require('~/server/services/Endpoints/assistants'); const { getConvosByPage, deleteConvos, getConvo, saveConvo } = require('~/models/Conversation'); const { storage, importFileFilter } = require('~/server/routes/files/multer'); const requireJwtAuth = require('~/server/middleware/requireJwtAuth'); const { forkConversation } = require('~/server/utils/import/fork'); const { importConversations } = require('~/server/utils/import'); -const { createImportLimiters } = require('~/server/middleware'); +const { createImportLimiters, generateCheckAccess } = require('~/server/middleware'); const getLogStores = require('~/cache/getLogStores'); const { sleep } = require('~/server/utils'); const { logger } = require('~/config'); @@ -72,7 +72,9 @@ router.post('/gen_title', async (req, res) => { } }); -router.post('/clear', async (req, res) => { +const checkDeleteConvoAccess = generateCheckAccess(PermissionTypes.DELETE_CONVO, [Permissions.USE]); + +router.post('/clear', checkDeleteConvoAccess, async (req, res) => { let filter = {}; const { conversationId, source, thread_id } = req.body.arg; if (conversationId) { diff --git a/api/server/services/start/interface.js b/api/server/services/start/interface.js index bf31eb78b89..d60dcacfa22 100644 --- a/api/server/services/start/interface.js +++ b/api/server/services/start/interface.js @@ -32,17 +32,20 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol bookmarks: interfaceConfig?.bookmarks ?? defaults.bookmarks, prompts: interfaceConfig?.prompts ?? defaults.prompts, multiConvo: interfaceConfig?.multiConvo ?? defaults.multiConvo, + deleteConvo: interfaceConfig?.deleteConvo ?? defaults.deleteConvo, }); await updateAccessPermissions(roleName, { [PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts }, [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks }, [PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo }, + [PermissionTypes.DELETE_CONVO]: { [Permissions.USE]: loadedInterface.deleteConvo }, }); await updateAccessPermissions(SystemRoles.ADMIN, { [PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts }, [PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks }, [PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo }, + [PermissionTypes.DELETE_CONVO]: { [Permissions.USE]: loadedInterface.deleteConvo }, }); let i = 0; diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index 9b6f504b5e9..aa4fb2a8313 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -14,7 +14,7 @@ import { useToastContext } from '~/Providers'; import { ConvoOptions } from './ConvoOptions'; import { cn } from '~/utils'; import store from '~/store'; -import { useLocalize } from '~/hooks' +import { useLocalize } from '~/hooks'; type KeyEvent = KeyboardEvent; @@ -151,11 +151,23 @@ export default function Conversation({ aria-label={`${localize('com_ui_rename')} ${localize('com_ui_chat')}`} />
- -
diff --git a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx index 59c63f896ac..fbeea684191 100644 --- a/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx +++ b/client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx @@ -2,11 +2,12 @@ import { useState, useId } from 'react'; import * as Ariakit from '@ariakit/react'; import { Ellipsis, Share2, Archive, Pen, Trash } from 'lucide-react'; import { useGetStartupConfig } from 'librechat-data-provider/react-query'; -import { useLocalize, useArchiveHandler } from '~/hooks'; +import { useLocalize, useArchiveHandler, useHasAccess } from '~/hooks'; import { DropdownPopup } from '~/components/ui'; import DeleteButton from './DeleteButton'; import ShareButton from './ShareButton'; import { cn } from '~/utils'; +import { PermissionTypes, Permissions } from 'librechat-data-provider'; export default function ConvoOptions({ conversation, @@ -33,6 +34,11 @@ export default function ConvoOptions({ setShowDeleteDialog(true); }; + const hasAccessToDeleteConvo = useHasAccess({ + permissionType: PermissionTypes.DELETE_CONVO, + permission: Permissions.USE, + }); + const dropdownItems = [ { label: localize('com_ui_rename'), @@ -50,12 +56,15 @@ export default function ConvoOptions({ onClick: archiveHandler, icon: , }, - { + ]; + + if (hasAccessToDeleteConvo) { + dropdownItems.push({ label: localize('com_ui_delete'), onClick: deleteHandler, icon: , - }, - ]; + }); + } const menuId = useId(); diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index be0dfd3d8ef..e459fb8c8d9 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -465,6 +465,7 @@ export const configSchema = z.object({ parameters: z.boolean().optional(), sidePanel: z.boolean().optional(), multiConvo: z.boolean().optional(), + deleteConvo: z.boolean().optional(), bookmarks: z.boolean().optional(), presets: z.boolean().optional(), prompts: z.boolean().optional(), @@ -476,6 +477,7 @@ export const configSchema = z.object({ sidePanel: true, presets: true, multiConvo: true, + deleteConvo: true, bookmarks: true, prompts: true, }), diff --git a/packages/data-provider/src/roles.ts b/packages/data-provider/src/roles.ts index fca276b00e0..1185c8896aa 100644 --- a/packages/data-provider/src/roles.ts +++ b/packages/data-provider/src/roles.ts @@ -34,6 +34,10 @@ export enum PermissionTypes { * Type for Multi-Conversation Permissions */ MULTI_CONVO = 'MULTI_CONVO', + /** + * Type for Delete Conversation Permissions + */ + DELETE_CONVO = 'DELETE_CONVO', } /** @@ -68,12 +72,17 @@ export const multiConvoPermissionsSchema = z.object({ [Permissions.USE]: z.boolean().default(false), }); +export const deleteConvoPermissionsSchema = z.object({ + [Permissions.USE]: z.boolean().default(false), +}); + export const roleSchema = z.object({ name: z.string(), [PermissionTypes.PROMPTS]: promptPermissionsSchema, [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.AGENTS]: agentPermissionsSchema, [PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema, + [PermissionTypes.DELETE_CONVO]: deleteConvoPermissionsSchema, }); export type TRole = z.infer; @@ -103,6 +112,9 @@ const defaultRolesSchema = z.object({ [PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema.extend({ [Permissions.USE]: z.boolean().default(true), }), + [PermissionTypes.DELETE_CONVO]: deleteConvoPermissionsSchema.extend({ + [Permissions.USE]: z.boolean().default(true), + }), }), [SystemRoles.USER]: roleSchema.extend({ name: z.literal(SystemRoles.USER), @@ -110,6 +122,7 @@ const defaultRolesSchema = z.object({ [PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema, [PermissionTypes.AGENTS]: agentPermissionsSchema, [PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema, + [PermissionTypes.DELETE_CONVO]: multiConvoPermissionsSchema, }), }); @@ -120,6 +133,7 @@ export const roleDefaults = defaultRolesSchema.parse({ [PermissionTypes.BOOKMARKS]: {}, [PermissionTypes.AGENTS]: {}, [PermissionTypes.MULTI_CONVO]: {}, + [PermissionTypes.DELETE_CONVO]: {}, }, [SystemRoles.USER]: { name: SystemRoles.USER, @@ -127,5 +141,6 @@ export const roleDefaults = defaultRolesSchema.parse({ [PermissionTypes.BOOKMARKS]: {}, [PermissionTypes.AGENTS]: {}, [PermissionTypes.MULTI_CONVO]: {}, + [PermissionTypes.DELETE_CONVO]: {}, }, }); diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 41276c7557e..86174705677 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -319,6 +319,7 @@ export type TInterfaceConfig = { sidePanel: boolean; presets: boolean; multiConvo: boolean; + deleteConvo: boolean; bookmarks: boolean; prompts: boolean; };