LibreChat/client/src/hooks/Files/useSharePointDownload.ts
Danny Avila a955097faf
📁 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>
2025-08-13 16:24:16 -04:00

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,
};
}