diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/DnDWrapper/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/DnDWrapper/index.jsx index 32999e11c64..27148ef044a 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/DnDWrapper/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/DnDWrapper/index.jsx @@ -10,6 +10,8 @@ export const DndUploaderContext = createContext(); export const REMOVE_ATTACHMENT_EVENT = "ATTACHMENT_REMOVE"; export const CLEAR_ATTACHMENTS_EVENT = "ATTACHMENT_CLEAR"; export const PASTE_ATTACHMENT_EVENT = "ATTACHMENT_PASTED"; +export const ATTACHMENTS_PROCESSING_EVENT = "ATTACHMENTS_PROCESSING"; +export const ATTACHMENTS_PROCESSED_EVENT = "ATTACHMENTS_PROCESSED"; /** * File Attachment for automatic upload on the chat container page. @@ -169,34 +171,44 @@ export function DnDFileUploaderProvider({ workspace, children }) { * @param {Attachment[]} newAttachments */ function embedEligibleAttachments(newAttachments = []) { + window.dispatchEvent(new CustomEvent(ATTACHMENTS_PROCESSING_EVENT)); + const promises = []; + for (const attachment of newAttachments) { // Images/attachments are chat specific. if (attachment.type === "attachment") continue; const formData = new FormData(); formData.append("file", attachment.file, attachment.file.name); - Workspace.uploadAndEmbedFile(workspace.slug, formData).then( - ({ response, data }) => { - const updates = { - status: response.ok ? "success" : "failed", - error: data?.error ?? null, - document: data?.document, - }; + promises.push( + Workspace.uploadAndEmbedFile(workspace.slug, formData).then( + ({ response, data }) => { + const updates = { + status: response.ok ? "success" : "failed", + error: data?.error ?? null, + document: data?.document, + }; - setFiles((prev) => { - return prev.map( - ( - /** @type {Attachment} */ - prevFile - ) => { - if (prevFile.uid !== attachment.uid) return prevFile; - return { ...prevFile, ...updates }; - } - ); - }); - } + setFiles((prev) => { + return prev.map( + ( + /** @type {Attachment} */ + prevFile + ) => { + if (prevFile.uid !== attachment.uid) return prevFile; + return { ...prevFile, ...updates }; + } + ); + }); + } + ) ); } + + // Wait for all promises to resolve in some way before dispatching the event to unlock the send button + Promise.all(promises).finally(() => + window.dispatchEvent(new CustomEvent(ATTACHMENTS_PROCESSED_EVENT)) + ); } return ( diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx index 51c730f25a4..fa1f08530e2 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/index.jsx @@ -15,7 +15,11 @@ import SpeechToText from "./SpeechToText"; import { Tooltip } from "react-tooltip"; import AttachmentManager from "./Attachments"; import AttachItem from "./AttachItem"; -import { PASTE_ATTACHMENT_EVENT } from "../DnDWrapper"; +import { + ATTACHMENTS_PROCESSED_EVENT, + ATTACHMENTS_PROCESSING_EVENT, + PASTE_ATTACHMENT_EVENT, +} from "../DnDWrapper"; import useTextSize from "@/hooks/useTextSize"; import { useTranslation } from "react-i18next"; import Appearance from "@/models/appearance"; @@ -31,6 +35,7 @@ export default function PromptInput({ attachments = [], }) { const { t } = useTranslation(); + const { isDisabled } = useIsDisabled(); const [promptInput, setPromptInput] = useState(""); const { showAgents, setShowAgents } = useAvailableAgents(); const { showSlashCommand, setShowSlashCommand } = useSlashCommands(); @@ -112,7 +117,7 @@ export default function PromptInput({ // Is simple enter key press w/o shift key if (event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); - if (isStreaming) return; + if (isStreaming || isDisabled) return; // Prevent submission if streaming or disabled return submit(event); } @@ -280,14 +285,19 @@ export default function PromptInput({