🎨 feat: Unified file upload UX — single attach button

Phase 3 of the unified file experience. When defaultFileInteraction is
configured (not 'legacy'), the attach file menu is replaced with a single
click-to-upload button that accepts all file types. Files are uploaded
without a tool_resource and provisioned lazily at chat time.

- AttachFileMenu: render single button in unified mode, legacy dropdown
  when defaultFileInteraction is 'legacy'
- validateFiles: accept union of all supported MIME types (provider +
  text + ocr + stt) in unified mode
- useFileHandling: already supports undefined tool_resource, no changes needed
This commit is contained in:
Danny Avila 2026-03-21 18:16:50 -04:00
parent b6fe36f4e5
commit 90c20d2ad6
2 changed files with 57 additions and 2 deletions

View file

@ -114,6 +114,10 @@ const AttachFileMenu = ({
ephemeralAgent,
);
const isUnifiedMode =
endpointFileConfig?.defaultFileInteraction != null &&
endpointFileConfig.defaultFileInteraction !== 'legacy';
const handleUploadClick = useCallback(
(fileType?: FileUploadType) => {
if (!inputRef.current) {
@ -139,11 +143,16 @@ const AttachFileMenu = ({
inputRef.current.accept = '';
}
inputRef.current.click();
inputRef.current.accept = '';
},
[endpointFileConfig?.supportedMimeTypes],
);
/** Unified mode: single click triggers file upload with no tool_resource */
const handleUnifiedUpload = () => {
setToolResource(undefined);
handleUploadClick();
};
const dropdownItems = useMemo(() => {
const setToolResource = (value: EToolResources | undefined) => {
toolResourceRef.current = value;
@ -305,6 +314,47 @@ const AttachFileMenu = ({
}
};
if (isUnifiedMode) {
return (
<>
<FileUpload
ref={inputRef}
handleFileChange={(e) => {
handleFileChange(e, toolResource);
}}
>
<TooltipAnchor
render={
<button
type="button"
disabled={isUploadDisabled}
id="attach-file-button"
aria-label={localize('com_sidepanel_attach_files')}
onClick={handleUnifiedUpload}
className="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"
>
<div className="flex w-full items-center justify-center gap-2">
<AttachmentIcon />
</div>
</button>
}
id="attach-file-button"
description={localize('com_sidepanel_attach_files')}
disabled={isUploadDisabled}
/>
</FileUpload>
<SharePointPickerDialog
isOpen={isSharePointDialogOpen}
onOpenChange={setIsSharePointDialogOpen}
onFilesSelected={handleSharePointFilesSelected}
isDownloading={isProcessing}
downloadProgress={downloadProgress}
maxSelectionCount={endpointFileConfig?.fileLimit}
/>
</>
);
}
return (
<>
<FileUpload

View file

@ -273,8 +273,13 @@ export const validateFiles = ({
}
let mimeTypesToCheck = supportedMimeTypes;
if (toolResource === EToolResources.context) {
const isUnifiedMode =
!toolResource &&
endpointFileConfig.defaultFileInteraction != null &&
endpointFileConfig.defaultFileInteraction !== 'legacy';
if (toolResource === EToolResources.context || isUnifiedMode) {
mimeTypesToCheck = [
...(supportedMimeTypes || []),
...(fileConfig?.text?.supportedMimeTypes || []),
...(fileConfig?.ocr?.supportedMimeTypes || []),
...(fileConfig?.stt?.supportedMimeTypes || []),