Agent chat file drag and drop (#13226)

- Added drag and drop support
- The AIChatTab component has been refactored into smaller, more focused
components to improve readability, maintainability, and scalability of
the codebase.
- Introduced a custom useAIChatFileUpload hook to encapsulate and manage
file upload logic, promoting code reuse and separation of concerns.

### Demo

https://github.com/user-attachments/assets/c4b2a67a-2736-48ae-9ba8-8e124e4b6069

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: MD Readul Islam <99027968+readul-islam@users.noreply.github.com>
Co-authored-by: readul-islam <developer.readul@gamil.com>
Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com>
Co-authored-by: Guillim <guillim@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Jean-Baptiste Ronssin <65334819+jbronssin@users.noreply.github.com>
Co-authored-by: kahkashan shaik <93042682+kahkashanshaik@users.noreply.github.com>
Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Naifer <161821705+omarNaifer12@users.noreply.github.com>
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
Abdul Rahman
2025-07-16 17:09:54 +05:30
committed by GitHub
parent 47386e92a3
commit 0c73c9df50
6 changed files with 464 additions and 369 deletions
@@ -0,0 +1,82 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { agentChatSelectedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatSelectedFilesComponentState';
import { agentChatUploadedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatUploadedFilesComponentState';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared/utils';
import {
File as FileDocument,
useCreateFileMutation,
} from '~/generated-metadata/graphql';
export const useAIChatFileUpload = ({ agentId }: { agentId: string }) => {
const coreClient = useApolloCoreClient();
const [createFile] = useCreateFileMutation({ client: coreClient });
const { t } = useLingui();
const { enqueueErrorSnackBar } = useSnackBar();
const [agentChatSelectedFiles, setAgentChatSelectedFiles] =
useRecoilComponentStateV2(agentChatSelectedFilesComponentState, agentId);
const [agentChatUploadedFiles, setAgentChatUploadedFiles] =
useRecoilComponentStateV2(agentChatUploadedFilesComponentState, agentId);
const sendFile = async (file: File) => {
try {
const result = await createFile({
variables: {
file,
},
});
const uploadedFile = result?.data?.createFile;
if (!isDefined(uploadedFile)) {
throw new Error(t`Couldn't upload the file.`);
}
setAgentChatSelectedFiles(
agentChatSelectedFiles.filter((f) => f.name !== file.name),
);
return uploadedFile;
} catch (error) {
const fileName = file.name;
enqueueErrorSnackBar({
message: t`Failed to upload file: ${fileName}`,
});
return null;
}
};
const uploadFiles = async (files: File[]) => {
const uploadResults = await Promise.allSettled(
files.map((file) => sendFile(file)),
);
const successfulUploads = uploadResults.reduce<FileDocument[]>(
(acc, result) => {
if (result.status === 'fulfilled' && isDefined(result.value)) {
acc.push(result.value);
}
return acc;
},
[],
);
if (successfulUploads.length > 0) {
setAgentChatUploadedFiles([
...agentChatUploadedFiles,
...successfulUploads,
]);
}
const failedCount = uploadResults.filter(
(result) => result.status === 'rejected',
).length;
if (failedCount > 0) {
enqueueErrorSnackBar({
message: t`${failedCount} file(s) failed to upload`,
});
}
};
return { uploadFiles };
};