mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-29 05:36:13 +01:00
* refactor(OpenSidebar): removed useless classNames
* style(Header): update hover styles across various components for improved UI consistency
* style(Nav): update hover styles in AccountSettings and SearchBar for improved UI consistency
* style: update button classes for consistent hover effects and improved UI responsiveness
* style(Nav, OpenSidebar, Header, Convo): improve UI responsiveness and animation transitions
* style(PresetsMenu, NewChat): update icon sizes and improve component styling for better UI consistency
* style(Nav, Root): enhance sidebar mobile animations and responsiveness for better UI experience
* style(ExportAndShareMenu, BookmarkMenu): update icon sizes for improved UI consistency
* style: remove transition duration from button classes for improved UI responsiveness
* style(CustomMenu, ModelSelector): update background colors for improved UI consistency and responsiveness
* style(ExportAndShareMenu): update icon color for improved UI consistency
* style(TemporaryChat): refine button styles for improved UI consistency and responsiveness
* style(BookmarkNav): refactor to use DropdownPopup and remove BookmarkNavItems for improved UI consistency and functionality
* style(CustomMenu, EndpointItem): enhance UI elements for improved consistency and accessibility
* style(EndpointItem): adjust gap in icon container for improved layout consistency
* style(CustomMenu, EndpointItem): update focus ring color for improved UI consistency
* style(EndpointItem): update icon color for improved UI consistency in dark theme
* style: update focus styles for improved accessibility and consistency across components
* refactor(Nav): extract sidebar width to NAV_WIDTH constant
Centralize mobile (320px) and desktop (260px) sidebar widths in a single
exported constant to avoid magic numbers and ensure consistency.
* fix(BookmarkNav): memoize handlers used in useMemo
Wrap handleTagClick and handleClear in useCallback and add them to the
dropdownItems useMemo dependency array to prevent stale closures.
* feat: introduce FilterInput component and replace existing inputs with it across multiple components
* feat(DataTable): replace custom input with FilterInput component for improved filtering
* fix: Nested dialog overlay stacking issue
Fixes overlay appearing behind content when opening nested dialogs.
Introduced dynamic z-index calculation based on dialog depth using React context.
- First dialog: overlay z-50, content z-100
- Nested dialogs increment by 60: overlay z-110/content z-160, etc.
Preserves a11y escape key handling from #10975 and #10851.
Regression from #11008 (afb67fcf1) which increased content z-index
without adjusting overlay z-index for nested dialog scenarios.
* Refactor admin settings components to use a unified AdminSettingsDialog
- Removed redundant code from AdminSettings, MCPAdminSettings, and Memories AdminSettings components.
- Introduced AdminSettingsDialog component to handle permission management for different sections.
- Updated permission handling logic to use a consistent structure across components.
- Enhanced role selection and permission confirmation features in the new dialog.
- Improved UI consistency and maintainability by centralizing dialog functionality.
* refactor(Memory): memory management UI components and replace MemoryViewer with MemoryPanel
* refactor(Memory): enhance UI components for Memory dialogs and improve input styling
* refactor(Bookmarks): improve bookmark management UI with enhanced styling
* refactor(translations): remove redundant filter input and bookmark count entries
* refactor(Convo): integrate useShiftKey hook for enhanced keyboard interaction and improve UI responsiveness
292 lines
8.8 KiB
TypeScript
292 lines
8.8 KiB
TypeScript
import React, { useRef, useState, useMemo } from 'react';
|
|
import { useRecoilState } from 'recoil';
|
|
import * as Ariakit from '@ariakit/react';
|
|
import {
|
|
FileSearch,
|
|
ImageUpIcon,
|
|
FileType2Icon,
|
|
FileImageIcon,
|
|
TerminalSquareIcon,
|
|
} from 'lucide-react';
|
|
import {
|
|
Providers,
|
|
EToolResources,
|
|
EModelEndpoint,
|
|
defaultAgentCapabilities,
|
|
isDocumentSupportedProvider,
|
|
} from 'librechat-data-provider';
|
|
import {
|
|
FileUpload,
|
|
TooltipAnchor,
|
|
DropdownPopup,
|
|
AttachmentIcon,
|
|
SharePointIcon,
|
|
} from '@librechat/client';
|
|
import type { EndpointFileConfig } from 'librechat-data-provider';
|
|
import {
|
|
useAgentToolPermissions,
|
|
useAgentCapabilities,
|
|
useGetAgentsConfig,
|
|
useFileHandling,
|
|
useLocalize,
|
|
} from '~/hooks';
|
|
import useSharePointFileHandling from '~/hooks/Files/useSharePointFileHandling';
|
|
import { SharePointPickerDialog } from '~/components/SharePoint';
|
|
import { useGetStartupConfig } from '~/data-provider';
|
|
import { ephemeralAgentByConvoId } from '~/store';
|
|
import { MenuItemProps } from '~/common';
|
|
import { cn } from '~/utils';
|
|
|
|
type FileUploadType = 'image' | 'document' | 'image_document' | 'image_document_video_audio';
|
|
|
|
interface AttachFileMenuProps {
|
|
agentId?: string | null;
|
|
endpoint?: string | null;
|
|
disabled?: boolean | null;
|
|
conversationId: string;
|
|
endpointType?: EModelEndpoint;
|
|
endpointFileConfig?: EndpointFileConfig;
|
|
useResponsesApi?: boolean;
|
|
}
|
|
|
|
const AttachFileMenu = ({
|
|
agentId,
|
|
endpoint,
|
|
disabled,
|
|
endpointType,
|
|
conversationId,
|
|
endpointFileConfig,
|
|
useResponsesApi,
|
|
}: AttachFileMenuProps) => {
|
|
const localize = useLocalize();
|
|
const isUploadDisabled = disabled ?? false;
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [isPopoverActive, setIsPopoverActive] = useState(false);
|
|
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(
|
|
ephemeralAgentByConvoId(conversationId),
|
|
);
|
|
const [toolResource, setToolResource] = useState<EToolResources | undefined>();
|
|
const { handleFileChange } = useFileHandling();
|
|
const { handleSharePointFiles, isProcessing, downloadProgress } = useSharePointFileHandling({
|
|
toolResource,
|
|
});
|
|
|
|
const { agentsConfig } = useGetAgentsConfig();
|
|
const { data: startupConfig } = useGetStartupConfig();
|
|
const sharePointEnabled = startupConfig?.sharePointFilePickerEnabled;
|
|
|
|
const [isSharePointDialogOpen, setIsSharePointDialogOpen] = useState(false);
|
|
|
|
/** TODO: Ephemeral Agent Capabilities
|
|
* Allow defining agent capabilities on a per-endpoint basis
|
|
* Use definition for agents endpoint for ephemeral agents
|
|
* */
|
|
const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
|
|
|
|
const { fileSearchAllowedByAgent, codeAllowedByAgent, provider } = useAgentToolPermissions(
|
|
agentId,
|
|
ephemeralAgent,
|
|
);
|
|
|
|
const handleUploadClick = (fileType?: FileUploadType) => {
|
|
if (!inputRef.current) {
|
|
return;
|
|
}
|
|
inputRef.current.value = '';
|
|
if (fileType === 'image') {
|
|
inputRef.current.accept = 'image/*';
|
|
} else if (fileType === 'document') {
|
|
inputRef.current.accept = '.pdf,application/pdf';
|
|
} else if (fileType === 'image_document') {
|
|
inputRef.current.accept = 'image/*,.pdf,application/pdf';
|
|
} else if (fileType === 'image_document_video_audio') {
|
|
inputRef.current.accept = 'image/*,.pdf,application/pdf,video/*,audio/*';
|
|
} else {
|
|
inputRef.current.accept = '';
|
|
}
|
|
inputRef.current.click();
|
|
inputRef.current.accept = '';
|
|
};
|
|
|
|
const dropdownItems = useMemo(() => {
|
|
const createMenuItems = (onAction: (fileType?: FileUploadType) => void) => {
|
|
const items: MenuItemProps[] = [];
|
|
|
|
let currentProvider = provider || endpoint;
|
|
|
|
// This will be removed in a future PR to formally normalize Providers comparisons to be case insensitive
|
|
if (currentProvider?.toLowerCase() === Providers.OPENROUTER) {
|
|
currentProvider = Providers.OPENROUTER;
|
|
}
|
|
|
|
const isAzureWithResponsesApi =
|
|
currentProvider === EModelEndpoint.azureOpenAI && useResponsesApi;
|
|
|
|
if (
|
|
isDocumentSupportedProvider(endpointType) ||
|
|
isDocumentSupportedProvider(currentProvider) ||
|
|
isAzureWithResponsesApi
|
|
) {
|
|
items.push({
|
|
label: localize('com_ui_upload_provider'),
|
|
onClick: () => {
|
|
setToolResource(undefined);
|
|
let fileType: Exclude<FileUploadType, 'image' | 'document'> = 'image_document';
|
|
if (currentProvider === Providers.GOOGLE || currentProvider === Providers.OPENROUTER) {
|
|
fileType = 'image_document_video_audio';
|
|
}
|
|
onAction(fileType);
|
|
},
|
|
icon: <FileImageIcon className="icon-md" />,
|
|
});
|
|
} else {
|
|
items.push({
|
|
label: localize('com_ui_upload_image_input'),
|
|
onClick: () => {
|
|
setToolResource(undefined);
|
|
onAction('image');
|
|
},
|
|
icon: <ImageUpIcon className="icon-md" />,
|
|
});
|
|
}
|
|
|
|
if (capabilities.contextEnabled) {
|
|
items.push({
|
|
label: localize('com_ui_upload_ocr_text'),
|
|
onClick: () => {
|
|
setToolResource(EToolResources.context);
|
|
onAction();
|
|
},
|
|
icon: <FileType2Icon className="icon-md" />,
|
|
});
|
|
}
|
|
|
|
if (capabilities.fileSearchEnabled && fileSearchAllowedByAgent) {
|
|
items.push({
|
|
label: localize('com_ui_upload_file_search'),
|
|
onClick: () => {
|
|
setToolResource(EToolResources.file_search);
|
|
setEphemeralAgent((prev) => ({
|
|
...prev,
|
|
[EToolResources.file_search]: true,
|
|
}));
|
|
onAction();
|
|
},
|
|
icon: <FileSearch className="icon-md" />,
|
|
});
|
|
}
|
|
|
|
if (capabilities.codeEnabled && codeAllowedByAgent) {
|
|
items.push({
|
|
label: localize('com_ui_upload_code_files'),
|
|
onClick: () => {
|
|
setToolResource(EToolResources.execute_code);
|
|
setEphemeralAgent((prev) => ({
|
|
...prev,
|
|
[EToolResources.execute_code]: true,
|
|
}));
|
|
onAction();
|
|
},
|
|
icon: <TerminalSquareIcon className="icon-md" />,
|
|
});
|
|
}
|
|
|
|
return items;
|
|
};
|
|
|
|
const localItems = createMenuItems(handleUploadClick);
|
|
|
|
if (sharePointEnabled) {
|
|
const sharePointItems = createMenuItems(() => {
|
|
setIsSharePointDialogOpen(true);
|
|
// Note: toolResource will be set by the specific item clicked
|
|
});
|
|
localItems.push({
|
|
label: localize('com_files_upload_sharepoint'),
|
|
onClick: () => {},
|
|
icon: <SharePointIcon className="icon-md" />,
|
|
subItems: sharePointItems,
|
|
});
|
|
return localItems;
|
|
}
|
|
|
|
return localItems;
|
|
}, [
|
|
localize,
|
|
endpoint,
|
|
provider,
|
|
endpointType,
|
|
capabilities,
|
|
useResponsesApi,
|
|
setToolResource,
|
|
setEphemeralAgent,
|
|
sharePointEnabled,
|
|
codeAllowedByAgent,
|
|
fileSearchAllowedByAgent,
|
|
setIsSharePointDialogOpen,
|
|
]);
|
|
|
|
const menuTrigger = (
|
|
<TooltipAnchor
|
|
render={
|
|
<Ariakit.MenuButton
|
|
disabled={isUploadDisabled}
|
|
id="attach-file-menu-button"
|
|
aria-label="Attach File Options"
|
|
className={cn(
|
|
'flex size-9 items-center justify-center rounded-full p-1 hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-opacity-50',
|
|
isPopoverActive && 'bg-surface-hover',
|
|
)}
|
|
>
|
|
<div className="flex w-full items-center justify-center gap-2">
|
|
<AttachmentIcon />
|
|
</div>
|
|
</Ariakit.MenuButton>
|
|
}
|
|
id="attach-file-menu-button"
|
|
description={localize('com_sidepanel_attach_files')}
|
|
disabled={isUploadDisabled}
|
|
/>
|
|
);
|
|
const handleSharePointFilesSelected = async (sharePointFiles: any[]) => {
|
|
try {
|
|
await handleSharePointFiles(sharePointFiles);
|
|
setIsSharePointDialogOpen(false);
|
|
} catch (error) {
|
|
console.error('SharePoint file processing error:', error);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<FileUpload
|
|
ref={inputRef}
|
|
handleFileChange={(e) => {
|
|
handleFileChange(e, toolResource);
|
|
}}
|
|
>
|
|
<DropdownPopup
|
|
menuId="attach-file-menu"
|
|
className="overflow-visible"
|
|
isOpen={isPopoverActive}
|
|
setIsOpen={setIsPopoverActive}
|
|
modal={true}
|
|
unmountOnHide={true}
|
|
trigger={menuTrigger}
|
|
items={dropdownItems}
|
|
iconClassName="mr-0"
|
|
/>
|
|
</FileUpload>
|
|
<SharePointPickerDialog
|
|
isOpen={isSharePointDialogOpen}
|
|
onOpenChange={setIsSharePointDialogOpen}
|
|
onFilesSelected={handleSharePointFilesSelected}
|
|
isDownloading={isProcessing}
|
|
downloadProgress={downloadProgress}
|
|
maxSelectionCount={endpointFileConfig?.fileLimit}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default React.memo(AttachFileMenu);
|