mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +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
b6413b06bc
commit
a955097faf
40 changed files with 2500 additions and 123 deletions
|
|
@ -29,7 +29,8 @@ interface DropdownProps {
|
|||
type MenuProps = Omit<
|
||||
DropdownProps,
|
||||
'trigger' | 'isOpen' | 'setIsOpen' | 'focusLoop' | 'mountByState'
|
||||
>;
|
||||
> &
|
||||
Ariakit.MenuProps;
|
||||
|
||||
const DropdownPopup: React.FC<DropdownProps> = ({
|
||||
trigger,
|
||||
|
|
@ -70,7 +71,9 @@ const Menu: React.FC<MenuProps> = ({
|
|||
finalFocus,
|
||||
unmountOnHide,
|
||||
preserveTabOrder,
|
||||
...props
|
||||
}) => {
|
||||
const menuStore = Ariakit.useMenuStore();
|
||||
const menu = Ariakit.useMenuContext();
|
||||
return (
|
||||
<Ariakit.Menu
|
||||
|
|
@ -83,13 +86,53 @@ const Menu: React.FC<MenuProps> = ({
|
|||
unmountOnHide={unmountOnHide}
|
||||
preserveTabOrder={preserveTabOrder}
|
||||
className={cn('popover-ui z-50', className)}
|
||||
{...props}
|
||||
>
|
||||
{items
|
||||
.filter((item) => item.show !== false)
|
||||
.map((item, index) => {
|
||||
const { subItems } = item;
|
||||
if (item.separate === true) {
|
||||
return <Ariakit.MenuSeparator key={index} className="my-1 h-px bg-white/10" />;
|
||||
}
|
||||
if (subItems && subItems.length > 0) {
|
||||
return (
|
||||
<Ariakit.MenuProvider
|
||||
store={menuStore}
|
||||
key={`${keyPrefix ?? ''}${index}-${item.id ?? ''}-provider`}
|
||||
>
|
||||
<Ariakit.MenuButton
|
||||
className={cn(
|
||||
'group flex w-full cursor-pointer items-center justify-between gap-2 rounded-lg px-3 py-3.5 text-sm text-text-primary outline-none transition-colors duration-200 hover:bg-surface-hover focus:bg-surface-hover md:px-2.5 md:py-2',
|
||||
itemClassName,
|
||||
)}
|
||||
disabled={item.disabled}
|
||||
id={item.id}
|
||||
render={item.render}
|
||||
ref={item.ref}
|
||||
// hideOnClick={item.hideOnClick}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{item.icon != null && (
|
||||
<span className={cn('mr-2 size-4', iconClassName)} aria-hidden="true">
|
||||
{item.icon}
|
||||
</span>
|
||||
)}
|
||||
{item.label}
|
||||
</span>
|
||||
<Ariakit.MenuButtonArrow className="stroke-1 text-base opacity-75" />
|
||||
</Ariakit.MenuButton>
|
||||
<Menu
|
||||
items={subItems}
|
||||
menuId={`${menuId}-${index}`}
|
||||
key={`${keyPrefix ?? ''}${index}-${item.id ?? ''}`}
|
||||
gutter={12}
|
||||
portal={true}
|
||||
/>
|
||||
</Ariakit.MenuProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Ariakit.MenuItem
|
||||
key={`${keyPrefix ?? ''}${index}-${item.id ?? ''}`}
|
||||
|
|
|
|||
20
packages/client/src/svgs/CodePaths.tsx
Normal file
20
packages/client/src/svgs/CodePaths.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export default function CodePaths() {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
d="M21.333 23L26.333 18L21.333 13"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M14.667 13L9.66699 18L14.667 23"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
28
packages/client/src/svgs/FileIcon.tsx
Normal file
28
packages/client/src/svgs/FileIcon.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import type { TFile } from 'librechat-data-provider';
|
||||
import type { ExtendedFile } from '~/common';
|
||||
|
||||
export default function FileIcon({
|
||||
file,
|
||||
fileType,
|
||||
}: {
|
||||
file?: Partial<ExtendedFile | TFile>;
|
||||
fileType: {
|
||||
fill: string;
|
||||
paths: React.FC;
|
||||
title: string;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 36 36"
|
||||
fill="none"
|
||||
className="h-10 w-10 flex-shrink-0"
|
||||
width="36"
|
||||
height="36"
|
||||
>
|
||||
<rect width="36" height="36" rx="6" fill={fileType.fill} />
|
||||
{(file?.['progress'] ?? 1) >= 1 && <>{<fileType.paths />}</>}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
packages/client/src/svgs/FilePaths.tsx
Normal file
20
packages/client/src/svgs/FilePaths.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export default function FilePaths() {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
d="M18.833 9.66663H12.9997C12.5576 9.66663 12.1337 9.84222 11.8212 10.1548C11.5086 10.4673 11.333 10.8913 11.333 11.3333V24.6666C11.333 25.1087 11.5086 25.5326 11.8212 25.8451C12.1337 26.1577 12.5576 26.3333 12.9997 26.3333H22.9997C23.4417 26.3333 23.8656 26.1577 24.1782 25.8451C24.4907 25.5326 24.6663 25.1087 24.6663 24.6666V15.5L18.833 9.66663Z"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M18.833 9.66663V15.5H24.6663"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
8
packages/client/src/svgs/SharePointIcon.tsx
Normal file
8
packages/client/src/svgs/SharePointIcon.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
export default function SharePointIcon({ className = '' }) {
|
||||
return (
|
||||
<svg fill="currentColor" width="24" height="24" viewBox="0 0 24 24" className={className}>
|
||||
<path d="M24 13.5q0 1.242-.475 2.332-.474 1.09-1.289 1.904-.814.815-1.904 1.29-1.09.474-2.332.474-.762 0-1.523-.2-.106.997-.557 1.858-.451.862-1.154 1.494-.704.633-1.606.99-.902.358-1.91.358-1.09 0-2.045-.416-.955-.416-1.664-1.125-.709-.709-1.125-1.664Q6 19.84 6 18.75q0-.188.018-.375.017-.188.04-.375H.997q-.41 0-.703-.293T0 17.004V6.996q0-.41.293-.703T.996 6h3.54q.14-1.277.726-2.373.586-1.096 1.488-1.904Q7.652.914 8.807.457 9.96 0 11.25 0q1.395 0 2.625.533T16.02 1.98q.914.915 1.447 2.145T18 6.75q0 .188-.012.375-.011.188-.035.375 1.242 0 2.344.469 1.101.468 1.928 1.277.826.809 1.3 1.904Q24 12.246 24 13.5zm-12.75-12q-.973 0-1.857.34-.885.34-1.577.943-.691.604-1.154 1.43Q6.2 5.039 6.06 6h4.945q.41 0 .703.293t.293.703v4.945l.21-.035q.212-.75.61-1.424.399-.673.944-1.218.545-.545 1.213-.944.668-.398 1.43-.61.093-.503.093-.96 0-1.09-.416-2.045-.416-.955-1.125-1.664-.709-.709-1.664-1.125Q12.34 1.5 11.25 1.5zM6.117 15.902q.54 0 1.06-.111.522-.111.932-.37.41-.257.662-.679.252-.422.252-1.055 0-.632-.263-1.054-.264-.422-.662-.703-.399-.282-.856-.463l-.855-.34q-.399-.158-.662-.334-.264-.176-.264-.445 0-.2.14-.323.141-.123.335-.193.193-.07.404-.094.21-.023.351-.023.598 0 1.055.152.457.153.95.457V8.543q-.282-.082-.522-.14-.24-.06-.475-.1-.234-.041-.486-.059-.252-.017-.557-.017-.515 0-1.054.117-.54.117-.979.375-.44.258-.715.68-.275.421-.275 1.03 0 .598.263.997.264.398.663.68.398.28.855.474l.856.363q.398.17.662.358.263.187.263.457 0 .222-.123.351-.123.13-.31.2-.188.07-.393.087-.205.018-.369.018-.703 0-1.248-.234-.545-.235-1.107-.621v1.875q1.195.468 2.472.468zM11.25 22.5q.773 0 1.453-.293t1.19-.803q.51-.51.808-1.195.299-.686.299-1.459 0-.668-.223-1.277-.222-.61-.62-1.096-.4-.486-.95-.826-.55-.34-1.207-.48v1.933q0 .41-.293.703t-.703.293H7.57q-.07.375-.07.75 0 .773.293 1.459t.803 1.195q.51.51 1.195.803.686.293 1.459.293zM18 18q.926 0 1.746-.352.82-.351 1.436-.966.615-.616.966-1.43.352-.815.352-1.752 0-.926-.352-1.746-.351-.82-.966-1.436-.616-.615-1.436-.966Q18.926 9 18 9t-1.74.357q-.815.358-1.43.973t-.973 1.43q-.357.814-.357 1.74 0 .129.006.258t.017.258q.551.27 1.02.65t.838.855q.369.475.627 1.026.258.55.387 1.148Q17.18 18 18 18Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
13
packages/client/src/svgs/SheetPaths.tsx
Normal file
13
packages/client/src/svgs/SheetPaths.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export default function SheetPaths() {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
d="M15.5 10.5H12.1667C11.2462 10.5 10.5 11.2462 10.5 12.1667V13.5V18M15.5 10.5H23.8333C24.7538 10.5 25.5 11.2462 25.5 12.1667V13.5V18M15.5 10.5V25.5M15.5 25.5H18H23.8333C24.7538 25.5 25.5 24.7538 25.5 23.8333V18M15.5 25.5H12.1667C11.2462 25.5 10.5 24.7538 10.5 23.8333V18M10.5 18H25.5"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
41
packages/client/src/svgs/TextPaths.tsx
Normal file
41
packages/client/src/svgs/TextPaths.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
export default function TextPaths() {
|
||||
return (
|
||||
<>
|
||||
<path
|
||||
d="M19.6663 9.66663H12.9997C12.5576 9.66663 12.1337 9.84222 11.8212 10.1548C11.5086 10.4673 11.333 10.8913 11.333 11.3333V24.6666C11.333 25.1087 11.5086 25.5326 11.8212 25.8451C12.1337 26.1577 12.5576 26.3333 12.9997 26.3333H22.9997C23.4417 26.3333 23.8656 26.1577 24.1782 25.8451C24.4907 25.5326 24.6663 25.1087 24.6663 24.6666V14.6666L19.6663 9.66663Z"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M19.667 9.66663V14.6666H24.667"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M21.3337 18.8334H14.667"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M21.3337 22.1666H14.667"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M16.3337 15.5H15.5003H14.667"
|
||||
stroke="white"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -65,3 +65,9 @@ export { default as PersonalizationIcon } from './PersonalizationIcon';
|
|||
export { default as MCPIcon } from './MCPIcon';
|
||||
export { default as VectorIcon } from './VectorIcon';
|
||||
export { default as SquirclePlusIcon } from './SquirclePlusIcon';
|
||||
export { default as CodePaths } from './CodePaths';
|
||||
export { default as FileIcon } from './FileIcon';
|
||||
export { default as FilePaths } from './FilePaths';
|
||||
export { default as SheetPaths } from './SheetPaths';
|
||||
export { default as TextPaths } from './TextPaths';
|
||||
export { default as SharePointIcon } from './SharePointIcon';
|
||||
|
|
|
|||
|
|
@ -305,3 +305,7 @@ export const verifyTwoFactorTemp = () => '/api/auth/2fa/verify-temp';
|
|||
export const memories = () => '/api/memories';
|
||||
export const memory = (key: string) => `${memories()}/${encodeURIComponent(key)}`;
|
||||
export const memoryPreferences = () => `${memories()}/preferences`;
|
||||
|
||||
// SharePoint Graph API Token
|
||||
export const graphToken = (scopes: string) =>
|
||||
`/api/auth/graph-token?scopes=${encodeURIComponent(scopes)}`;
|
||||
|
|
|
|||
|
|
@ -597,6 +597,11 @@ export type TStartupConfig = {
|
|||
instanceProjectId: string;
|
||||
bundlerURL?: string;
|
||||
staticBundlerURL?: string;
|
||||
sharePointFilePickerEnabled?: boolean;
|
||||
sharePointBaseUrl?: string;
|
||||
sharePointPickerGraphScope?: string;
|
||||
sharePointPickerSharePointScope?: string;
|
||||
openidReuseTokens?: boolean;
|
||||
webSearch?: {
|
||||
searchProvider?: SearchProviders;
|
||||
scraperType?: ScraperTypes;
|
||||
|
|
|
|||
|
|
@ -858,3 +858,8 @@ export const createMemory = (data: {
|
|||
}): Promise<{ created: boolean; memory: q.TUserMemory }> => {
|
||||
return request.post(endpoints.memories(), data);
|
||||
};
|
||||
|
||||
// SharePoint Graph API Token
|
||||
export function getGraphApiToken(params: q.GraphTokenParams): Promise<q.GraphTokenResponse> {
|
||||
return request.get(endpoints.graphToken(params.scopes));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export enum QueryKeys {
|
|||
banner = 'banner',
|
||||
/* Memories */
|
||||
memories = 'memories',
|
||||
graphToken = 'graphToken',
|
||||
}
|
||||
|
||||
// Dynamic query keys that require parameters
|
||||
|
|
|
|||
|
|
@ -147,3 +147,15 @@ export interface MCPAuthValuesResponse {
|
|||
serverName: string;
|
||||
authValueFlags: Record<string, boolean>;
|
||||
}
|
||||
|
||||
/* SharePoint Graph API Token */
|
||||
export type GraphTokenParams = {
|
||||
scopes: string;
|
||||
};
|
||||
|
||||
export type GraphTokenResponse = {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
scope: string;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue