mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 03:40:14 +01:00
feat: Implement prompt permissions management and access control middleware fix: agent deletion process to remove associated permissions and ACL entries fix: Import Permissions for enhanced access control in GrantAccessDialog feat: use PromptGroup for access control - Added migration script for PromptGroup permissions, categorizing groups into global view access and private groups. - Created unit tests for the migration script to ensure correct categorization and permission granting. - Introduced middleware for checking access permissions on PromptGroups and prompts via their groups. - Updated routes to utilize new access control middleware for PromptGroups. - Enhanced access role definitions to include roles specific to PromptGroups. - Modified ACL entry schema and types to accommodate PromptGroup resource type. - Updated data provider to include new access role identifiers for PromptGroups. feat: add generic access management dialogs and hooks for resource permissions fix: remove duplicate imports in FileContext component fix: remove duplicate mongoose dependency in package.json feat: add access permissions handling for dynamic resource types and add promptGroup roles feat: implement centralized role localization and update access role types refactor: simplify author handling in prompt group routes and enhance ACL checks feat: implement addPromptToGroup functionality and update PromptForm to use it feat: enhance permission handling in ChatGroupItem, DashGroupItem, and PromptForm components chore: rename migration script for prompt group permissions and update package.json scripts chore: update prompt tests
197 lines
6.5 KiB
TypeScript
197 lines
6.5 KiB
TypeScript
import { useState, useRef } from 'react';
|
|
import { Folder } from 'lucide-react';
|
|
import * as Ariakit from '@ariakit/react';
|
|
import {
|
|
EModelEndpoint,
|
|
EToolResources,
|
|
mergeFileConfig,
|
|
fileConfig as defaultFileConfig,
|
|
} from 'librechat-data-provider';
|
|
import {
|
|
HoverCard,
|
|
DropdownPopup,
|
|
AttachmentIcon,
|
|
CircleHelpIcon,
|
|
SharePointIcon,
|
|
HoverCardPortal,
|
|
HoverCardContent,
|
|
HoverCardTrigger,
|
|
} from '@librechat/client';
|
|
import type { ExtendedFile } from '~/common';
|
|
import { useFileHandling, useLocalize, useLazyEffect, useSharePointFileHandling } from '~/hooks';
|
|
import { useGetFileConfig, useGetStartupConfig } from '~/data-provider';
|
|
import { SharePointPickerDialog } from '~/components/SharePoint';
|
|
import FileRow from '~/components/Chat/Input/Files/FileRow';
|
|
import { useChatContext } from '~/Providers';
|
|
import { ESide } from '~/common';
|
|
|
|
export default function FileContext({
|
|
agent_id,
|
|
files: _files,
|
|
}: {
|
|
agent_id: string;
|
|
files?: [string, ExtendedFile][];
|
|
}) {
|
|
const localize = useLocalize();
|
|
const { setFilesLoading } = useChatContext();
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [files, setFiles] = useState<Map<string, ExtendedFile>>(new Map());
|
|
const [isPopoverActive, setIsPopoverActive] = useState(false);
|
|
const [isSharePointDialogOpen, setIsSharePointDialogOpen] = useState(false);
|
|
const { data: startupConfig } = useGetStartupConfig();
|
|
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
|
|
|
|
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
|
|
select: (data) => mergeFileConfig(data),
|
|
});
|
|
|
|
const { handleFileChange } = useFileHandling({
|
|
overrideEndpoint: EModelEndpoint.agents,
|
|
additionalMetadata: { agent_id, tool_resource: EToolResources.ocr },
|
|
fileSetter: setFiles,
|
|
});
|
|
const { handleSharePointFiles, isProcessing, downloadProgress } = useSharePointFileHandling({
|
|
overrideEndpoint: EModelEndpoint.agents,
|
|
additionalMetadata: { agent_id, tool_resource: EToolResources.file_search },
|
|
fileSetter: setFiles,
|
|
});
|
|
useLazyEffect(
|
|
() => {
|
|
if (_files) {
|
|
setFiles(new Map(_files));
|
|
}
|
|
},
|
|
[_files],
|
|
750,
|
|
);
|
|
|
|
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents];
|
|
const isUploadDisabled = endpointFileConfig.disabled ?? false;
|
|
const handleSharePointFilesSelected = async (sharePointFiles: any[]) => {
|
|
try {
|
|
await handleSharePointFiles(sharePointFiles);
|
|
setIsSharePointDialogOpen(false);
|
|
} catch (error) {
|
|
console.error('SharePoint file processing error:', error);
|
|
}
|
|
};
|
|
if (isUploadDisabled) {
|
|
return null;
|
|
}
|
|
|
|
const handleLocalFileClick = () => {
|
|
// necessary to reset the input
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = '';
|
|
}
|
|
fileInputRef.current?.click();
|
|
};
|
|
const dropdownItems = [
|
|
{
|
|
label: localize('com_files_upload_local_machine'),
|
|
onClick: handleLocalFileClick,
|
|
icon: <Folder className="icon-md" />,
|
|
},
|
|
{
|
|
label: localize('com_files_upload_sharepoint'),
|
|
onClick: () => setIsSharePointDialogOpen(true),
|
|
icon: <SharePointIcon className="icon-md" />,
|
|
},
|
|
];
|
|
const menuTrigger = (
|
|
<Ariakit.MenuButton className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium">
|
|
<div className="flex w-full items-center justify-center gap-1">
|
|
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
|
|
{localize('com_ui_upload_file_context')}
|
|
</div>
|
|
</Ariakit.MenuButton>
|
|
);
|
|
return (
|
|
<div className="w-full">
|
|
<HoverCard openDelay={50}>
|
|
<div className="mb-2 flex items-center gap-2">
|
|
<HoverCardTrigger asChild>
|
|
<span className="flex items-center gap-2">
|
|
<label className="text-token-text-primary block font-medium">
|
|
{localize('com_agents_file_context')}
|
|
</label>
|
|
<CircleHelpIcon className="h-4 w-4 text-text-tertiary" />
|
|
</span>
|
|
</HoverCardTrigger>
|
|
<HoverCardPortal>
|
|
<HoverCardContent side={ESide.Top} className="w-80">
|
|
<div className="space-y-2">
|
|
<p className="text-sm text-text-secondary">
|
|
{localize('com_agents_file_context_info')}
|
|
</p>
|
|
</div>
|
|
</HoverCardContent>
|
|
</HoverCardPortal>
|
|
</div>
|
|
</HoverCard>
|
|
<div className="flex flex-col gap-3">
|
|
{/* File Context (OCR) Files */}
|
|
<FileRow
|
|
files={files}
|
|
setFiles={setFiles}
|
|
setFilesLoading={setFilesLoading}
|
|
agent_id={agent_id}
|
|
tool_resource={EToolResources.ocr}
|
|
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
|
|
/>
|
|
<div>
|
|
{sharePointEnabled ? (
|
|
<>
|
|
<DropdownPopup
|
|
gutter={2}
|
|
menuId="file-search-upload-menu"
|
|
isOpen={isPopoverActive}
|
|
setIsOpen={setIsPopoverActive}
|
|
trigger={menuTrigger}
|
|
items={dropdownItems}
|
|
modal={true}
|
|
unmountOnHide={true}
|
|
/>
|
|
</>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
disabled={!agent_id}
|
|
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
|
|
onClick={handleLocalFileClick}
|
|
>
|
|
<div className="flex w-full items-center justify-center gap-1">
|
|
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
|
|
|
|
{localize('com_ui_upload_file_context')}
|
|
</div>
|
|
</button>
|
|
)}
|
|
<input
|
|
multiple={true}
|
|
type="file"
|
|
style={{ display: 'none' }}
|
|
tabIndex={-1}
|
|
ref={fileInputRef}
|
|
disabled={!agent_id}
|
|
onChange={handleFileChange}
|
|
/>
|
|
</div>
|
|
{/* Disabled Message */}
|
|
{agent_id ? null : (
|
|
<div className="text-xs text-text-secondary">
|
|
{localize('com_agents_file_context_disabled')}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<SharePointPickerDialog
|
|
isOpen={isSharePointDialogOpen}
|
|
onOpenChange={setIsSharePointDialogOpen}
|
|
onFilesSelected={handleSharePointFilesSelected}
|
|
isDownloading={isProcessing}
|
|
downloadProgress={downloadProgress}
|
|
maxSelectionCount={endpointFileConfig?.fileLimit}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|