@@ -117,6 +117,9 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
117117 const ignoreVoiceClickUntilRef = useRef ( 0 )
118118 const voiceStartRequestRef = useRef ( 0 )
119119 const handleSubmitRef = useRef < ( ) => void > ( ( ) => { } )
120+ const promptRef = useRef ( prompt )
121+ const attachedFilesRef = useRef ( attachedFiles )
122+ const imageAttachmentsRef = useRef ( imageAttachments )
120123 const pendingPromptCommand = useUIState ( ( state ) => state . pendingPromptCommand )
121124 const pendingPromptFile = useUIState ( ( state ) => state . pendingPromptFile )
122125 const clearPendingPromptCommand = useUIState ( ( state ) => state . clearPendingPromptCommand )
@@ -148,6 +151,29 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
148151 setIsVoiceAutoSendPending ( false )
149152 setIsVoiceAutoSendWaitingForTranscript ( false )
150153 } , [ ] )
154+
155+ useEffect ( ( ) => {
156+ promptRef . current = prompt
157+ attachedFilesRef . current = attachedFiles
158+ imageAttachmentsRef . current = imageAttachments
159+ } , [ attachedFiles , imageAttachments , prompt ] )
160+
161+ const clearSubmittedPrompt = useCallback ( ( submittedPrompt : string , submittedAttachedFiles : Map < string , FileAttachmentInfo > , submittedImageAttachments : ImageAttachment [ ] ) => {
162+ if (
163+ promptRef . current !== submittedPrompt ||
164+ attachedFilesRef . current !== submittedAttachedFiles ||
165+ imageAttachmentsRef . current !== submittedImageAttachments
166+ ) {
167+ return
168+ }
169+
170+ setPrompt ( '' )
171+ setAttachedFiles ( new Map ( ) )
172+ revokeBlobUrls ( submittedImageAttachments )
173+ setImageAttachments ( [ ] )
174+ setSelectedAgent ( null )
175+ clearSTT ( )
176+ } , [ clearSTT ] )
151177
152178 useImperativeHandle ( ref , ( ) => ( {
153179 setPromptValue : ( value : string ) => {
@@ -174,6 +200,7 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
174200 } ) , [ imageAttachments , clearSTT , isRecording , abortRecording , resetVoiceGestureState ] )
175201 const sendPrompt = useSendPrompt ( opcodeUrl , directory )
176202 const sendShell = useSendShell ( opcodeUrl , directory )
203+ const isPromptSubmitPending = sendPrompt . isPending || sendShell . isPending
177204 const abortSession = useAbortSession ( opcodeUrl , directory , sessionID )
178205 const { filterCommands } = useCommands ( opcodeUrl )
179206 const { executeCommand } = useCommandHandler ( {
@@ -225,6 +252,7 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
225252
226253 const handleSubmit = ( ) => {
227254 if ( disabled ) return
255+ if ( isPromptSubmitPending ) return
228256 if ( ! prompt . trim ( ) && imageAttachments . length === 0 ) return
229257
230258 pendingVoiceAutoSubmitRef . current = false
@@ -234,39 +262,49 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
234262 onScrollToBottom ( )
235263 const parts = parsePromptToParts ( prompt , attachedFiles , imageAttachments )
236264 const agentUsed = selectedAgent || currentMode
237- sendPrompt . mutate ( {
238- sessionID,
239- parts,
240- model : currentModel ,
241- agent : agentUsed ,
242- variant : currentVariant ,
243- queued : true
244- } )
265+ const submittedPrompt = prompt
266+ const submittedAttachedFiles = attachedFiles
267+ const submittedImageAttachments = imageAttachments
268+ sendPrompt . mutate (
269+ {
270+ sessionID,
271+ parts,
272+ model : currentModel ,
273+ agent : agentUsed ,
274+ variant : currentVariant ,
275+ queued : true
276+ } ,
277+ {
278+ onSuccess : ( ) => clearSubmittedPrompt ( submittedPrompt , submittedAttachedFiles , submittedImageAttachments )
279+ }
280+ )
245281 setStoredAgent ( sessionID , agentUsed )
246282 if ( model ) {
247283 setStoredModel ( { providerID : model . providerID , modelID : model . modelID } )
248284 }
249- setPrompt ( '' )
250- setAttachedFiles ( new Map ( ) )
251- revokeBlobUrls ( imageAttachments )
252- setImageAttachments ( [ ] )
253- setSelectedAgent ( null )
254- clearSTT ( )
255285 return
256286 }
257287
258288 if ( isBashMode ) {
259289 const command = prompt . startsWith ( '!' ) ? prompt . slice ( 1 ) : prompt
260290 addUserBashCommand ( command )
261- sendShell . mutate ( {
262- sessionID,
263- command,
264- agent : currentMode
265- } )
291+ const submittedPrompt = prompt
292+ sendShell . mutate (
293+ {
294+ sessionID,
295+ command,
296+ agent : currentMode
297+ } ,
298+ {
299+ onSuccess : ( ) => {
300+ if ( promptRef . current !== submittedPrompt ) return
301+ setPrompt ( '' )
302+ setIsBashMode ( false )
303+ clearSTT ( )
304+ }
305+ }
306+ )
266307 setStoredAgent ( sessionID , currentMode )
267- setPrompt ( '' )
268- setIsBashMode ( false )
269- clearSTT ( )
270308 return
271309 }
272310
@@ -287,27 +325,29 @@ export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(
287325
288326 const parts = parsePromptToParts ( prompt , attachedFiles , imageAttachments )
289327 const agentUsed = selectedAgent || currentMode
328+ const submittedPrompt = prompt
329+ const submittedAttachedFiles = attachedFiles
330+ const submittedImageAttachments = imageAttachments
290331
291- sendPrompt . mutate ( {
292- sessionID,
293- parts,
294- model : currentModel ,
295- agent : agentUsed ,
296- variant : currentVariant
297- } )
332+ sendPrompt . mutate (
333+ {
334+ sessionID,
335+ parts,
336+ model : currentModel ,
337+ agent : agentUsed ,
338+ variant : currentVariant
339+ } ,
340+ {
341+ onSuccess : ( ) => clearSubmittedPrompt ( submittedPrompt , submittedAttachedFiles , submittedImageAttachments )
342+ }
343+ )
298344
299345 onScrollToBottom ( )
300346
301347 setStoredAgent ( sessionID , agentUsed )
302348 if ( model ) {
303349 setStoredModel ( { providerID : model . providerID , modelID : model . modelID } )
304350 }
305- setPrompt ( '' )
306- setAttachedFiles ( new Map ( ) )
307- revokeBlobUrls ( imageAttachments )
308- setImageAttachments ( [ ] )
309- setSelectedAgent ( null )
310- clearSTT ( )
311351 }
312352
313353 handleSubmitRef . current = handleSubmit
@@ -1317,7 +1357,7 @@ return (
13171357 < button
13181358 data-submit-prompt
13191359 onClick = { hasPendingPermissionForSession ? ( ) => setShowDialog ( true ) : handleSubmit }
1320- disabled = { hasPendingPermissionForSession ? false : ( ( ! prompt . trim ( ) && imageAttachments . length === 0 ) || disabled ) }
1360+ disabled = { hasPendingPermissionForSession ? false : ( ( ! prompt . trim ( ) && imageAttachments . length === 0 ) || disabled || isPromptSubmitPending ) }
13211361 className = { `px-4 md:px-5 py-1.5 md:py-2 rounded-lg text-sm font-medium transition-colors dark:border flex-shrink-0 min-w-[52px] ${
13221362 hasPendingPermissionForSession
13231363 ? 'bg-orange-500 hover:bg-orange-600 border-orange-400 text-primary-foreground ring-orange-500/20'
0 commit comments