From 292257770c246da1a2da9dce2ac913b9c5bb8258 Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Thu, 10 Apr 2025 03:02:39 +0530 Subject: [PATCH] (refactor:attach) centralize attachment state --- frontend/src/components/MessageInput.tsx | 260 ++++++++---------- .../src/conversation/ConversationBubble.tsx | 33 --- .../src/conversation/ConversationMessages.tsx | 2 - .../src/conversation/conversationModels.ts | 12 +- .../src/conversation/conversationSlice.ts | 39 ++- 5 files changed, 159 insertions(+), 187 deletions(-) diff --git a/frontend/src/components/MessageInput.tsx b/frontend/src/components/MessageInput.tsx index 612cbaf2..16696932 100644 --- a/frontend/src/components/MessageInput.tsx +++ b/frontend/src/components/MessageInput.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef,useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDarkTheme } from '../hooks'; import { useSelector, useDispatch } from 'react-redux'; @@ -18,7 +18,12 @@ import { selectSelectedDocs, selectToken } from '../preferences/preferenceSlice' import { ActiveState } from '../models/misc'; import Upload from '../upload/Upload'; import ClipIcon from '../assets/clip.svg'; -import { setAttachments, removeAttachment } from '../conversation/conversationSlice'; +import { + addAttachment, + updateAttachment, + removeAttachment, + selectAttachments +} from '../conversation/conversationSlice'; interface MessageInputProps { @@ -51,10 +56,10 @@ export default function MessageInput({ const [isSourcesPopupOpen, setIsSourcesPopupOpen] = useState(false); const [isToolsPopupOpen, setIsToolsPopupOpen] = useState(false); const [uploadModalState, setUploadModalState] = useState('INACTIVE'); - const [uploads, setUploads] = useState([]); const selectedDocs = useSelector(selectSelectedDocs); const token = useSelector(selectToken); + const attachments = useSelector(selectAttachments); const dispatch = useDispatch(); @@ -88,67 +93,53 @@ export default function MessageInput({ const apiHost = import.meta.env.VITE_API_HOST; const xhr = new XMLHttpRequest(); - const uploadState: UploadState = { - taskId: '', + const newAttachment = { fileName: file.name, progress: 0, - status: 'uploading' + status: 'uploading' as const, + taskId: '', }; - setUploads(prev => { - const newUploads = [...prev, uploadState]; - const uploadIndex = newUploads.length - 1; - - xhr.upload.addEventListener('progress', (event) => { - if (event.lengthComputable) { - const progress = Math.round((event.loaded / event.total) * 100); - setUploads(current => current.map((upload, idx) => - idx === uploadIndex - ? { ...upload, progress } - : upload - )); - } - }); + dispatch(addAttachment(newAttachment)); - xhr.onload = () => { - if (xhr.status === 200) { - const response = JSON.parse(xhr.responseText); - console.log('File uploaded successfully:', response); - - if (response.task_id) { - setUploads(current => current.map((upload, idx) => - idx === uploadIndex - ? { - ...upload, - taskId: response.task_id, - status: 'processing', - progress: 10 - } - : upload - )); - } - } else { - setUploads(current => current.map((upload, idx) => - idx === uploadIndex - ? { ...upload, status: 'failed' } - : upload - )); - console.error('Error uploading file:', xhr.responseText); - } - }; - - xhr.onerror = () => { - setUploads(current => current.map((upload, idx) => - idx === uploadIndex - ? { ...upload, status: 'failed' } - : upload - )); - console.error('Network error during file upload'); - }; - - return newUploads; + xhr.upload.addEventListener('progress', (event) => { + if (event.lengthComputable) { + const progress = Math.round((event.loaded / event.total) * 100); + dispatch(updateAttachment({ + taskId: newAttachment.taskId, + updates: { progress } + })); + } }); + xhr.onload = () => { + if (xhr.status === 200) { + const response = JSON.parse(xhr.responseText); + if (response.task_id) { + dispatch(updateAttachment({ + taskId: newAttachment.taskId, + updates: { + taskId: response.task_id, + status: 'processing', + progress: 10 + } + })); + } + } else { + dispatch(updateAttachment({ + taskId: newAttachment.taskId, + updates: { status: 'failed' } + })); + } + }; + + xhr.onerror = () => { + dispatch(updateAttachment({ + taskId: newAttachment.taskId, + updates: { status: 'failed' } + })); + }; + xhr.open('POST', `${apiHost}${endpoints.USER.STORE_ATTACHMENT}`); xhr.setRequestHeader('Authorization', `Bearer ${token}`); xhr.send(formData); @@ -156,64 +147,55 @@ export default function MessageInput({ }; useEffect(() => { - let timeoutIds: number[] = []; - const checkTaskStatus = () => { - const processingUploads = uploads.filter(upload => - upload.status === 'processing' && upload.taskId + const processingAttachments = attachments.filter(att => + att.status === 'processing' && att.taskId ); - processingUploads.forEach(upload => { + processingAttachments.forEach(attachment => { userService - .getTaskStatus(upload.taskId, null) + .getTaskStatus(attachment.taskId!, null) .then((data) => data.json()) .then((data) => { - console.log('Task status:', data); - - setUploads(prev => prev.map(u => { - if (u.taskId !== upload.taskId) return u; - - if (data.status === 'SUCCESS') { - return { - ...u, + if (data.status === 'SUCCESS') { + dispatch(updateAttachment({ + taskId: attachment.taskId!, + updates: { status: 'completed', progress: 100, - attachment_id: data.result?.attachment_id, + id: data.result?.attachment_id, token_count: data.result?.token_count - }; - } else if (data.status === 'FAILURE') { - return { ...u, status: 'failed' }; - } else if (data.status === 'PROGRESS' && data.result?.current) { - return { ...u, progress: data.result.current }; - } - return u; - })); - - if (data.status !== 'SUCCESS' && data.status !== 'FAILURE') { - const timeoutId = window.setTimeout(() => checkTaskStatus(), 2000); - timeoutIds.push(timeoutId); + } + })); + } else if (data.status === 'FAILURE') { + dispatch(updateAttachment({ + taskId: attachment.taskId!, + updates: { status: 'failed' } + })); + } else if (data.status === 'PROGRESS' && data.result?.current) { + dispatch(updateAttachment({ + taskId: attachment.taskId!, + updates: { progress: data.result.current } + })); } }) - .catch((error) => { - console.error('Error checking task status:', error); - setUploads(prev => prev.map(u => - u.taskId === upload.taskId - ? { ...u, status: 'failed' } - : u - )); + .catch(() => { + dispatch(updateAttachment({ + taskId: attachment.taskId!, + updates: { status: 'failed' } + })); }); }); }; - if (uploads.some(upload => upload.status === 'processing')) { - const timeoutId = window.setTimeout(checkTaskStatus, 2000); - timeoutIds.push(timeoutId); - } + const interval = setInterval(() => { + if (attachments.some(att => att.status === 'processing')) { + checkTaskStatus(); + } + }, 2000); - return () => { - timeoutIds.forEach(id => clearTimeout(id)); - }; - }, [uploads]); + return () => clearInterval(interval); + }, [attachments, dispatch]); const handleInput = () => { if (inputRef.current) { @@ -248,37 +230,27 @@ export default function MessageInput({ const handleSubmit = () => { - const completedAttachments = uploads - .filter(upload => upload.status === 'completed' && upload.attachment_id) - .map(upload => ({ - fileName: upload.fileName, - id: upload.attachment_id as string - })); - - dispatch(setAttachments(completedAttachments)); - onSubmit(); }; return (
- {uploads.map((upload, index) => ( + {attachments.map((attachment, index) => (
- {upload.fileName} + {attachment.fileName} - {upload.status === 'completed' && ( + {attachment.status === 'completed' && ( )} - {upload.status === 'failed' && ( + {attachment.status === 'failed' && ( Upload failed )} -{(upload.status === 'uploading' || upload.status === 'processing') && ( -
- - {/* Background circle */} - - - -
-)} + {(attachment.status === 'uploading' || attachment.status === 'processing') && ( +
+ + {/* Background circle */} + + + +
+ )}
))}
diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index ec244087..a241b2d3 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -57,7 +57,6 @@ const ConversationBubble = forwardRef< updated?: boolean, index?: number, ) => void; - attachments?: { fileName: string; id: string }[]; } >(function ConversationBubble( { @@ -72,7 +71,6 @@ const ConversationBubble = forwardRef< retryBtn, questionNumber, handleUpdatedQuestionSubmission, - attachments, }, ref, ) { @@ -99,36 +97,6 @@ const ConversationBubble = forwardRef< handleUpdatedQuestionSubmission?.(editInputBox, true, questionNumber); }; let bubble; - const renderAttachments = () => { - if (!attachments || attachments.length === 0) return null; - - return ( -
- {attachments.map((attachment, index) => ( -
- - - - {attachment.fileName} -
- ))} -
- ); - }; if (type === 'QUESTION') { bubble = (
{message}
- {renderAttachments()}