mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-25 12:48:53 +01:00
* 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>
129 lines
4 KiB
TypeScript
129 lines
4 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
import { useToastContext } from '@librechat/client';
|
|
import type { SharePointFile, SharePointBatchProgress } from '~/data-provider/Files';
|
|
import { useSharePointBatchDownload } from '~/data-provider/Files';
|
|
import useSharePointToken from './useSharePointToken';
|
|
|
|
interface UseSharePointDownloadProps {
|
|
onFilesDownloaded?: (files: File[]) => void | Promise<void>;
|
|
onError?: (error: Error) => void;
|
|
}
|
|
|
|
interface UseSharePointDownloadReturn {
|
|
downloadSharePointFiles: (files: SharePointFile[]) => Promise<File[]>;
|
|
isDownloading: boolean;
|
|
downloadProgress: SharePointBatchProgress | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export default function useSharePointDownload({
|
|
onFilesDownloaded,
|
|
onError,
|
|
}: UseSharePointDownloadProps = {}): UseSharePointDownloadReturn {
|
|
const { showToast } = useToastContext();
|
|
const [downloadProgress, setDownloadProgress] = useState<SharePointBatchProgress | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const { token, refetch: refetchToken } = useSharePointToken({
|
|
enabled: false,
|
|
purpose: 'Download',
|
|
});
|
|
|
|
const batchDownloadMutation = useSharePointBatchDownload();
|
|
|
|
const downloadSharePointFiles = useCallback(
|
|
async (files: SharePointFile[]): Promise<File[]> => {
|
|
if (!files || files.length === 0) {
|
|
throw new Error('No files provided for download');
|
|
}
|
|
|
|
setError(null);
|
|
setDownloadProgress({ completed: 0, total: files.length, failed: [] });
|
|
|
|
try {
|
|
let accessToken = token?.access_token;
|
|
if (!accessToken) {
|
|
showToast({
|
|
message: 'Getting SharePoint access token...',
|
|
status: 'info',
|
|
duration: 2000,
|
|
});
|
|
|
|
const tokenResult = await refetchToken();
|
|
accessToken = tokenResult.data?.access_token;
|
|
|
|
if (!accessToken) {
|
|
throw new Error('Failed to obtain SharePoint access token');
|
|
}
|
|
}
|
|
|
|
showToast({
|
|
message: `Downloading ${files.length} file(s) from SharePoint...`,
|
|
status: 'info',
|
|
duration: 3000,
|
|
});
|
|
|
|
const downloadedFiles = await batchDownloadMutation.mutateAsync({
|
|
files,
|
|
accessToken,
|
|
onProgress: (progress) => {
|
|
setDownloadProgress(progress);
|
|
|
|
if (files.length > 5 && progress.completed % 3 === 0) {
|
|
showToast({
|
|
message: `Downloaded ${progress.completed}/${progress.total} files...`,
|
|
status: 'info',
|
|
duration: 1000,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
if (downloadedFiles.length > 0) {
|
|
const failedCount = files.length - downloadedFiles.length;
|
|
const successMessage =
|
|
failedCount > 0
|
|
? `Downloaded ${downloadedFiles.length}/${files.length} files from SharePoint (${failedCount} failed)`
|
|
: `Successfully downloaded ${downloadedFiles.length} file(s) from SharePoint`;
|
|
|
|
showToast({
|
|
message: successMessage,
|
|
status: failedCount > 0 ? 'warning' : 'success',
|
|
duration: 4000,
|
|
});
|
|
|
|
if (onFilesDownloaded) {
|
|
await onFilesDownloaded(downloadedFiles);
|
|
}
|
|
}
|
|
|
|
setDownloadProgress(null);
|
|
return downloadedFiles;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown download error';
|
|
setError(errorMessage);
|
|
|
|
showToast({
|
|
message: `SharePoint download failed: ${errorMessage}`,
|
|
status: 'error',
|
|
duration: 5000,
|
|
});
|
|
|
|
if (onError) {
|
|
onError(error instanceof Error ? error : new Error(errorMessage));
|
|
}
|
|
|
|
setDownloadProgress(null);
|
|
throw error;
|
|
}
|
|
},
|
|
[token, showToast, batchDownloadMutation, onFilesDownloaded, onError, refetchToken],
|
|
);
|
|
|
|
return {
|
|
downloadSharePointFiles,
|
|
isDownloading: batchDownloadMutation.isLoading,
|
|
downloadProgress,
|
|
error,
|
|
};
|
|
}
|