diff --git a/api/controllers/embed.controller.ts b/api/controllers/embed.controller.ts index 31b443a..5cece31 100644 --- a/api/controllers/embed.controller.ts +++ b/api/controllers/embed.controller.ts @@ -3,28 +3,28 @@ import { CreateDocumentEmbeddingHandler } from "../handlers/create-document-embe import { documentRequestSchema } from "../lib/validation-schemas"; import { generateErrorResponse } from "../utils/utils"; import { Result } from "../lib/result"; +import multer from "multer"; export class EmbeddingController { path = "/embed"; router = express.Router(); + upload = multer(); constructor() { this.initRoutes(); } initRoutes() { - this.router.post(`${this.path}/documents`, this.createDocumentEmbeddings); + this.router.post(`${this.path}/documents`, this.upload.single("pdf"), this.createDocumentEmbeddings); } - async createDocumentEmbeddings( - req: express.Request, - res: express.Response, - next: express.NextFunction, - ) { - const embeddingHandler: CreateDocumentEmbeddingHandler = - new CreateDocumentEmbeddingHandler(); + async createDocumentEmbeddings(req: express.Request, res: express.Response, next: express.NextFunction) { + if (!req.file) { + return res.json(Result.fail("No file uploaded", 400)); + } + const file = req.file; + const { buffer } = file; + const embeddingHandler: CreateDocumentEmbeddingHandler = new CreateDocumentEmbeddingHandler(buffer); try { - const { title, documentType, domain } = documentRequestSchema.parse( - req.body, - ); + const { title, documentType, domain } = documentRequestSchema.parse(req.body); const result = await embeddingHandler.handle({ title, documentType, diff --git a/api/handlers/create-document-embed.handler.ts b/api/handlers/create-document-embed.handler.ts index bf96adc..4161ddf 100644 --- a/api/handlers/create-document-embed.handler.ts +++ b/api/handlers/create-document-embed.handler.ts @@ -6,24 +6,17 @@ import { ICreateEmbeddingRequestDTO } from "../repositories/dtos/dtos"; import { EmbeddingService } from "../services/embed.service"; import { getValue } from "../utils"; -export class CreateDocumentEmbeddingHandler - implements IRequestHandler> -{ +export class CreateDocumentEmbeddingHandler implements IRequestHandler> { + constructor(private readonly pdf: Buffer) {} private readonly apiKey: string = getValue("API_KEY"); - embeddingService: EmbeddingService = new EmbeddingService(this.apiKey); + async handle(request: ICreateEmbeddingRequestDTO): Promise> { + const embeddingService: EmbeddingService = new EmbeddingService(this.apiKey, this.pdf); try { const { title, documentType, domain } = request; - const result = await this.embeddingService.createDocumentsEmbeddings( - title, - documentType, - domain - ); + const result = await embeddingService.createDocumentsEmbeddings(title, documentType, domain); if (!result) { - throw new HttpException( - HTTP_RESPONSE_CODE.BAD_REQUEST, - "An error occured, could not create embeddings" - ); + throw new HttpException(HTTP_RESPONSE_CODE.BAD_REQUEST, "An error occured, could not create embeddings"); } return result; } catch (error) { diff --git a/api/interfaces/document-service.interface.ts b/api/interfaces/document-service.interface.ts index 90f4557..fe1cbbd 100644 --- a/api/interfaces/document-service.interface.ts +++ b/api/interfaces/document-service.interface.ts @@ -1,5 +1,5 @@ export interface IDocumentService { - convertPDFToText(pdfFilePath: string): Promise; + convertPDFToText(file: Buffer): Promise; writeToFile(outputFilePath: string, text: string): void; breakTextIntoChunks(text: string, partSize: number): string[]; adjustChunkToEndAtCharacter(chunk: string): string; diff --git a/api/package-lock.json b/api/package-lock.json index d891bb6..5c749b4 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -12,6 +12,7 @@ "@google/generative-ai": "^0.1.3", "@prisma/client": "^5.11.0", "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", "@types/pdf-parse": "^1.1.4", "@types/pg": "^8.11.0", "body-parser": "^1.20.2", @@ -21,6 +22,7 @@ "dotenv": "^16.4.1", "express": "^4.18.2", "langchain": "^0.1.9", + "multer": "^1.4.5-lts.1", "pdf-parse": "^1.1.1", "pg": "^8.11.3", "pgvector": "^0.1.7", @@ -1816,6 +1818,14 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.11.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", @@ -2065,6 +2075,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -2337,8 +2352,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/buffer-writer": { "version": "2.0.0", @@ -2348,6 +2362,17 @@ "node": ">=4" } }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2551,6 +2576,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/concurrently": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", @@ -2615,6 +2654,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -3510,6 +3554,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4753,6 +4802,25 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ml-array-mean": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ml-array-mean/-/ml-array-mean-1.1.6.tgz", @@ -4798,6 +4866,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5412,6 +5497,11 @@ "node": ">=16.13" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5495,6 +5585,25 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -5787,6 +5896,27 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6099,6 +6229,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", @@ -6155,6 +6290,11 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/api/package.json b/api/package.json index ec51ed3..246b0bd 100644 --- a/api/package.json +++ b/api/package.json @@ -21,6 +21,7 @@ "@google/generative-ai": "^0.1.3", "@prisma/client": "^5.11.0", "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", "@types/pdf-parse": "^1.1.4", "@types/pg": "^8.11.0", "body-parser": "^1.20.2", @@ -30,6 +31,7 @@ "dotenv": "^16.4.1", "express": "^4.18.2", "langchain": "^0.1.9", + "multer": "^1.4.5-lts.1", "pdf-parse": "^1.1.1", "pg": "^8.11.3", "pgvector": "^0.1.7", diff --git a/api/services/document.service.ts b/api/services/document.service.ts index ceba5e7..c340a49 100644 --- a/api/services/document.service.ts +++ b/api/services/document.service.ts @@ -3,10 +3,9 @@ import pdf from "pdf-parse"; import { IDocumentService } from "../interfaces/document-service.interface"; export class DocumentService implements IDocumentService { - async convertPDFToText(pdfFilePath: string): Promise { + async convertPDFToText(file: Buffer): Promise { try { - const dataBuffer = fs.readFileSync(pdfFilePath); - const data = await pdf(dataBuffer); + const data = await pdf(file); const text = this.formatText(data.text); return text; } catch (error) { diff --git a/api/services/embed.service.ts b/api/services/embed.service.ts index 136ef8d..886a4db 100644 --- a/api/services/embed.service.ts +++ b/api/services/embed.service.ts @@ -60,8 +60,10 @@ of information. For example, you could use this task type to embed articles, FAQ or product manuals to create a searchable knowledge base for customer support or information retrieval systems.*/ export class EmbeddingService extends GenerativeAIService implements IEmbeddingService { - documentPath: string = getValue("PDF_ABSOLUTE_PATH"); - constructor(apiKey: string) { + constructor( + apiKey: string, + private file: Buffer + ) { super(apiKey); } /** @@ -194,10 +196,10 @@ export class EmbeddingService extends GenerativeAIService implements IEmbeddingS async createContentEmbeddings(): Promise<{ text: string; embeddings?: number[] }[]> { const documentService: IDocumentService = new DocumentService(); let text: string; - if (!this.documentPath.length) { + if (!this.file.length) { throw new HttpException(HTTP_RESPONSE_CODE.BAD_REQUEST, "Could not read PDF file"); } - text = await documentService.convertPDFToText(this.documentPath); + text = await documentService.convertPDFToText(this.file); const chunks: string[] = documentService.breakTextIntoChunks(text, 2000); const contentEmbed = chunks.map( diff --git a/presentation/package-lock.json b/presentation/package-lock.json index c01a940..e9edc3d 100644 --- a/presentation/package-lock.json +++ b/presentation/package-lock.json @@ -1123,6 +1123,16 @@ "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", + "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -3317,6 +3327,13 @@ "react": ">=15.0.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "optional": true, + "peer": true + }, "node_modules/unload": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", diff --git a/presentation/src/components/ChatForm.tsx b/presentation/src/components/ChatForm.tsx index 987e5be..d27ad05 100644 --- a/presentation/src/components/ChatForm.tsx +++ b/presentation/src/components/ChatForm.tsx @@ -1,12 +1,12 @@ import DOMPurify from "dompurify"; +import markdownIt from "markdown-it"; import { useState } from "react"; import { Button, Card, Container, Form, ListGroup, Row, Stack } from "react-bootstrap"; import useAxiosPrivate from "../hooks/useAxiosPrivate"; -import NavBar from "./NavBar"; -import markdownIt from "markdown-it"; -import Books from "./DropDown"; import { IDocument } from "../interfaces/document.interface"; -import FileUploader from "./DragAndDrop"; +import { FileUploader } from "./DragAndDrop"; +import Books from "./DropDown"; +import NavBar from "./NavBar"; interface IHistory { role: string; diff --git a/presentation/src/components/DragAndDrop.tsx b/presentation/src/components/DragAndDrop.tsx index c7b9e82..195fce0 100644 --- a/presentation/src/components/DragAndDrop.tsx +++ b/presentation/src/components/DragAndDrop.tsx @@ -1,33 +1,39 @@ import React from "react"; import { useDropzone, DropzoneOptions } from "react-dropzone"; +import useAxiosPrivate from "../hooks/useAxiosPrivate"; -// interface UploadResponse { -// // Define the structure of the API response -// message: string; -// // Add more properties as needed -// } +export const FileUploader: React.FC = () => { + const axiosPrivate = useAxiosPrivate(); -const FileUploader: React.FC = () => { - const onDrop = async (acceptedFiles: File[]) => { + const handleFileUpload = async (file: File) => { + const formData = createFormData(file); + try { + const response = await uploadFileToServer(formData); + console.log("File uploaded successfully:", response.data.message); + } catch (error) { + console.error("Error uploading file:", error); + } + }; + + const onDrop = (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { - const file = acceptedFiles[0]; - const formData = new FormData(); - formData.append("pdf", file); - console.log(file); - - // try { - // const response = await axios.post("https://api.example.com/upload", formData, { - // headers: { - // "Content-Type": "multipart/form-data", - // }, - // }); - // console.log("File uploaded successfully:", response.data.message); - // } catch (error) { - // console.error("Error uploading file:", error); - // } + handleFileUpload(acceptedFiles[0]); } }; + const createFormData = (file: File) => { + const formData = new FormData(); + formData.append("pdf", file); + return formData; + }; + + const uploadFileToServer = async (formData: FormData) => { + const url = "/embed/documents"; + const headers = { "Content-Type": "multipart/form-data" }; + const response = await axiosPrivate.post(url, formData, { headers }); + return response; + }; + const options: DropzoneOptions = { onDrop, maxSize: 5 * 1024 * 1024, @@ -47,5 +53,3 @@ const FileUploader: React.FC = () => { ); }; - -export default FileUploader;