From 7f97c3d1ed26ca2865f1459f640b2d6549789bd7 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa <72977554+Cristhianzl@users.noreply.github.com> Date: Thu, 4 Jul 2024 17:25:57 -0300 Subject: [PATCH] refactor: add new use-query endpoint of upload files (#2533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: add new use-query endpoint of upload files * extend type for payload callbackSuccess * [autofix.ci] apply automated fixes * bugfix: drag and drop image on chat not working * Refactored query function to use Options * Used dedicated function as queryFn * ✨ (API): export use-post-upload-file in files index ♻️ (FileInput): refactor file upload mutation to use onSuccess and onError ♻️ (chatInput): refactor file upload mutation to use onSuccess and onError ♻️ (chatView): refactor file upload mutation to use onSuccess and onError * ♻️ (use-get-download-images.ts): simplify getDownloadImagesFn function ♻️ (use-get-transactions.ts): simplify getTransactionsFn function ✨ (use-handle-file-change.tsx): add hook to handle file input changes ✨ (use-upload.tsx): add hook to handle file paste events * Added type on Request Processor --------- Co-authored-by: anovazzi1 Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lucas Oliveira (cherry picked from commit bfaac8b4bae9c7444770fe577336cebccfc2ecdc) --- src/frontend/src/App.tsx | 1 - .../components/inputFileComponent/index.tsx | 40 ++++++---- .../src/controllers/API/helpers/constants.ts | 1 + .../queries/api-keys/use-post-add-api-key.ts | 20 +---- .../controllers/API/queries/files/index.ts | 3 + .../queries/files/use-get-download-images.ts | 38 +++++++++ .../queries/files/use-get-profile-pictures.ts | 36 +++++++++ .../API/queries/files/use-post-upload-file.ts | 40 ++++++++++ .../transactions/use-get-transactions.ts | 19 ++--- .../API/services/request-processor.ts | 25 +++++- .../components/FileInput/index.tsx | 26 +++--- .../chatInput/hooks/use-drag-and-drop.tsx | 39 +-------- .../chatInput/hooks/use-file-upload.tsx | 27 ------- .../hooks/use-handle-file-change.tsx | 4 +- .../chatView/chatInput/hooks/use-upload.tsx | 2 - .../components/chatView/chatInput/index.tsx | 73 ++++++++++++++++- .../IOModal/components/chatView/index.tsx | 79 +++++++++++++++++-- .../components/ProfilePictureForm/index.tsx | 53 +++++-------- .../SettingsPage/pages/GeneralPage/index.tsx | 10 ++- src/frontend/src/types/api/index.ts | 10 +-- 20 files changed, 362 insertions(+), 184 deletions(-) create mode 100644 src/frontend/src/controllers/API/queries/files/index.ts create mode 100644 src/frontend/src/controllers/API/queries/files/use-get-download-images.ts create mode 100644 src/frontend/src/controllers/API/queries/files/use-get-profile-pictures.ts create mode 100644 src/frontend/src/controllers/API/queries/files/use-post-upload-file.ts delete mode 100644 src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-upload.tsx diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index b1891bb7e12..ecf5cba0707 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -38,7 +38,6 @@ export default function App() { const dark = useDarkStore((state) => state.dark); const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders); - useGetVersionQuery(undefined, "updateState"); const [isLoadingHealth, setIsLoadingHealth] = useState(false); useEffect(() => { diff --git a/src/frontend/src/components/inputFileComponent/index.tsx b/src/frontend/src/components/inputFileComponent/index.tsx index f8dd9725f2b..63f0ae1d006 100644 --- a/src/frontend/src/components/inputFileComponent/index.tsx +++ b/src/frontend/src/components/inputFileComponent/index.tsx @@ -1,3 +1,4 @@ +import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; import { useEffect, useState } from "react"; import { CONSOLE_ERROR_MSG, @@ -46,6 +47,8 @@ export default function InputFileComponent({ setMyValue(value); }, [value]); + const mutation = usePostUploadFile(); + const handleButtonClick = (): void => { // Create a file input element const input = document.createElement("input"); @@ -63,24 +66,27 @@ export default function InputFileComponent({ // Check if the file type is correct if (file && checkFileType(file.name)) { // Upload the file - uploadFile(file, currentFlowId) - .then((res) => res.data) - .then((data) => { - // Get the file name from the response - const { file_path } = data; + mutation.mutate( + { file, id: currentFlowId }, + { + onSuccess: (data) => { + // Get the file name from the response + const { file_path } = data; - // sets the value that goes to the backend - onFileChange(file_path); - // Update the state and callback with the name of the file - // sets the value to the user - setMyValue(file.name); - onChange(file.name); - setLoading(false); - }) - .catch(() => { - console.error(CONSOLE_ERROR_MSG); - setLoading(false); - }); + // sets the value that goes to the backend + onFileChange(file_path); + // Update the state and on with the name of the file + // sets the value to the user + setMyValue(file.name); + onChange(file.name); + setLoading(false); + }, + onError: () => { + console.error(CONSOLE_ERROR_MSG); + setLoading(false); + }, + }, + ); } else { // Show an error if the file type is not allowed setErrorData({ diff --git a/src/frontend/src/controllers/API/helpers/constants.ts b/src/frontend/src/controllers/API/helpers/constants.ts index d14f277499b..18c34f07699 100644 --- a/src/frontend/src/controllers/API/helpers/constants.ts +++ b/src/frontend/src/controllers/API/helpers/constants.ts @@ -3,6 +3,7 @@ import { BASE_URL_API } from "../../../constants/constants"; export const URLs = { TRANSACTIONS: `monitor/transactions`, API_KEY: `api_key`, + FILES: `files`, VERSION: `version`, } as const; diff --git a/src/frontend/src/controllers/API/queries/api-keys/use-post-add-api-key.ts b/src/frontend/src/controllers/API/queries/api-keys/use-post-add-api-key.ts index dce822647ef..fd0eb5d35d4 100644 --- a/src/frontend/src/controllers/API/queries/api-keys/use-post-add-api-key.ts +++ b/src/frontend/src/controllers/API/queries/api-keys/use-post-add-api-key.ts @@ -9,10 +9,9 @@ interface IPostAddApiKey { } // add types for error handling and success -export const usePostAddApiKey: useMutationFunctionType = ({ - callbackSuccess, - callbackError, -}) => { +export const usePostAddApiKey: useMutationFunctionType = ( + options, +) => { const { mutate } = UseRequestProcessor(); const postAddApiKeyFn = async (payload: IPostAddApiKey): Promise => { @@ -27,18 +26,7 @@ export const usePostAddApiKey: useMutationFunctionType = ({ const res = await postAddApiKeyFn(payload); return res.data; }, - { - onError: (err) => { - if (callbackError) { - callbackError(err); - } - }, - onSuccess: (data) => { - if (callbackSuccess) { - callbackSuccess(data); - } - }, - }, + options, ); return mutation; diff --git a/src/frontend/src/controllers/API/queries/files/index.ts b/src/frontend/src/controllers/API/queries/files/index.ts new file mode 100644 index 00000000000..54d5f7070c1 --- /dev/null +++ b/src/frontend/src/controllers/API/queries/files/index.ts @@ -0,0 +1,3 @@ +export * from "./use-get-download-images"; +export * from "./use-get-profile-pictures"; +export * from "./use-post-upload-file"; diff --git a/src/frontend/src/controllers/API/queries/files/use-get-download-images.ts b/src/frontend/src/controllers/API/queries/files/use-get-download-images.ts new file mode 100644 index 00000000000..e1173d876d2 --- /dev/null +++ b/src/frontend/src/controllers/API/queries/files/use-get-download-images.ts @@ -0,0 +1,38 @@ +import { keepPreviousData } from "@tanstack/react-query"; +import { useQueryFunctionType } from "../../../../types/api"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface DownloadImagesQueryParams { + flowId: string; + fileName: string; +} + +export interface DownloadImagesResponse { + response: string; +} + +export const useGetDownloadImagesQuery: useQueryFunctionType< + DownloadImagesQueryParams, + DownloadImagesResponse +> = ({ flowId, fileName }) => { + const { query } = UseRequestProcessor(); + + const getDownloadImagesFn = async () => { + const response = await api.get( + `${getURL("FILES")}/images/${flowId}/${fileName}`, + ); + return response["data"]; + }; + + const queryResult = query( + ["useGetDownloadImagesQuery"], + getDownloadImagesFn, + { + placeholderData: keepPreviousData, + }, + ); + + return queryResult; +}; diff --git a/src/frontend/src/controllers/API/queries/files/use-get-profile-pictures.ts b/src/frontend/src/controllers/API/queries/files/use-get-profile-pictures.ts new file mode 100644 index 00000000000..69e7d0fb4ab --- /dev/null +++ b/src/frontend/src/controllers/API/queries/files/use-get-profile-pictures.ts @@ -0,0 +1,36 @@ +import { keepPreviousData } from "@tanstack/react-query"; +import { useQueryFunctionType } from "../../../../types/api"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface ProfilePicturesQueryParams {} + +export interface ProfilePicturesResponse { + files: string[]; +} + +export const useGetProfilePicturesQuery: useQueryFunctionType< + ProfilePicturesQueryParams, + ProfilePicturesResponse +> = () => { + const { query } = UseRequestProcessor(); + + const getProfilePicturesFn = async () => { + const response = await api.get( + `${getURL("FILES")}/profile_pictures/list`, + ); + + return response.data; + }; + + const queryResult = query( + ["useGetProfilePicturesQuery"], + getProfilePicturesFn, + { + placeholderData: keepPreviousData, + }, + ); + + return queryResult; +}; diff --git a/src/frontend/src/controllers/API/queries/files/use-post-upload-file.ts b/src/frontend/src/controllers/API/queries/files/use-post-upload-file.ts new file mode 100644 index 00000000000..532476c126d --- /dev/null +++ b/src/frontend/src/controllers/API/queries/files/use-post-upload-file.ts @@ -0,0 +1,40 @@ +import { useMutationFunctionType } from "@/types/api"; +import { UseMutationResult } from "@tanstack/react-query"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface IPostUploadFile { + file: File; + id: string; +} + +export const usePostUploadFile: useMutationFunctionType = ( + options?, +) => { + const { mutate } = UseRequestProcessor(); + + const postUploadFileFn = async (payload: IPostUploadFile): Promise => { + const formData = new FormData(); + formData.append("file", payload.file); + + const response = await api.post( + `${getURL("FILES")}/upload/${payload.id}`, + formData, + ); + + return response.data; + }; + + const mutation: UseMutationResult = + mutate( + ["usePostUploadFile"], + async (payload: IPostUploadFile) => { + const res = await postUploadFileFn(payload); + return res; + }, + options, + ); + + return mutation; +}; diff --git a/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts b/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts index f99e81f32f5..74a175e5cbf 100644 --- a/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts +++ b/src/frontend/src/controllers/API/queries/transactions/use-get-transactions.ts @@ -39,29 +39,24 @@ export const useGetTransactionsQuery: useQueryFunctionType< } }; - const getTransactionsFn = async (id: string, params = {}) => { + const getTransactionsFn = async () => { const config = {}; config["params"] = { flow_id: id }; if (params) { config["params"] = { ...config["params"], ...params }; } - return await api.get( + const rows = await api.get( `${getURL("TRANSACTIONS")}`, config, ); + + return responseFn(rows); }; - const queryResult = query( - ["useGetTransactionsQuery"], - async () => { - const rows = await getTransactionsFn(id, params); - return responseFn(rows); - }, - { - placeholderData: keepPreviousData, - }, - ); + const queryResult = query(["useGetTransactionsQuery"], getTransactionsFn, { + placeholderData: keepPreviousData, + }); return queryResult; }; diff --git a/src/frontend/src/controllers/API/services/request-processor.ts b/src/frontend/src/controllers/API/services/request-processor.ts index bb85301dbc2..a61ef13e53e 100644 --- a/src/frontend/src/controllers/API/services/request-processor.ts +++ b/src/frontend/src/controllers/API/services/request-processor.ts @@ -1,4 +1,10 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + useMutation, + UseMutationOptions, + useQuery, + useQueryClient, + UseQueryOptions, +} from "@tanstack/react-query"; import { MutationFunctionType, QueryFunctionType } from "../../../types/api"; export function UseRequestProcessor(): { @@ -7,7 +13,11 @@ export function UseRequestProcessor(): { } { const queryClient = useQueryClient(); - function query(queryKey, queryFn, options = {}) { + function query( + queryKey: UseQueryOptions["queryKey"], + queryFn: UseQueryOptions["queryFn"], + options: Omit = {}, + ) { return useQuery({ queryKey, queryFn, @@ -15,11 +25,18 @@ export function UseRequestProcessor(): { }); } - function mutate(mutationKey, mutationFn, options = {}) { + function mutate( + mutationKey: UseMutationOptions["mutationKey"], + mutationFn: UseMutationOptions["mutationFn"], + options: Omit = {}, + ) { return useMutation({ mutationKey, mutationFn, - onSettled: () => queryClient.invalidateQueries(mutationKey), + onSettled: (data, error, variables, context) => { + queryClient.invalidateQueries({ queryKey: mutationKey }); + options.onSettled && options.onSettled(data, error, variables, context); + }, ...options, }); } diff --git a/src/frontend/src/modals/IOModal/components/IOFieldView/components/FileInput/index.tsx b/src/frontend/src/modals/IOModal/components/IOFieldView/components/FileInput/index.tsx index 465de179f62..58ac9f05e8c 100644 --- a/src/frontend/src/modals/IOModal/components/IOFieldView/components/FileInput/index.tsx +++ b/src/frontend/src/modals/IOModal/components/IOFieldView/components/FileInput/index.tsx @@ -1,5 +1,6 @@ import { Button } from "../../../../../../components/ui/button"; +import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; import { useEffect, useState } from "react"; import IconComponent from "../../../../../../components/genericIconComponent"; import { BASE_URL_API } from "../../../../../../constants/constants"; @@ -66,6 +67,8 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) { setIsDragging(false); }; + const mutation = usePostUploadFile(); + const upload = async (file) => { if (file) { // Check if a file was selected @@ -80,17 +83,18 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) { document.body.appendChild(imgElement); // Add the image to the body or replace this with your desired location }; fileReader.readAsDataURL(file); - - uploadFile(file, currentFlowId) - .then((res) => res.data) - .then((data) => { - // Get the file name from the response - const { file_path, flowId } = data; - setFilePath(file_path); - }) - .catch(() => { - console.error("Error occurred while uploading file"); - }); + mutation.mutate( + { file, id: currentFlowId }, + { + onSuccess: (data) => { + const { file_path } = data; + setFilePath(file_path); + }, + onError: () => { + console.error("Error occurred while uploading file"); + }, + }, + ); } }; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx index 9b30fba419d..36ba4803a18 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-drag-and-drop.tsx @@ -4,7 +4,7 @@ import { FS_ERROR_TEXT, SN_ERROR_TEXT, } from "../../../../../../constants/constants"; -import useFileUpload from "./use-file-upload"; +// import useFileUpload from "./use-file-upload"; const useDragAndDrop = ( setIsDragging: (value: boolean) => void, @@ -31,48 +31,11 @@ const useDragAndDrop = ( setIsDragging(false); }; - const onDrop = (e) => { - e.preventDefault(); - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - handleFiles(e.dataTransfer.files, setFiles, currentFlowId, setErrorData); - e.dataTransfer.clearData(); - } - setIsDragging(false); - }; return { dragOver, dragEnter, dragLeave, - onDrop, }; }; -const handleFiles = (files, setFiles, currentFlowId, setErrorData) => { - if (files) { - const file = files?.[0]; - const fileExtension = file.name.split(".").pop()?.toLowerCase(); - if ( - !fileExtension || - !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension) - ) { - console.log("Error uploading file"); - setErrorData({ - title: "Error uploading file", - list: [FS_ERROR_TEXT, SN_ERROR_TEXT], - }); - return; - } - const uid = new ShortUniqueId(); - const id = uid.randomUUID(3); - const type = files[0].type.split("/")[0]; - const blob = files[0]; - - setFiles((prevFiles) => [ - ...prevFiles, - { file: blob, loading: true, error: false, id, type }, - ]); - - useFileUpload(blob, currentFlowId, setFiles, id); - } -}; export default useDragAndDrop; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-upload.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-upload.tsx deleted file mode 100644 index 4acc2ed1bf1..00000000000 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-file-upload.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { uploadFile } from "../../../../../../controllers/API"; - -const useFileUpload = (blob, currentFlowId, setFiles, id) => { - uploadFile(blob, currentFlowId) - .then((res) => { - setFiles((prev) => { - const newFiles = [...prev]; - const updatedIndex = newFiles.findIndex((file) => file.id === id); - newFiles[updatedIndex].loading = false; - newFiles[updatedIndex].path = res.data.file_path; - return newFiles; - }); - }) - .catch(() => { - setFiles((prev) => { - const newFiles = [...prev]; - const updatedIndex = newFiles.findIndex((file) => file.id === id); - newFiles[updatedIndex].loading = false; - newFiles[updatedIndex].error = true; - return newFiles; - }); - }); - - return null; -}; - -export default useFileUpload; diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.tsx index 748187133ca..7b7fcd5048a 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-handle-file-change.tsx @@ -5,7 +5,7 @@ import { SN_ERROR_TEXT, } from "../../../../../../constants/constants"; import useAlertStore from "../../../../../../stores/alertStore"; -import useFileUpload from "./use-file-upload"; +import handleFileUpload from "../helpers/handle-file-upload"; export const useHandleFileChange = (setFiles, currentFlowId) => { const setErrorData = useAlertStore((state) => state.setErrorData); @@ -39,7 +39,7 @@ export const useHandleFileChange = (setFiles, currentFlowId) => { { file: blob, loading: true, error: false, id, type }, ]); - useFileUpload(blob, currentFlowId, setFiles, id); + handleFileUpload(blob, currentFlowId, setFiles, id); } // Clear the file input value to ensure the change event is triggered even for the same file diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.tsx index 29bc86e6a39..b6b31647fe1 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/hooks/use-upload.tsx @@ -8,7 +8,6 @@ import { } from "../../../../../../constants/constants"; import useAlertStore from "../../../../../../stores/alertStore"; import { UploadFileTypeAPI } from "../../../../../../types/api"; -import useFileUpload from "./use-file-upload"; const useUpload = ( uploadFile: ( @@ -49,7 +48,6 @@ const useUpload = ( ...prevFiles, { file: blob, loading: true, error: false, id, type }, ]); - useFileUpload(blob, currentFlowId, setFiles, id); } } } diff --git a/src/frontend/src/modals/IOModal/components/chatView/chatInput/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/chatInput/index.tsx index a3c070a5f4a..4ffe967a9d2 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/chatInput/index.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/chatInput/index.tsx @@ -1,7 +1,13 @@ +import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; +import useAlertStore from "@/stores/alertStore"; import { useRef, useState } from "react"; +import ShortUniqueId from "short-unique-id"; import { + ALLOWED_IMAGE_INPUT_EXTENSIONS, CHAT_INPUT_PLACEHOLDER, CHAT_INPUT_PLACEHOLDER_SEND, + FS_ERROR_TEXT, + SN_ERROR_TEXT, } from "../../../../../constants/constants"; import { uploadFile } from "../../../../../controllers/API"; import useFlowsManagerStore from "../../../../../stores/flowsManagerStore"; @@ -16,8 +22,6 @@ import UploadFileButton from "./components/uploadFileButton"; import { getClassNamesFilePreview } from "./helpers/get-class-file-preview"; import useAutoResizeTextArea from "./hooks/use-auto-resize-text-area"; import useFocusOnUnlock from "./hooks/use-focus-unlock"; -import useHandleFileChange from "./hooks/use-handle-file-change"; -import useUpload from "./hooks/use-upload"; export default function ChatInput({ lockChat, chatValue, @@ -34,11 +38,72 @@ export default function ChatInput({ const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const [inputFocus, setInputFocus] = useState(false); const fileInputRef = useRef(null); + const setErrorData = useAlertStore((state) => state.setErrorData); + const [id, setId] = useState(""); useFocusOnUnlock(lockChat, inputRef); useAutoResizeTextArea(chatValue, inputRef); - useUpload(uploadFile, currentFlowId, setFiles, lockChat || saveLoading); - const { handleFileChange } = useHandleFileChange(setFiles, currentFlowId); + + const handleFileChange = async ( + event: React.ChangeEvent, + ) => { + const fileInput = event.target; + const file = fileInput.files?.[0]; + if (file) { + const fileExtension = file.name.split(".").pop()?.toLowerCase(); + + if ( + !fileExtension || + !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension) + ) { + setErrorData({ + title: "Error uploading file", + list: [FS_ERROR_TEXT, SN_ERROR_TEXT], + }); + return; + } + + const uid = new ShortUniqueId(); + const id = uid.randomUUID(10); + setId(id); + + const type = file.type.split("/")[0]; + const blob = file; + + setFiles((prevFiles) => [ + ...prevFiles, + { file: blob, loading: true, error: false, id, type }, + ]); + + mutation.mutate( + { file: blob, id: currentFlowId }, + { + onSuccess: (data) => { + setFiles((prev) => { + const newFiles = [...prev]; + const updatedIndex = newFiles.findIndex((file) => file.id === id); + newFiles[updatedIndex].loading = false; + newFiles[updatedIndex].path = data.file_path; + return newFiles; + }); + }, + onError: () => { + setFiles((prev) => { + const newFiles = [...prev]; + const updatedIndex = newFiles.findIndex((file) => file.id === id); + newFiles[updatedIndex].loading = false; + newFiles[updatedIndex].error = true; + return newFiles; + }); + }, + }, + ); + } + + fileInput.value = ""; + }; + + const mutation = usePostUploadFile(); const send = () => { sendMessage({ diff --git a/src/frontend/src/modals/IOModal/components/chatView/index.tsx b/src/frontend/src/modals/IOModal/components/chatView/index.tsx index e8b0ae09267..018b3459d64 100644 --- a/src/frontend/src/modals/IOModal/components/chatView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/chatView/index.tsx @@ -1,10 +1,14 @@ +import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file"; import { useEffect, useRef, useState } from "react"; +import ShortUniqueId from "short-unique-id"; import IconComponent from "../../../../components/genericIconComponent"; import { Button } from "../../../../components/ui/button"; import { + ALLOWED_IMAGE_INPUT_EXTENSIONS, CHAT_FIRST_INITIAL_TEXT, CHAT_SECOND_INITIAL_TEXT, - EMPTY_INPUT_SEND_MESSAGE, + FS_ERROR_TEXT, + SN_ERROR_TEXT, } from "../../../../constants/constants"; import { deleteFlowPool } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; @@ -34,8 +38,8 @@ export default function ChatView({ const inputTypes = inputs.map((obj) => obj.type); const inputIds = inputs.map((obj) => obj.id); const outputIds = outputs.map((obj) => obj.id); - const outputTypes = outputs.map((obj) => obj.type); const updateFlowPool = useFlowStore((state) => state.updateFlowPool); + const [id, setId] = useState(""); //build chat history useEffect(() => { @@ -102,11 +106,6 @@ export default function ChatView({ } }, []); - async function sendAll(data: sendAllProps): Promise {} - useEffect(() => { - if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); - }, []); - const ref = useRef(null); useEffect(() => { @@ -150,13 +149,77 @@ export default function ChatView({ const [files, setFiles] = useState([]); const [isDragging, setIsDragging] = useState(false); - const { dragOver, dragEnter, dragLeave, onDrop } = useDragAndDrop( + const { dragOver, dragEnter, dragLeave } = useDragAndDrop( setIsDragging, setFiles, currentFlowId, setErrorData, ); + const onDrop = (e) => { + e.preventDefault(); + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + handleFiles(e.dataTransfer.files, setFiles, currentFlowId, setErrorData); + e.dataTransfer.clearData(); + } + setIsDragging(false); + }; + + const handleFiles = (files, setFiles, currentFlowId, setErrorData) => { + if (files) { + const file = files?.[0]; + const fileExtension = file.name.split(".").pop()?.toLowerCase(); + if ( + !fileExtension || + !ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension) + ) { + console.log("Error uploading file"); + setErrorData({ + title: "Error uploading file", + list: [FS_ERROR_TEXT, SN_ERROR_TEXT], + }); + return; + } + const uid = new ShortUniqueId(); + const id = uid.randomUUID(3); + setId(id); + + const type = files[0].type.split("/")[0]; + const blob = files[0]; + + setFiles((prevFiles) => [ + ...prevFiles, + { file: blob, loading: true, error: false, id, type }, + ]); + + mutation.mutate( + { file: blob, id: currentFlowId }, + { + onSuccess: (data) => { + setFiles((prev) => { + const newFiles = [...prev]; + const updatedIndex = newFiles.findIndex((file) => file.id === id); + newFiles[updatedIndex].loading = false; + newFiles[updatedIndex].path = data.file_path; + return newFiles; + }); + }, + onError: () => { + setFiles((prev) => { + const newFiles = [...prev]; + const updatedIndex = newFiles.findIndex((file) => file.id === id); + newFiles[updatedIndex].loading = false; + newFiles[updatedIndex].error = true; + return newFiles; + }); + }, + }, + ); + } + }; + + const mutation = usePostUploadFile(); + return (
void; handlePatchProfilePicture: (gradient: string) => void; - handleGetProfilePictures: () => Promise; + handleGetProfilePictures: () => ProfilePicturesResponse | undefined; userData: any; }; const ProfilePictureFormComponent = ({ @@ -29,39 +33,24 @@ const ProfilePictureFormComponent = ({ const [profilePictures, setProfilePictures] = useState<{ [key: string]: string[]; }>({}); - const [loading, setLoading] = useState(true); - useEffect(() => { - const abortController = new AbortController(); + const { data: response, isFetching } = useGetProfilePicturesQuery({}); - handleGetProfilePictures() - .then((data) => { - if (data) { - data.forEach((profile_picture) => { - const [folder, path] = profile_picture.split("/"); - setProfilePictures((prev) => { - if (prev[folder]) { - prev[folder].push(path); - } else { - prev[folder] = [path]; - } - setLoading(false); - return prev; - }); - }); - } - }) - .catch(() => { - setLoading(false); + useEffect(() => { + if (response?.files) { + response?.files?.forEach((profile_picture) => { + const [folder, path] = profile_picture.split("/"); + setProfilePictures((prev) => { + if (prev[folder]) { + prev[folder].push(path); + } else { + prev[folder] = [path]; + } + return prev; + }); }); - - /* - Abort the request as it isn't needed anymore, the component being - unmounted. It helps avoid, among other things, the well-known "can't - perform a React state update on an unmounted component" warning. - */ - return () => abortController.abort(); - }, []); + } + }, [response]); return ( { setErrorData, ); - const { handleGetProfilePictures } = useGetProfilePictures(setErrorData); + const handleGetProfilePictures = () => { + const { data } = useGetProfilePicturesQuery({}); + return data; + }; const { handlePatchProfilePicture } = usePatchProfilePicture( setSuccessData, @@ -64,14 +68,14 @@ export const GeneralPage = () => { useScrollToElement(scrollId, setCurrentFlowId); const { mutate } = usePostAddApiKey({ - callbackSuccess: () => { + onSuccess: () => { setSuccessData({ title: "API key saved successfully" }); setHasApiKey(true); setValidApiKey(true); setLoadingApiKey(false); handleInput({ target: { name: "apikey", value: "" } }); }, - callbackError: (error) => { + onError: (error) => { setErrorData({ title: "API key save error", list: [(error as any)?.response?.data?.detail], diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index e1c9173b9ad..00f4abe720b 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -249,10 +249,6 @@ export type MutationFunctionType = ( options?: Omit, "mutationFn" | "mutationKey">, ) => UseMutationResult; -export type useMutationFunctionType = ({ - callbackError, - callbackSuccess, -}: { - callbackSuccess: (data: Data) => void; - callbackError: (err: Error) => void; -}) => UseMutationResult; +export type useMutationFunctionType = ( + options?: Omit, "mutationFn" | "mutationKey">, +) => UseMutationResult;