mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 11:50:14 +01:00
📁 feat: Integrate SharePoint File Picker and Download Workflow (#8651)
* feat(sharepoint): integrate SharePoint file picker and download workflow Introduces end‑to‑end SharePoint import support: * Token exchange with Microsoft Graph and scope management (`useSharePointToken`) * Re‑usable hooks: `useSharePointPicker`, `useSharePointDownload`, `useSharePointFileHandling` * FileSearch dropdown now offers **From Local Machine** / **From SharePoint** sources and gracefully falls back when SharePoint is disabled * Agent upload model, `AttachFileMenu`, and `DropdownPopup` extended for SharePoint files and sub‑menus * Blurry overlay with progress indicator and `maxSelectionCount` limit during downloads * Cache‑flush utility (`config/flush-cache.js`) supporting Redis & filesystem, with dry‑run and npm script * Updated `SharePointIcon` (uses `currentColor`) and new i18n keys * Bug fixes: placeholder syntax in progress message, picker event‑listener cleanup * Misc style and performance optimizations * Fix ESLint warnings --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
parent
47caafa8f8
commit
d786b1b419
40 changed files with 2500 additions and 123 deletions
|
|
@ -1,4 +1,6 @@
|
|||
import { useState, useRef } from 'react';
|
||||
import { Folder } from 'lucide-react';
|
||||
import * as Ariakit from '@ariakit/react';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
EToolResources,
|
||||
|
|
@ -7,16 +9,21 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardPortal,
|
||||
HoverCardTrigger,
|
||||
DropdownPopup,
|
||||
AttachmentIcon,
|
||||
CircleHelpIcon,
|
||||
AttachmentIcon,
|
||||
CircleHelpIcon,
|
||||
SharePointIcon,
|
||||
HoverCardPortal,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@librechat/client';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
|
||||
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 { useGetFileConfig } from '~/data-provider';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
|
|
@ -31,6 +38,10 @@ export default function FileContext({
|
|||
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),
|
||||
|
|
@ -41,7 +52,11 @@ export default function FileContext({
|
|||
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) {
|
||||
|
|
@ -54,19 +69,45 @@ export default function FileContext({
|
|||
|
||||
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 handleButtonClick = () => {
|
||||
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}>
|
||||
|
|
@ -101,26 +142,42 @@ export default function FileContext({
|
|||
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!agent_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 w-full rounded-lg font-medium"
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-1">
|
||||
<AttachmentIcon className="text-token-text-primary h-4 w-4" />
|
||||
<input
|
||||
multiple={true}
|
||||
type="file"
|
||||
style={{ display: 'none' }}
|
||||
tabIndex={-1}
|
||||
ref={fileInputRef}
|
||||
disabled={!agent_id}
|
||||
onChange={handleFileChange}
|
||||
{sharePointEnabled ? (
|
||||
<>
|
||||
<DropdownPopup
|
||||
gutter={2}
|
||||
menuId="file-search-upload-menu"
|
||||
isOpen={isPopoverActive}
|
||||
setIsOpen={setIsPopoverActive}
|
||||
trigger={menuTrigger}
|
||||
items={dropdownItems}
|
||||
modal={true}
|
||||
unmountOnHide={true}
|
||||
/>
|
||||
{localize('com_ui_upload_file_context')}
|
||||
</div>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<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 : (
|
||||
|
|
@ -129,6 +186,14 @@ export default function FileContext({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SharePointPickerDialog
|
||||
isOpen={isSharePointDialogOpen}
|
||||
onOpenChange={setIsSharePointDialogOpen}
|
||||
onFilesSelected={handleSharePointFilesSelected}
|
||||
isDownloading={isProcessing}
|
||||
downloadProgress={downloadProgress}
|
||||
maxSelectionCount={endpointFileConfig?.fileLimit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue