Success fail messages for upload document (#208)

* WIP success fail messages for upload document

* added success/error msgs for uploading feedback and disabled fileUploadProgress in backend

* remove unused middleware

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2023-08-22 19:18:47 -07:00 committed by GitHub
parent cfcd14a307
commit c0adcc129d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 44 deletions

View File

@ -10,6 +10,8 @@ function FileUploadProgressComponent({
file, file,
rejected = false, rejected = false,
reason = null, reason = null,
onUploadSuccess,
onUploadError,
}) { }) {
const [timerMs, setTimerMs] = useState(10); const [timerMs, setTimerMs] = useState(10);
const [status, setStatus] = useState(file?.rejected ? "uploading" : "failed"); const [status, setStatus] = useState(file?.rejected ? "uploading" : "failed");
@ -24,9 +26,16 @@ function FileUploadProgressComponent({
}, 100); }, 100);
// Chunk streaming not working in production so we just sit and wait // Chunk streaming not working in production so we just sit and wait
await Workspace.uploadFile(slug, formData); const { response, data } = await Workspace.uploadFile(slug, formData);
setStatus("complete"); if (!response.ok) {
clearInterval(timer); setStatus("failed");
clearInterval(timer);
onUploadError(data.error);
} else {
setStatus("complete");
clearInterval(timer);
onUploadSuccess();
}
} }
!!file && !rejected && uploadFile(); !!file && !rejected && uploadFile();
}, []); }, []);

View File

@ -10,6 +10,19 @@ import { Frown } from "react-feather";
export default function UploadToWorkspace({ workspace, fileTypes }) { export default function UploadToWorkspace({ workspace, fileTypes }) {
const [ready, setReady] = useState(null); const [ready, setReady] = useState(null);
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [successMsg, setSuccessMsg] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const handleUploadSuccess = () => {
setSuccessMsg("File uploaded successfully");
setErrorMsg(null);
};
const handleUploadError = (message) => {
setErrorMsg(`Upload failed: ${message}`);
setSuccessMsg(null);
};
const onDrop = useCallback(async (acceptedFiles, rejections) => { const onDrop = useCallback(async (acceptedFiles, rejections) => {
const newAccepted = acceptedFiles.map((file) => { const newAccepted = acceptedFiles.map((file) => {
return { return {
@ -37,6 +50,20 @@ export default function UploadToWorkspace({ workspace, fileTypes }) {
checkProcessorOnline(); checkProcessorOnline();
}, []); }, []);
useEffect(() => {
if (!!successMsg) {
setTimeout(() => {
setSuccessMsg("");
}, 3_500);
}
if (!!errorMsg) {
setTimeout(() => {
setErrorMsg("");
}, 3_500);
}
}, [successMsg, errorMsg]);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
onDrop, onDrop,
accept: { accept: {
@ -133,6 +160,8 @@ export default function UploadToWorkspace({ workspace, fileTypes }) {
slug={workspace.slug} slug={workspace.slug}
rejected={file?.rejected} rejected={file?.rejected}
reason={file?.reason} reason={file?.reason}
onUploadSuccess={handleUploadSuccess}
onUploadError={handleUploadError}
/> />
))} ))}
</div> </div>
@ -144,6 +173,16 @@ export default function UploadToWorkspace({ workspace, fileTypes }) {
{Object.values(fileTypes).flat().join(" ")} {Object.values(fileTypes).flat().join(" ")}
</code> </code>
</p> </p>
{successMsg && (
<p className="text-green-600 dark:text-green-400 text-sm text-center pt-2">
{successMsg}
</p>
)}
{errorMsg && (
<p className="text-red-600 dark:text-red-400 text-sm text-center pt-2">
{errorMsg}
</p>
)}
</ModalWrapper> </ModalWrapper>
); );
} }

View File

@ -107,7 +107,9 @@ const Workspace = {
body: formData, body: formData,
headers: baseHeaders(), headers: baseHeaders(),
}); });
return response;
const data = await response.json();
return { response, data };
}, },
}; };

View File

@ -6,9 +6,6 @@ const { WorkspaceChats } = require("../models/workspaceChats");
const { convertToChatHistory } = require("../utils/chats"); const { convertToChatHistory } = require("../utils/chats");
const { getVectorDbClass } = require("../utils/helpers"); const { getVectorDbClass } = require("../utils/helpers");
const { setupMulter } = require("../utils/files/multer"); const { setupMulter } = require("../utils/files/multer");
const {
fileUploadProgress,
} = require("../utils/middleware/fileUploadProgress");
const { const {
checkPythonAppAlive, checkPythonAppAlive,
processDocument, processDocument,
@ -69,32 +66,31 @@ function workspaceEndpoints(app) {
app.post( app.post(
"/workspace/:slug/upload", "/workspace/:slug/upload",
fileUploadProgress,
handleUploads.single("file"), handleUploads.single("file"),
async function (request, _) { async function (request, response) {
const { originalname } = request.file; const { originalname } = request.file;
const processingOnline = await checkPythonAppAlive(); const processingOnline = await checkPythonAppAlive();
if (!processingOnline) { if (!processingOnline) {
console.log( response
`Python processing API is not online. Document ${originalname} will not be processed automatically.` .status(500)
); .json({
return; success: false,
error: `Python processing API is not online. Document ${originalname} will not be processed automatically.`,
})
.end();
} }
const { success, reason } = await processDocument(originalname); const { success, reason } = await processDocument(originalname);
if (!success) { if (!success) {
console.log( response.status(500).json({ success: false, error: reason }).end();
`Python processing API was not able to process document ${originalname}. Reason: ${reason}`
);
return false;
} }
console.log( console.log(
`Document ${originalname} uploaded processed and successfully. It is now available in documents.` `Document ${originalname} uploaded processed and successfully. It is now available in documents.`
); );
await Telemetry.sendTelemetry("document_uploaded"); await Telemetry.sendTelemetry("document_uploaded");
return; response.status(200).json({ success: true, error: null });
} }
); );

View File

@ -1,26 +0,0 @@
async function fileUploadProgress(request, response, next) {
let progress = 0;
const fileSize = request.headers["content-length"]
? parseInt(request.headers["content-length"])
: 0;
// Note(tcarambat): While this is chunked it does not stream back to the UI for some reason.
// It just waits for the entire requests to finish. Likely because it is not using EventSource on frontend
// which is limited to GET.
// TODO: Someone smarter than me add streaming here to report back real-time progress.
response.writeHead(200);
request.on("data", (chunk) => {
progress += chunk.length;
const percentage = (progress / fileSize) * 100;
response.write(`${JSON.stringify({ progress, fileSize, percentage })}\n`);
if (progress >= fileSize) {
response.end();
}
});
next();
}
module.exports = {
fileUploadProgress,
};