mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🔒 feat: RBAC for Multi-Convo Feature (#3964)
* fix: remove duplicate keys in German language translations * wip: multi-convo role permissions * ci: Update loadDefaultInterface tests due to MULTI_CONVO * ci: update Role.spec.js with tests for MULTI_CONVO permission type * fix: Update ContentParts component to handle undefined content array * feat: render Multi-Convo based on UI permissions
This commit is contained in:
parent
d59b62174f
commit
748b41eda4
18 changed files with 302 additions and 56 deletions
|
|
@ -7,6 +7,7 @@ const {
|
||||||
agentPermissionsSchema,
|
agentPermissionsSchema,
|
||||||
promptPermissionsSchema,
|
promptPermissionsSchema,
|
||||||
bookmarkPermissionsSchema,
|
bookmarkPermissionsSchema,
|
||||||
|
multiConvoPermissionsSchema,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const Role = require('~/models/schema/roleSchema');
|
const Role = require('~/models/schema/roleSchema');
|
||||||
|
|
@ -75,6 +76,7 @@ const permissionSchemas = {
|
||||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,75 @@ describe('updateAccessPermissions', () => {
|
||||||
SHARED_GLOBAL: true,
|
SHARED_GLOBAL: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update MULTI_CONVO permissions', async () => {
|
||||||
|
await new Role({
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
USE: false,
|
||||||
|
},
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await updateAccessPermissions(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
USE: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
expect(updatedRole[PermissionTypes.MULTI_CONVO]).toEqual({
|
||||||
|
USE: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update MULTI_CONVO permissions along with other permission types', async () => {
|
||||||
|
await new Role({
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.PROMPTS]: {
|
||||||
|
CREATE: true,
|
||||||
|
USE: true,
|
||||||
|
SHARED_GLOBAL: false,
|
||||||
|
},
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
USE: false,
|
||||||
|
},
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await updateAccessPermissions(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { SHARED_GLOBAL: true },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { USE: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
expect(updatedRole[PermissionTypes.PROMPTS]).toEqual({
|
||||||
|
CREATE: true,
|
||||||
|
USE: true,
|
||||||
|
SHARED_GLOBAL: true,
|
||||||
|
});
|
||||||
|
expect(updatedRole[PermissionTypes.MULTI_CONVO]).toEqual({
|
||||||
|
USE: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update MULTI_CONVO permissions when no changes are needed', async () => {
|
||||||
|
await new Role({
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
USE: true,
|
||||||
|
},
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await updateAccessPermissions(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
USE: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
expect(updatedRole[PermissionTypes.MULTI_CONVO]).toEqual({
|
||||||
|
USE: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initializeRoles', () => {
|
describe('initializeRoles', () => {
|
||||||
|
|
@ -313,4 +382,39 @@ describe('initializeRoles', () => {
|
||||||
expect(adminRole[PermissionTypes.AGENTS].USE).toBeDefined();
|
expect(adminRole[PermissionTypes.AGENTS].USE).toBeDefined();
|
||||||
expect(adminRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined();
|
expect(adminRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should include MULTI_CONVO permissions when creating default roles', async () => {
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const adminRole = await Role.findOne({ name: SystemRoles.ADMIN }).lean();
|
||||||
|
const userRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
|
||||||
|
expect(adminRole[PermissionTypes.MULTI_CONVO]).toBeDefined();
|
||||||
|
expect(userRole[PermissionTypes.MULTI_CONVO]).toBeDefined();
|
||||||
|
|
||||||
|
// Check if MULTI_CONVO permissions match defaults
|
||||||
|
expect(adminRole[PermissionTypes.MULTI_CONVO].USE).toBe(
|
||||||
|
roleDefaults[SystemRoles.ADMIN][PermissionTypes.MULTI_CONVO].USE,
|
||||||
|
);
|
||||||
|
expect(userRole[PermissionTypes.MULTI_CONVO].USE).toBe(
|
||||||
|
roleDefaults[SystemRoles.USER][PermissionTypes.MULTI_CONVO].USE,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add MULTI_CONVO permissions to existing roles without them', async () => {
|
||||||
|
const partialUserRole = {
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.PROMPTS]: roleDefaults[SystemRoles.USER][PermissionTypes.PROMPTS],
|
||||||
|
[PermissionTypes.BOOKMARKS]: roleDefaults[SystemRoles.USER][PermissionTypes.BOOKMARKS],
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Role(partialUserRole).save();
|
||||||
|
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const userRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
|
||||||
|
expect(userRole[PermissionTypes.MULTI_CONVO]).toBeDefined();
|
||||||
|
expect(userRole[PermissionTypes.MULTI_CONVO].USE).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ const roleSchema = new mongoose.Schema({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {
|
||||||
|
[Permissions.USE]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Role = mongoose.model('Role', roleSchema);
|
const Role = mongoose.model('Role', roleSchema);
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,13 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
|
||||||
termsOfService: interfaceConfig?.termsOfService ?? defaults.termsOfService,
|
termsOfService: interfaceConfig?.termsOfService ?? defaults.termsOfService,
|
||||||
bookmarks: interfaceConfig?.bookmarks ?? defaults.bookmarks,
|
bookmarks: interfaceConfig?.bookmarks ?? defaults.bookmarks,
|
||||||
prompts: interfaceConfig?.prompts ?? defaults.prompts,
|
prompts: interfaceConfig?.prompts ?? defaults.prompts,
|
||||||
|
multiConvo: interfaceConfig?.multiConvo ?? defaults.multiConvo,
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateAccessPermissions(roleName, {
|
await updateAccessPermissions(roleName, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: loadedInterface.multiConvo },
|
||||||
});
|
});
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: false },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: false },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -40,6 +42,7 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -52,6 +55,7 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -64,6 +68,7 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -76,6 +81,72 @@ describe('loadDefaultInterface', () => {
|
||||||
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
||||||
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call updateAccessPermissions with the correct parameters when multiConvo is true', async () => {
|
||||||
|
const config = { interface: { multiConvo: true } };
|
||||||
|
const configDefaults = { interface: {} };
|
||||||
|
|
||||||
|
await loadDefaultInterface(config, configDefaults);
|
||||||
|
|
||||||
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call updateAccessPermissions with false when multiConvo is false', async () => {
|
||||||
|
const config = { interface: { multiConvo: false } };
|
||||||
|
const configDefaults = { interface: {} };
|
||||||
|
|
||||||
|
await loadDefaultInterface(config, configDefaults);
|
||||||
|
|
||||||
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call updateAccessPermissions with undefined when multiConvo is not specified in config', async () => {
|
||||||
|
const config = {};
|
||||||
|
const configDefaults = { interface: {} };
|
||||||
|
|
||||||
|
await loadDefaultInterface(config, configDefaults);
|
||||||
|
|
||||||
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: undefined },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call updateAccessPermissions with all interface options including multiConvo', async () => {
|
||||||
|
const config = { interface: { prompts: true, bookmarks: false, multiConvo: true } };
|
||||||
|
const configDefaults = { interface: {} };
|
||||||
|
|
||||||
|
await loadDefaultInterface(config, configDefaults);
|
||||||
|
|
||||||
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: true },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use default values for multiConvo when config is undefined', async () => {
|
||||||
|
const config = undefined;
|
||||||
|
const configDefaults = { interface: { prompts: true, bookmarks: true, multiConvo: false } };
|
||||||
|
|
||||||
|
await loadDefaultInterface(config, configDefaults);
|
||||||
|
|
||||||
|
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
|
||||||
|
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
|
||||||
|
[PermissionTypes.MULTI_CONVO]: { [Permissions.USE]: false },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ export default function Header() {
|
||||||
permission: Permissions.USE,
|
permission: Permissions.USE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasAccessToMultiConvo = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.MULTI_CONVO,
|
||||||
|
permission: Permissions.USE,
|
||||||
|
});
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -38,7 +43,7 @@ export default function Header() {
|
||||||
{<HeaderOptions interfaceConfig={interfaceConfig} />}
|
{<HeaderOptions interfaceConfig={interfaceConfig} />}
|
||||||
{interfaceConfig.presets === true && <PresetsMenu />}
|
{interfaceConfig.presets === true && <PresetsMenu />}
|
||||||
{hasAccessToBookmarks === true && <BookmarkMenu />}
|
{hasAccessToBookmarks === true && <BookmarkMenu />}
|
||||||
<AddMultiConvo />
|
{hasAccessToMultiConvo === true && <AddMultiConvo />}
|
||||||
{isSmallScreen && (
|
{isSmallScreen && (
|
||||||
<ExportAndShareMenu
|
<ExportAndShareMenu
|
||||||
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
|
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { TMessageContentParts } from 'librechat-data-provider';
|
||||||
import Part from './Part';
|
import Part from './Part';
|
||||||
|
|
||||||
type ContentPartsProps = {
|
type ContentPartsProps = {
|
||||||
content: Array<TMessageContentParts | undefined>;
|
content: Array<TMessageContentParts | undefined> | undefined;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
isCreatedByUser: boolean;
|
isCreatedByUser: boolean;
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
|
|
@ -12,6 +12,9 @@ type ContentPartsProps = {
|
||||||
|
|
||||||
const ContentParts = memo(
|
const ContentParts = memo(
|
||||||
({ content, messageId, isCreatedByUser, isLast, isSubmitting }: ContentPartsProps) => {
|
({ content, messageId, isCreatedByUser, isLast, isSubmitting }: ContentPartsProps) => {
|
||||||
|
if (!content) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{content
|
{content
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import HoverCardSettings from '../HoverCardSettings';
|
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
|
||||||
import { Switch } from '~/components/ui';
|
import { Switch } from '~/components/ui';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,53 @@
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import * as Tabs from '@radix-ui/react-tabs';
|
import * as Tabs from '@radix-ui/react-tabs';
|
||||||
import { SettingsTabValues } from 'librechat-data-provider';
|
import { SettingsTabValues, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||||
|
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
|
||||||
|
import { useLocalize, useHasAccess } from '~/hooks';
|
||||||
import SlashCommandSwitch from './SlashCommandSwitch';
|
import SlashCommandSwitch from './SlashCommandSwitch';
|
||||||
import PlusCommandSwitch from './PlusCommandSwitch';
|
import PlusCommandSwitch from './PlusCommandSwitch';
|
||||||
import AtCommandSwitch from './AtCommandSwitch';
|
import AtCommandSwitch from './AtCommandSwitch';
|
||||||
|
|
||||||
function Commands() {
|
function Commands() {
|
||||||
|
const localize = useLocalize();
|
||||||
|
|
||||||
|
const hasAccessToPrompts = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.PROMPTS,
|
||||||
|
permission: Permissions.USE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasAccessToMultiConvo = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.MULTI_CONVO,
|
||||||
|
permission: Permissions.USE,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.Content
|
<Tabs.Content
|
||||||
value={SettingsTabValues.COMMANDS}
|
value={SettingsTabValues.COMMANDS}
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
className="w-full md:min-h-[271px]"
|
className="w-full md:min-h-[271px]"
|
||||||
>
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="text-lg font-medium text-text-primary">
|
||||||
|
{localize('com_nav_chat_commands')}
|
||||||
|
</h3>
|
||||||
|
<HoverCardSettings side="bottom" text="com_nav_chat_commands_info" />
|
||||||
|
</div>
|
||||||
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
||||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||||
<AtCommandSwitch />
|
<AtCommandSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
{hasAccessToMultiConvo === true && (
|
||||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||||
<PlusCommandSwitch />
|
<PlusCommandSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{hasAccessToPrompts === true && (
|
||||||
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
<div className="border-b border-border-medium pb-3 last-of-type:border-b-0">
|
||||||
<SlashCommandSwitch />
|
<SlashCommandSwitch />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ const HoverCardSettings = ({ side, text }) => {
|
||||||
return (
|
return (
|
||||||
<HoverCard openDelay={500}>
|
<HoverCard openDelay={500}>
|
||||||
<HoverCardTrigger>
|
<HoverCardTrigger>
|
||||||
<CircleHelpIcon className="h-5 w-5 text-gray-500" />{' '}
|
<CircleHelpIcon className="h-5 w-5 text-text-tertiary" />{' '}
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardPortal>
|
<HoverCardPortal>
|
||||||
<HoverCardContent side={side} className="z-[999] w-80">
|
<HoverCardContent side={side} className="z-[999] w-80">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">{localize(text)}</p>
|
<p className="text-sm text-text-secondary">{localize(text)}</p>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCardPortal>
|
</HoverCardPortal>
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,14 @@ const useHandleKeyUp = ({
|
||||||
setShowPlusPopover: SetterOrUpdater<boolean>;
|
setShowPlusPopover: SetterOrUpdater<boolean>;
|
||||||
setShowMentionPopover: SetterOrUpdater<boolean>;
|
setShowMentionPopover: SetterOrUpdater<boolean>;
|
||||||
}) => {
|
}) => {
|
||||||
const hasAccess = useHasAccess({
|
const hasPromptsAccess = useHasAccess({
|
||||||
permissionType: PermissionTypes.PROMPTS,
|
permissionType: PermissionTypes.PROMPTS,
|
||||||
permission: Permissions.USE,
|
permission: Permissions.USE,
|
||||||
});
|
});
|
||||||
|
const hasMultiConvoAccess = useHasAccess({
|
||||||
|
permissionType: PermissionTypes.MULTI_CONVO,
|
||||||
|
permission: Permissions.USE,
|
||||||
|
});
|
||||||
const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index));
|
const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index));
|
||||||
|
|
||||||
// Get the current state of command toggles
|
// Get the current state of command toggles
|
||||||
|
|
@ -64,19 +68,22 @@ const useHandleKeyUp = ({
|
||||||
}, [textAreaRef, setShowMentionPopover, atCommandEnabled]);
|
}, [textAreaRef, setShowMentionPopover, atCommandEnabled]);
|
||||||
|
|
||||||
const handlePlusCommand = useCallback(() => {
|
const handlePlusCommand = useCallback(() => {
|
||||||
if (plusCommandEnabled && shouldTriggerCommand(textAreaRef, '+')) {
|
if (!hasMultiConvoAccess || !plusCommandEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shouldTriggerCommand(textAreaRef, '+')) {
|
||||||
setShowPlusPopover(true);
|
setShowPlusPopover(true);
|
||||||
}
|
}
|
||||||
}, [textAreaRef, setShowPlusPopover, plusCommandEnabled]);
|
}, [textAreaRef, setShowPlusPopover, plusCommandEnabled, hasMultiConvoAccess]);
|
||||||
|
|
||||||
const handlePromptsCommand = useCallback(() => {
|
const handlePromptsCommand = useCallback(() => {
|
||||||
if (!hasAccess || !slashCommandEnabled) {
|
if (!hasPromptsAccess || !slashCommandEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (shouldTriggerCommand(textAreaRef, '/')) {
|
if (shouldTriggerCommand(textAreaRef, '/')) {
|
||||||
setShowPromptsPopover(true);
|
setShowPromptsPopover(true);
|
||||||
}
|
}
|
||||||
}, [textAreaRef, hasAccess, setShowPromptsPopover, slashCommandEnabled]);
|
}, [textAreaRef, hasPromptsAccess, setShowPromptsPopover, slashCommandEnabled]);
|
||||||
|
|
||||||
const commandHandlers = useMemo(
|
const commandHandlers = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
|
||||||
|
|
@ -728,12 +728,12 @@ export default {
|
||||||
com_ui_dropdown_variables: 'Dropdown-Variablen:',
|
com_ui_dropdown_variables: 'Dropdown-Variablen:',
|
||||||
com_ui_dropdown_variables_info:
|
com_ui_dropdown_variables_info:
|
||||||
'Erstellen Sie benutzerdefinierte Dropdown-Menüs für Ihre Eingabeaufforderungen: `{{variable_name:option1|option2|option3}}`',
|
'Erstellen Sie benutzerdefinierte Dropdown-Menüs für Ihre Eingabeaufforderungen: `{{variable_name:option1|option2|option3}}`',
|
||||||
com_nav_at_command_description:
|
|
||||||
'Schaltet den Befehl "@" zum Wechseln von Endpunkten, Modellen, Voreinstellungen usw. um.',
|
|
||||||
com_nav_plus_command: '+-Befehl',
|
com_nav_plus_command: '+-Befehl',
|
||||||
com_nav_plus_command_description: 'Schaltet den Befehl "+" zum Hinzufügen einer Mehrfachantwort-Einstellung um',
|
com_nav_plus_command_description:
|
||||||
|
'Schaltet den Befehl "+" zum Hinzufügen einer Mehrfachantwort-Einstellung um',
|
||||||
com_nav_slash_command: '/-Befehl',
|
com_nav_slash_command: '/-Befehl',
|
||||||
com_nav_slash_command_description: 'Schaltet den Befehl "/" zur Auswahl einer Eingabeaufforderung über die Tastatur um',
|
com_nav_slash_command_description:
|
||||||
|
'Schaltet den Befehl "/" zur Auswahl einer Eingabeaufforderung über die Tastatur um',
|
||||||
com_nav_command_settings: 'Befehlseinstellungen',
|
com_nav_command_settings: 'Befehlseinstellungen',
|
||||||
com_nav_convo_menu_options: 'Optionen des Gesprächsmenüs',
|
com_nav_convo_menu_options: 'Optionen des Gesprächsmenüs',
|
||||||
com_ui_artifacts: 'Artefakte',
|
com_ui_artifacts: 'Artefakte',
|
||||||
|
|
@ -755,12 +755,8 @@ com_nav_commands_tab: 'Befehlseinstellungen',
|
||||||
com_nav_at_command: '@-Befehl',
|
com_nav_at_command: '@-Befehl',
|
||||||
com_nav_at_command_description:
|
com_nav_at_command_description:
|
||||||
'Schaltet den Befehl "@" zum Wechseln von Endpunkten, Modellen, Voreinstellungen usw. um.',
|
'Schaltet den Befehl "@" zum Wechseln von Endpunkten, Modellen, Voreinstellungen usw. um.',
|
||||||
com_nav_plus_command: '+-Befehl',
|
com_nav_command_settings_description:
|
||||||
com_nav_plus_command_description: 'Schaltet den Befehl "+" zum Hinzufügen einer Mehrfachantwort-Einstellung um',
|
'Passen Sie an, welche Tastaturbefehle im Chat verfügbar sind',
|
||||||
com_nav_slash_command: '/-Befehl',
|
|
||||||
com_nav_slash_command_description: 'Schaltet den Befehl "/" zur Auswahl einer Eingabeaufforderung über die Tastatur um',
|
|
||||||
com_nav_command_settings: 'Tastaturbefehl-Einstellungen',
|
|
||||||
com_nav_command_settings_description: 'Passen Sie an, welche Tastaturbefehle im Chat verfügbar sind',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const comparisons = {
|
export const comparisons = {
|
||||||
|
|
|
||||||
|
|
@ -753,6 +753,9 @@ export default {
|
||||||
com_nav_info_delete_cache_storage:
|
com_nav_info_delete_cache_storage:
|
||||||
'This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.',
|
'This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.',
|
||||||
// Command Settings Tab
|
// Command Settings Tab
|
||||||
|
com_nav_chat_commands: 'Chat Commands',
|
||||||
|
com_nav_chat_commands_info:
|
||||||
|
'These commands are activated by typing specific characters at the beginning of your message. Each command is triggered by its designated prefix. You can disable them if you frequently use these characters to start messages.',
|
||||||
com_nav_commands: 'Commands',
|
com_nav_commands: 'Commands',
|
||||||
com_nav_commands_tab: 'Command Settings',
|
com_nav_commands_tab: 'Command Settings',
|
||||||
com_nav_at_command: '@-Command',
|
com_nav_at_command: '@-Command',
|
||||||
|
|
|
||||||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -36399,7 +36399,7 @@
|
||||||
},
|
},
|
||||||
"packages/data-provider": {
|
"packages/data-provider": {
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.420",
|
"version": "0.7.421",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.420",
|
"version": "0.7.421",
|
||||||
"description": "data services for librechat apps",
|
"description": "data services for librechat apps",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
|
||||||
|
|
@ -460,6 +460,7 @@ export const configSchema = z.object({
|
||||||
modelSelect: z.boolean().optional(),
|
modelSelect: z.boolean().optional(),
|
||||||
parameters: z.boolean().optional(),
|
parameters: z.boolean().optional(),
|
||||||
sidePanel: z.boolean().optional(),
|
sidePanel: z.boolean().optional(),
|
||||||
|
multiConvo: z.boolean().optional(),
|
||||||
bookmarks: z.boolean().optional(),
|
bookmarks: z.boolean().optional(),
|
||||||
presets: z.boolean().optional(),
|
presets: z.boolean().optional(),
|
||||||
prompts: z.boolean().optional(),
|
prompts: z.boolean().optional(),
|
||||||
|
|
@ -470,6 +471,7 @@ export const configSchema = z.object({
|
||||||
parameters: true,
|
parameters: true,
|
||||||
sidePanel: true,
|
sidePanel: true,
|
||||||
presets: true,
|
presets: true,
|
||||||
|
multiConvo: true,
|
||||||
bookmarks: true,
|
bookmarks: true,
|
||||||
prompts: true,
|
prompts: true,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ export enum PermissionTypes {
|
||||||
* Type for Agent Permissions
|
* Type for Agent Permissions
|
||||||
*/
|
*/
|
||||||
AGENTS = 'AGENTS',
|
AGENTS = 'AGENTS',
|
||||||
|
/**
|
||||||
|
* Type for Multi-Conversation Permissions
|
||||||
|
*/
|
||||||
|
MULTI_CONVO = 'MULTI_CONVO',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,17 +64,23 @@ export const agentPermissionsSchema = z.object({
|
||||||
// [Permissions.SHARE]: z.boolean().default(false),
|
// [Permissions.SHARE]: z.boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const multiConvoPermissionsSchema = z.object({
|
||||||
|
[Permissions.USE]: z.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
export const roleSchema = z.object({
|
export const roleSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TRole = z.infer<typeof roleSchema>;
|
export type TRole = z.infer<typeof roleSchema>;
|
||||||
export type TAgentPermissions = z.infer<typeof agentPermissionsSchema>;
|
export type TAgentPermissions = z.infer<typeof agentPermissionsSchema>;
|
||||||
export type TPromptPermissions = z.infer<typeof promptPermissionsSchema>;
|
export type TPromptPermissions = z.infer<typeof promptPermissionsSchema>;
|
||||||
export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>;
|
export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>;
|
||||||
|
export type TMultiConvoPermissions = z.infer<typeof multiConvoPermissionsSchema>;
|
||||||
|
|
||||||
const defaultRolesSchema = z.object({
|
const defaultRolesSchema = z.object({
|
||||||
[SystemRoles.ADMIN]: roleSchema.extend({
|
[SystemRoles.ADMIN]: roleSchema.extend({
|
||||||
|
|
@ -90,12 +100,16 @@ const defaultRolesSchema = z.object({
|
||||||
[Permissions.CREATE]: z.boolean().default(true),
|
[Permissions.CREATE]: z.boolean().default(true),
|
||||||
// [Permissions.SHARE]: z.boolean().default(true),
|
// [Permissions.SHARE]: z.boolean().default(true),
|
||||||
}),
|
}),
|
||||||
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema.extend({
|
||||||
|
[Permissions.USE]: z.boolean().default(true),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
[SystemRoles.USER]: roleSchema.extend({
|
[SystemRoles.USER]: roleSchema.extend({
|
||||||
name: z.literal(SystemRoles.USER),
|
name: z.literal(SystemRoles.USER),
|
||||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
|
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -105,11 +119,13 @@ export const roleDefaults = defaultRolesSchema.parse({
|
||||||
[PermissionTypes.PROMPTS]: {},
|
[PermissionTypes.PROMPTS]: {},
|
||||||
[PermissionTypes.BOOKMARKS]: {},
|
[PermissionTypes.BOOKMARKS]: {},
|
||||||
[PermissionTypes.AGENTS]: {},
|
[PermissionTypes.AGENTS]: {},
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {},
|
||||||
},
|
},
|
||||||
[SystemRoles.USER]: {
|
[SystemRoles.USER]: {
|
||||||
name: SystemRoles.USER,
|
name: SystemRoles.USER,
|
||||||
[PermissionTypes.PROMPTS]: {},
|
[PermissionTypes.PROMPTS]: {},
|
||||||
[PermissionTypes.BOOKMARKS]: {},
|
[PermissionTypes.BOOKMARKS]: {},
|
||||||
[PermissionTypes.AGENTS]: {},
|
[PermissionTypes.AGENTS]: {},
|
||||||
|
[PermissionTypes.MULTI_CONVO]: {},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -303,13 +303,16 @@ export type TInterfaceConfig = {
|
||||||
openNewTab?: boolean;
|
openNewTab?: boolean;
|
||||||
modalAcceptance?: boolean;
|
modalAcceptance?: boolean;
|
||||||
modalTitle?: string;
|
modalTitle?: string;
|
||||||
modalContent?: string;
|
modalContent?: string | string[];
|
||||||
};
|
};
|
||||||
endpointsMenu: boolean;
|
endpointsMenu: boolean;
|
||||||
modelSelect: boolean;
|
modelSelect: boolean;
|
||||||
parameters: boolean;
|
parameters: boolean;
|
||||||
sidePanel: boolean;
|
sidePanel: boolean;
|
||||||
presets: boolean;
|
presets: boolean;
|
||||||
|
multiConvo: boolean;
|
||||||
|
bookmarks: boolean;
|
||||||
|
prompts: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TStartupConfig = {
|
export type TStartupConfig = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue