refactor: add new use-query endpoint of upload files (#2533)
* 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 <otavio2204@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
This commit is contained in:
parent
4f586adf0a
commit
bfaac8b4ba
20 changed files with 362 additions and 184 deletions
|
|
@ -38,7 +38,6 @@ export default function App() {
|
||||||
const dark = useDarkStore((state) => state.dark);
|
const dark = useDarkStore((state) => state.dark);
|
||||||
|
|
||||||
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
||||||
useGetVersionQuery(undefined, "updateState");
|
|
||||||
|
|
||||||
const [isLoadingHealth, setIsLoadingHealth] = useState(false);
|
const [isLoadingHealth, setIsLoadingHealth] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
CONSOLE_ERROR_MSG,
|
CONSOLE_ERROR_MSG,
|
||||||
|
|
@ -46,6 +47,8 @@ export default function InputFileComponent({
|
||||||
setMyValue(value);
|
setMyValue(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
const mutation = usePostUploadFile();
|
||||||
|
|
||||||
const handleButtonClick = (): void => {
|
const handleButtonClick = (): void => {
|
||||||
// Create a file input element
|
// Create a file input element
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
|
|
@ -63,24 +66,27 @@ export default function InputFileComponent({
|
||||||
// Check if the file type is correct
|
// Check if the file type is correct
|
||||||
if (file && checkFileType(file.name)) {
|
if (file && checkFileType(file.name)) {
|
||||||
// Upload the file
|
// Upload the file
|
||||||
uploadFile(file, currentFlowId)
|
mutation.mutate(
|
||||||
.then((res) => res.data)
|
{ file, id: currentFlowId },
|
||||||
.then((data) => {
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
// Get the file name from the response
|
// Get the file name from the response
|
||||||
const { file_path } = data;
|
const { file_path } = data;
|
||||||
|
|
||||||
// sets the value that goes to the backend
|
// sets the value that goes to the backend
|
||||||
onFileChange(file_path);
|
onFileChange(file_path);
|
||||||
// Update the state and callback with the name of the file
|
// Update the state and on with the name of the file
|
||||||
// sets the value to the user
|
// sets the value to the user
|
||||||
setMyValue(file.name);
|
setMyValue(file.name);
|
||||||
onChange(file.name);
|
onChange(file.name);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
},
|
||||||
.catch(() => {
|
onError: () => {
|
||||||
console.error(CONSOLE_ERROR_MSG);
|
console.error(CONSOLE_ERROR_MSG);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Show an error if the file type is not allowed
|
// Show an error if the file type is not allowed
|
||||||
setErrorData({
|
setErrorData({
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { BASE_URL_API } from "../../../constants/constants";
|
||||||
export const URLs = {
|
export const URLs = {
|
||||||
TRANSACTIONS: `monitor/transactions`,
|
TRANSACTIONS: `monitor/transactions`,
|
||||||
API_KEY: `api_key`,
|
API_KEY: `api_key`,
|
||||||
|
FILES: `files`,
|
||||||
VERSION: `version`,
|
VERSION: `version`,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ interface IPostAddApiKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add types for error handling and success
|
// add types for error handling and success
|
||||||
export const usePostAddApiKey: useMutationFunctionType<IPostAddApiKey> = ({
|
export const usePostAddApiKey: useMutationFunctionType<IPostAddApiKey> = (
|
||||||
callbackSuccess,
|
options,
|
||||||
callbackError,
|
) => {
|
||||||
}) => {
|
|
||||||
const { mutate } = UseRequestProcessor();
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
const postAddApiKeyFn = async (payload: IPostAddApiKey): Promise<any> => {
|
const postAddApiKeyFn = async (payload: IPostAddApiKey): Promise<any> => {
|
||||||
|
|
@ -27,18 +26,7 @@ export const usePostAddApiKey: useMutationFunctionType<IPostAddApiKey> = ({
|
||||||
const res = await postAddApiKeyFn(payload);
|
const res = await postAddApiKeyFn(payload);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
{
|
options,
|
||||||
onError: (err) => {
|
|
||||||
if (callbackError) {
|
|
||||||
callbackError(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSuccess: (data) => {
|
|
||||||
if (callbackSuccess) {
|
|
||||||
callbackSuccess(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return mutation;
|
return mutation;
|
||||||
|
|
|
||||||
3
src/frontend/src/controllers/API/queries/files/index.ts
Normal file
3
src/frontend/src/controllers/API/queries/files/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./use-get-download-images";
|
||||||
|
export * from "./use-get-profile-pictures";
|
||||||
|
export * from "./use-post-upload-file";
|
||||||
|
|
@ -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<DownloadImagesResponse>(
|
||||||
|
`${getURL("FILES")}/images/${flowId}/${fileName}`,
|
||||||
|
);
|
||||||
|
return response["data"];
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryResult = query(
|
||||||
|
["useGetDownloadImagesQuery"],
|
||||||
|
getDownloadImagesFn,
|
||||||
|
{
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
};
|
||||||
|
|
@ -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<ProfilePicturesResponse>(
|
||||||
|
`${getURL("FILES")}/profile_pictures/list`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryResult = query(
|
||||||
|
["useGetProfilePicturesQuery"],
|
||||||
|
getProfilePicturesFn,
|
||||||
|
{
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
};
|
||||||
|
|
@ -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<IPostUploadFile> = (
|
||||||
|
options?,
|
||||||
|
) => {
|
||||||
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
|
const postUploadFileFn = async (payload: IPostUploadFile): Promise<any> => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", payload.file);
|
||||||
|
|
||||||
|
const response = await api.post<any>(
|
||||||
|
`${getURL("FILES")}/upload/${payload.id}`,
|
||||||
|
formData,
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutation: UseMutationResult<IPostUploadFile, any, IPostUploadFile> =
|
||||||
|
mutate(
|
||||||
|
["usePostUploadFile"],
|
||||||
|
async (payload: IPostUploadFile) => {
|
||||||
|
const res = await postUploadFileFn(payload);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mutation;
|
||||||
|
};
|
||||||
|
|
@ -39,29 +39,24 @@ export const useGetTransactionsQuery: useQueryFunctionType<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTransactionsFn = async (id: string, params = {}) => {
|
const getTransactionsFn = async () => {
|
||||||
const config = {};
|
const config = {};
|
||||||
config["params"] = { flow_id: id };
|
config["params"] = { flow_id: id };
|
||||||
if (params) {
|
if (params) {
|
||||||
config["params"] = { ...config["params"], ...params };
|
config["params"] = { ...config["params"], ...params };
|
||||||
}
|
}
|
||||||
|
|
||||||
return await api.get<TransactionsResponse>(
|
const rows = await api.get<TransactionsResponse>(
|
||||||
`${getURL("TRANSACTIONS")}`,
|
`${getURL("TRANSACTIONS")}`,
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return responseFn(rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryResult = query(
|
const queryResult = query(["useGetTransactionsQuery"], getTransactionsFn, {
|
||||||
["useGetTransactionsQuery"],
|
|
||||||
async () => {
|
|
||||||
const rows = await getTransactionsFn(id, params);
|
|
||||||
return responseFn(rows);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return queryResult;
|
return queryResult;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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";
|
import { MutationFunctionType, QueryFunctionType } from "../../../types/api";
|
||||||
|
|
||||||
export function UseRequestProcessor(): {
|
export function UseRequestProcessor(): {
|
||||||
|
|
@ -7,7 +13,11 @@ export function UseRequestProcessor(): {
|
||||||
} {
|
} {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
function query(queryKey, queryFn, options = {}) {
|
function query(
|
||||||
|
queryKey: UseQueryOptions["queryKey"],
|
||||||
|
queryFn: UseQueryOptions["queryFn"],
|
||||||
|
options: Omit<UseQueryOptions, "queryFn" | "queryKey"> = {},
|
||||||
|
) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn,
|
queryFn,
|
||||||
|
|
@ -15,11 +25,18 @@ export function UseRequestProcessor(): {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function mutate(mutationKey, mutationFn, options = {}) {
|
function mutate(
|
||||||
|
mutationKey: UseMutationOptions["mutationKey"],
|
||||||
|
mutationFn: UseMutationOptions["mutationFn"],
|
||||||
|
options: Omit<UseMutationOptions, "mutationFn" | "mutationKey"> = {},
|
||||||
|
) {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey,
|
mutationKey,
|
||||||
mutationFn,
|
mutationFn,
|
||||||
onSettled: () => queryClient.invalidateQueries(mutationKey),
|
onSettled: (data, error, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: mutationKey });
|
||||||
|
options.onSettled && options.onSettled(data, error, variables, context);
|
||||||
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Button } from "../../../../../../components/ui/button";
|
import { Button } from "../../../../../../components/ui/button";
|
||||||
|
|
||||||
|
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import IconComponent from "../../../../../../components/genericIconComponent";
|
import IconComponent from "../../../../../../components/genericIconComponent";
|
||||||
import { BASE_URL_API } from "../../../../../../constants/constants";
|
import { BASE_URL_API } from "../../../../../../constants/constants";
|
||||||
|
|
@ -66,6 +67,8 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mutation = usePostUploadFile();
|
||||||
|
|
||||||
const upload = async (file) => {
|
const upload = async (file) => {
|
||||||
if (file) {
|
if (file) {
|
||||||
// Check if a file was selected
|
// 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
|
document.body.appendChild(imgElement); // Add the image to the body or replace this with your desired location
|
||||||
};
|
};
|
||||||
fileReader.readAsDataURL(file);
|
fileReader.readAsDataURL(file);
|
||||||
|
mutation.mutate(
|
||||||
uploadFile(file, currentFlowId)
|
{ file, id: currentFlowId },
|
||||||
.then((res) => res.data)
|
{
|
||||||
.then((data) => {
|
onSuccess: (data) => {
|
||||||
// Get the file name from the response
|
const { file_path } = data;
|
||||||
const { file_path, flowId } = data;
|
|
||||||
setFilePath(file_path);
|
setFilePath(file_path);
|
||||||
})
|
},
|
||||||
.catch(() => {
|
onError: () => {
|
||||||
console.error("Error occurred while uploading file");
|
console.error("Error occurred while uploading file");
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
FS_ERROR_TEXT,
|
FS_ERROR_TEXT,
|
||||||
SN_ERROR_TEXT,
|
SN_ERROR_TEXT,
|
||||||
} from "../../../../../../constants/constants";
|
} from "../../../../../../constants/constants";
|
||||||
import useFileUpload from "./use-file-upload";
|
// import useFileUpload from "./use-file-upload";
|
||||||
|
|
||||||
const useDragAndDrop = (
|
const useDragAndDrop = (
|
||||||
setIsDragging: (value: boolean) => void,
|
setIsDragging: (value: boolean) => void,
|
||||||
|
|
@ -31,48 +31,11 @@ const useDragAndDrop = (
|
||||||
setIsDragging(false);
|
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 {
|
return {
|
||||||
dragOver,
|
dragOver,
|
||||||
dragEnter,
|
dragEnter,
|
||||||
dragLeave,
|
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;
|
export default useDragAndDrop;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
SN_ERROR_TEXT,
|
SN_ERROR_TEXT,
|
||||||
} from "../../../../../../constants/constants";
|
} from "../../../../../../constants/constants";
|
||||||
import useAlertStore from "../../../../../../stores/alertStore";
|
import useAlertStore from "../../../../../../stores/alertStore";
|
||||||
import useFileUpload from "./use-file-upload";
|
import handleFileUpload from "../helpers/handle-file-upload";
|
||||||
|
|
||||||
export const useHandleFileChange = (setFiles, currentFlowId) => {
|
export const useHandleFileChange = (setFiles, currentFlowId) => {
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
@ -39,7 +39,7 @@ export const useHandleFileChange = (setFiles, currentFlowId) => {
|
||||||
{ file: blob, loading: true, error: false, id, type },
|
{ 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
|
// Clear the file input value to ensure the change event is triggered even for the same file
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import {
|
||||||
} from "../../../../../../constants/constants";
|
} from "../../../../../../constants/constants";
|
||||||
import useAlertStore from "../../../../../../stores/alertStore";
|
import useAlertStore from "../../../../../../stores/alertStore";
|
||||||
import { UploadFileTypeAPI } from "../../../../../../types/api";
|
import { UploadFileTypeAPI } from "../../../../../../types/api";
|
||||||
import useFileUpload from "./use-file-upload";
|
|
||||||
|
|
||||||
const useUpload = (
|
const useUpload = (
|
||||||
uploadFile: (
|
uploadFile: (
|
||||||
|
|
@ -49,7 +48,6 @@ const useUpload = (
|
||||||
...prevFiles,
|
...prevFiles,
|
||||||
{ file: blob, loading: true, error: false, id, type },
|
{ file: blob, loading: true, error: false, id, type },
|
||||||
]);
|
]);
|
||||||
useFileUpload(blob, currentFlowId, setFiles, id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { useRef, useState } from "react";
|
||||||
|
import ShortUniqueId from "short-unique-id";
|
||||||
import {
|
import {
|
||||||
|
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||||
CHAT_INPUT_PLACEHOLDER,
|
CHAT_INPUT_PLACEHOLDER,
|
||||||
CHAT_INPUT_PLACEHOLDER_SEND,
|
CHAT_INPUT_PLACEHOLDER_SEND,
|
||||||
|
FS_ERROR_TEXT,
|
||||||
|
SN_ERROR_TEXT,
|
||||||
} from "../../../../../constants/constants";
|
} from "../../../../../constants/constants";
|
||||||
import { uploadFile } from "../../../../../controllers/API";
|
import { uploadFile } from "../../../../../controllers/API";
|
||||||
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
||||||
|
|
@ -16,8 +22,6 @@ import UploadFileButton from "./components/uploadFileButton";
|
||||||
import { getClassNamesFilePreview } from "./helpers/get-class-file-preview";
|
import { getClassNamesFilePreview } from "./helpers/get-class-file-preview";
|
||||||
import useAutoResizeTextArea from "./hooks/use-auto-resize-text-area";
|
import useAutoResizeTextArea from "./hooks/use-auto-resize-text-area";
|
||||||
import useFocusOnUnlock from "./hooks/use-focus-unlock";
|
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({
|
export default function ChatInput({
|
||||||
lockChat,
|
lockChat,
|
||||||
chatValue,
|
chatValue,
|
||||||
|
|
@ -34,11 +38,72 @@ export default function ChatInput({
|
||||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||||
const [inputFocus, setInputFocus] = useState<boolean>(false);
|
const [inputFocus, setInputFocus] = useState<boolean>(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const [id, setId] = useState<string>("");
|
||||||
|
|
||||||
useFocusOnUnlock(lockChat, inputRef);
|
useFocusOnUnlock(lockChat, inputRef);
|
||||||
useAutoResizeTextArea(chatValue, inputRef);
|
useAutoResizeTextArea(chatValue, inputRef);
|
||||||
useUpload(uploadFile, currentFlowId, setFiles, lockChat || saveLoading);
|
|
||||||
const { handleFileChange } = useHandleFileChange(setFiles, currentFlowId);
|
const handleFileChange = async (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
|
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 = () => {
|
const send = () => {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
|
import { usePostUploadFile } from "@/controllers/API/queries/files/use-post-upload-file";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import ShortUniqueId from "short-unique-id";
|
||||||
import IconComponent from "../../../../components/genericIconComponent";
|
import IconComponent from "../../../../components/genericIconComponent";
|
||||||
import { Button } from "../../../../components/ui/button";
|
import { Button } from "../../../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||||
CHAT_FIRST_INITIAL_TEXT,
|
CHAT_FIRST_INITIAL_TEXT,
|
||||||
CHAT_SECOND_INITIAL_TEXT,
|
CHAT_SECOND_INITIAL_TEXT,
|
||||||
EMPTY_INPUT_SEND_MESSAGE,
|
FS_ERROR_TEXT,
|
||||||
|
SN_ERROR_TEXT,
|
||||||
} from "../../../../constants/constants";
|
} from "../../../../constants/constants";
|
||||||
import { deleteFlowPool } from "../../../../controllers/API";
|
import { deleteFlowPool } from "../../../../controllers/API";
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
|
|
@ -34,8 +38,8 @@ export default function ChatView({
|
||||||
const inputTypes = inputs.map((obj) => obj.type);
|
const inputTypes = inputs.map((obj) => obj.type);
|
||||||
const inputIds = inputs.map((obj) => obj.id);
|
const inputIds = inputs.map((obj) => obj.id);
|
||||||
const outputIds = outputs.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 updateFlowPool = useFlowStore((state) => state.updateFlowPool);
|
||||||
|
const [id, setId] = useState<string>("");
|
||||||
|
|
||||||
//build chat history
|
//build chat history
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -102,11 +106,6 @@ export default function ChatView({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function sendAll(data: sendAllProps): Promise<void> {}
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -150,13 +149,77 @@ export default function ChatView({
|
||||||
const [files, setFiles] = useState<FilePreviewType[]>([]);
|
const [files, setFiles] = useState<FilePreviewType[]>([]);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
|
||||||
const { dragOver, dragEnter, dragLeave, onDrop } = useDragAndDrop(
|
const { dragOver, dragEnter, dragLeave } = useDragAndDrop(
|
||||||
setIsDragging,
|
setIsDragging,
|
||||||
setFiles,
|
setFiles,
|
||||||
currentFlowId,
|
currentFlowId,
|
||||||
setErrorData,
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className="eraser-column-arrangement"
|
className="eraser-column-arrangement"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
import {
|
||||||
|
ProfilePicturesResponse,
|
||||||
|
useGetProfilePicturesQuery,
|
||||||
|
} from "@/controllers/API/queries/files";
|
||||||
import * as Form from "@radix-ui/react-form";
|
import * as Form from "@radix-ui/react-form";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button } from "../../../../../../components/ui/button";
|
import { Button } from "../../../../../../components/ui/button";
|
||||||
|
|
@ -16,7 +20,7 @@ type ProfilePictureFormComponentProps = {
|
||||||
profilePicture: string;
|
profilePicture: string;
|
||||||
handleInput: (event: any) => void;
|
handleInput: (event: any) => void;
|
||||||
handlePatchProfilePicture: (gradient: string) => void;
|
handlePatchProfilePicture: (gradient: string) => void;
|
||||||
handleGetProfilePictures: () => Promise<string[] | undefined>;
|
handleGetProfilePictures: () => ProfilePicturesResponse | undefined;
|
||||||
userData: any;
|
userData: any;
|
||||||
};
|
};
|
||||||
const ProfilePictureFormComponent = ({
|
const ProfilePictureFormComponent = ({
|
||||||
|
|
@ -29,15 +33,12 @@ const ProfilePictureFormComponent = ({
|
||||||
const [profilePictures, setProfilePictures] = useState<{
|
const [profilePictures, setProfilePictures] = useState<{
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
}>({});
|
}>({});
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
const { data: response, isFetching } = useGetProfilePicturesQuery({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const abortController = new AbortController();
|
if (response?.files) {
|
||||||
|
response?.files?.forEach((profile_picture) => {
|
||||||
handleGetProfilePictures()
|
|
||||||
.then((data) => {
|
|
||||||
if (data) {
|
|
||||||
data.forEach((profile_picture) => {
|
|
||||||
const [folder, path] = profile_picture.split("/");
|
const [folder, path] = profile_picture.split("/");
|
||||||
setProfilePictures((prev) => {
|
setProfilePictures((prev) => {
|
||||||
if (prev[folder]) {
|
if (prev[folder]) {
|
||||||
|
|
@ -45,23 +46,11 @@ const ProfilePictureFormComponent = ({
|
||||||
} else {
|
} else {
|
||||||
prev[folder] = [path];
|
prev[folder] = [path];
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
}, [response]);
|
||||||
.catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
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();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Root
|
<Form.Root
|
||||||
|
|
@ -81,7 +70,7 @@ const ProfilePictureFormComponent = ({
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<ProfilePictureChooserComponent
|
<ProfilePictureChooserComponent
|
||||||
profilePictures={profilePictures}
|
profilePictures={profilePictures}
|
||||||
loading={loading}
|
loading={isFetching}
|
||||||
value={
|
value={
|
||||||
profilePicture == ""
|
profilePicture == ""
|
||||||
? userData?.profile_image ??
|
? userData?.profile_image ??
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
||||||
|
import { useGetProfilePicturesQuery } from "@/controllers/API/queries/files";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
|
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
|
||||||
|
|
@ -52,7 +53,10 @@ export const GeneralPage = () => {
|
||||||
setErrorData,
|
setErrorData,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { handleGetProfilePictures } = useGetProfilePictures(setErrorData);
|
const handleGetProfilePictures = () => {
|
||||||
|
const { data } = useGetProfilePicturesQuery({});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
const { handlePatchProfilePicture } = usePatchProfilePicture(
|
const { handlePatchProfilePicture } = usePatchProfilePicture(
|
||||||
setSuccessData,
|
setSuccessData,
|
||||||
|
|
@ -64,14 +68,14 @@ export const GeneralPage = () => {
|
||||||
useScrollToElement(scrollId, setCurrentFlowId);
|
useScrollToElement(scrollId, setCurrentFlowId);
|
||||||
|
|
||||||
const { mutate } = usePostAddApiKey({
|
const { mutate } = usePostAddApiKey({
|
||||||
callbackSuccess: () => {
|
onSuccess: () => {
|
||||||
setSuccessData({ title: "API key saved successfully" });
|
setSuccessData({ title: "API key saved successfully" });
|
||||||
setHasApiKey(true);
|
setHasApiKey(true);
|
||||||
setValidApiKey(true);
|
setValidApiKey(true);
|
||||||
setLoadingApiKey(false);
|
setLoadingApiKey(false);
|
||||||
handleInput({ target: { name: "apikey", value: "" } });
|
handleInput({ target: { name: "apikey", value: "" } });
|
||||||
},
|
},
|
||||||
callbackError: (error) => {
|
onError: (error) => {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "API key save error",
|
title: "API key save error",
|
||||||
list: [(error as any)?.response?.data?.detail],
|
list: [(error as any)?.response?.data?.detail],
|
||||||
|
|
|
||||||
|
|
@ -249,10 +249,6 @@ export type MutationFunctionType = (
|
||||||
options?: Omit<UseMutationOptions<any, any>, "mutationFn" | "mutationKey">,
|
options?: Omit<UseMutationOptions<any, any>, "mutationFn" | "mutationKey">,
|
||||||
) => UseMutationResult<any, any, any, any>;
|
) => UseMutationResult<any, any, any, any>;
|
||||||
|
|
||||||
export type useMutationFunctionType<Variables, Data = any, Error = any> = ({
|
export type useMutationFunctionType<Variables, Data = any, Error = any> = (
|
||||||
callbackError,
|
options?: Omit<UseMutationOptions<Data, Error>, "mutationFn" | "mutationKey">,
|
||||||
callbackSuccess,
|
) => UseMutationResult<Data, Error, Variables>;
|
||||||
}: {
|
|
||||||
callbackSuccess: (data: Data) => void;
|
|
||||||
callbackError: (err: Error) => void;
|
|
||||||
}) => UseMutationResult<Data, Error, Variables>;
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue