@@ -11,6 +11,7 @@ import {
1111import { useShallow } from 'zustand/react/shallow'
1212
1313import { routeUserPrompt , addBashMessageToHistory } from './commands/router'
14+ import type { CommandResult } from './commands/command-registry'
1415import { AnnouncementBanner } from './components/announcement-banner'
1516import { ChatInputBar } from './components/chat-input-bar'
1617import { MessageWithAgents } from './components/message-with-agents'
@@ -50,7 +51,6 @@ import { usePublishStore } from './state/publish-store'
5051import {
5152 addClipboardPlaceholder ,
5253 addPendingImageFromFile ,
53- capturePendingImages ,
5454 validateAndAddImage ,
5555} from './utils/add-pending-image'
5656import { createChatScrollAcceleration } from './utils/chat-scroll-accel'
@@ -617,6 +617,61 @@ export const Chat = ({
617617
618618 sendMessageRef . current = sendMessage
619619
620+ const onSubmitPrompt = useEvent (
621+ async (
622+ content : string ,
623+ mode : AgentMode ,
624+ options ?: { preserveInputValue ?: boolean } ,
625+ ) => {
626+ ensureQueueActiveBeforeSubmit ( )
627+
628+ const previousInputValue =
629+ options ?. preserveInputValue === true
630+ ? ( ( ) => {
631+ const {
632+ inputValue : text ,
633+ cursorPosition,
634+ lastEditDueToNav,
635+ } = useChatStore . getState ( )
636+ return { text, cursorPosition, lastEditDueToNav }
637+ } ) ( )
638+ : null
639+
640+ const result = await routeUserPrompt ( {
641+ abortControllerRef,
642+ agentMode : mode ,
643+ inputRef,
644+ inputValue : content ,
645+ isChainInProgressRef,
646+ isStreaming,
647+ logoutMutation,
648+ streamMessageIdRef,
649+ addToQueue,
650+ clearMessages,
651+ saveToHistory,
652+ scrollToLatest,
653+ sendMessage,
654+ setCanProcessQueue,
655+ setInputFocused,
656+ setInputValue,
657+ setIsAuthenticated,
658+ setMessages,
659+ setUser,
660+ stopStreaming,
661+ } )
662+
663+ if ( previousInputValue ) {
664+ setInputValue ( {
665+ text : previousInputValue . text ,
666+ cursorPosition : previousInputValue . cursorPosition ,
667+ lastEditDueToNav : previousInputValue . lastEditDueToNav ,
668+ } )
669+ }
670+
671+ return result
672+ } ,
673+ )
674+
620675 // Handle followup suggestion clicks
621676 useEffect ( ( ) => {
622677 const handleFollowupClick = ( event : Event ) => {
@@ -630,24 +685,8 @@ export const Chat = ({
630685 // Mark this followup as clicked (persisted per toolCallId)
631686 useChatStore . getState ( ) . markFollowupClicked ( toolCallId , index )
632687
633- // Send the followup prompt directly without clearing the user's current input
634- ensureQueueActiveBeforeSubmit ( )
635-
636- if (
637- isStreaming ||
638- streamMessageIdRef . current ||
639- isChainInProgressRef . current
640- ) {
641- const pendingImagesForQueue = capturePendingImages ( )
642- // Queue the followup message
643- addToQueue ( prompt , pendingImagesForQueue )
644- } else {
645- // Send directly
646- sendMessage ( { content : prompt , agentMode } )
647- setTimeout ( ( ) => {
648- scrollToLatest ( )
649- } , 0 )
650- }
688+ // Send the followup prompt while preserving any text the user has typed
689+ void onSubmitPrompt ( prompt , agentMode , { preserveInputValue : true } )
651690 }
652691
653692 globalThis . addEventListener ( 'codebuff:send-followup' , handleFollowupClick )
@@ -659,40 +698,9 @@ export const Chat = ({
659698 }
660699 } , [
661700 agentMode ,
662- isStreaming ,
663- streamMessageIdRef ,
664- isChainInProgressRef ,
665- addToQueue ,
666- scrollToLatest ,
667- sendMessage ,
668- ensureQueueActiveBeforeSubmit ,
701+ onSubmitPrompt ,
669702 ] )
670703
671- const onSubmitPrompt = useEvent ( ( content : string , mode : AgentMode ) => {
672- return routeUserPrompt ( {
673- abortControllerRef,
674- agentMode : mode ,
675- inputRef,
676- inputValue : content ,
677- isChainInProgressRef,
678- isStreaming,
679- logoutMutation,
680- streamMessageIdRef,
681- addToQueue,
682- clearMessages,
683- saveToHistory,
684- scrollToLatest,
685- sendMessage,
686- setCanProcessQueue,
687- setInputFocused,
688- setInputValue,
689- setIsAuthenticated,
690- setMessages,
691- setUser,
692- stopStreaming,
693- } )
694- } )
695-
696704 // handleSlashItemClick is defined later after feedback/publish stores are available
697705
698706 const handleMentionItemClick = useCallback (
@@ -772,32 +780,23 @@ export const Chat = ({
772780
773781 const publishMutation = usePublishMutation ( )
774782
775- // Click handler for slash menu items - executes command immediately
776- const handleSlashItemClick = useCallback (
777- async ( index : number ) => {
778- const selected = slashMatches [ index ]
779- if ( ! selected ) return
780-
781- // Execute the selected slash command immediately
782- const commandString = `/${ selected . id } `
783- setSlashSelectedIndex ( 0 )
784-
785- ensureQueueActiveBeforeSubmit ( )
786- const result = await onSubmitPrompt ( commandString , agentMode )
783+ const handleCommandResult = useCallback (
784+ ( result ?: CommandResult ) => {
785+ if ( ! result ) return
787786
788- if ( result ? .openFeedbackMode ) {
787+ if ( result . openFeedbackMode ) {
789788 // Save the feedback text that was set by the command handler before opening feedback mode
790- const prefilledText = useFeedbackStore . getState ( ) . feedbackText
791- const prefilledCursor = useFeedbackStore . getState ( ) . feedbackCursor
789+ const { feedbackText, feedbackCursor } = useFeedbackStore . getState ( )
792790 saveCurrentInput ( '' , 0 )
793791 openFeedbackForMessage ( null )
794792 // Restore the prefilled text after openFeedbackForMessage resets it
795- if ( prefilledText ) {
796- useFeedbackStore . getState ( ) . setFeedbackText ( prefilledText )
797- useFeedbackStore . getState ( ) . setFeedbackCursor ( prefilledCursor )
793+ if ( feedbackText ) {
794+ useFeedbackStore . getState ( ) . setFeedbackText ( feedbackText )
795+ useFeedbackStore . getState ( ) . setFeedbackCursor ( feedbackCursor )
798796 }
799797 }
800- if ( result ?. openPublishMode ) {
798+
799+ if ( result . openPublishMode ) {
801800 if ( result . preSelectAgents && result . preSelectAgents . length > 0 ) {
802801 // preSelectAgents already sets publishMode: true, so don't call openPublishMode
803802 // which would reset the selectedAgentIds
@@ -807,16 +806,28 @@ export const Chat = ({
807806 }
808807 }
809808 } ,
809+ [ saveCurrentInput , openFeedbackForMessage , openPublishMode , preSelectAgents ] ,
810+ )
811+
812+ // Click handler for slash menu items - executes command immediately
813+ const handleSlashItemClick = useCallback (
814+ async ( index : number ) => {
815+ const selected = slashMatches [ index ]
816+ if ( ! selected ) return
817+
818+ // Execute the selected slash command immediately
819+ const commandString = `/${ selected . id } `
820+ setSlashSelectedIndex ( 0 )
821+
822+ const result = await onSubmitPrompt ( commandString , agentMode )
823+ handleCommandResult ( result )
824+ } ,
810825 [
811826 slashMatches ,
812827 setSlashSelectedIndex ,
813- ensureQueueActiveBeforeSubmit ,
814828 onSubmitPrompt ,
815829 agentMode ,
816- saveCurrentInput ,
817- openFeedbackForMessage ,
818- openPublishMode ,
819- preSelectAgents ,
830+ handleCommandResult ,
820831 ] ,
821832 )
822833
@@ -897,79 +908,13 @@ export const Chat = ({
897908 } , [ feedbackMode , askUserState , inputRef ] )
898909
899910 const handleSubmit = useCallback ( async ( ) => {
900- ensureQueueActiveBeforeSubmit ( )
901-
902- const result = await routeUserPrompt ( {
903- abortControllerRef,
904- agentMode,
905- inputRef,
906- inputValue,
907- isChainInProgressRef,
908- isStreaming,
909- logoutMutation,
910- streamMessageIdRef,
911- addToQueue,
912- clearMessages,
913- saveToHistory,
914- scrollToLatest,
915- sendMessage,
916- setCanProcessQueue,
917- setInputFocused,
918- setInputValue,
919- setIsAuthenticated,
920- setMessages,
921- setUser,
922- stopStreaming,
923- } )
924-
925- if ( result ?. openFeedbackMode ) {
926- // Save the feedback text that was set by the command handler before opening feedback mode
927- const prefilledText = useFeedbackStore . getState ( ) . feedbackText
928- const prefilledCursor = useFeedbackStore . getState ( ) . feedbackCursor
929- saveCurrentInput ( '' , 0 )
930- openFeedbackForMessage ( null )
931- // Restore the prefilled text after openFeedbackForMessage resets it
932- if ( prefilledText ) {
933- useFeedbackStore . getState ( ) . setFeedbackText ( prefilledText )
934- useFeedbackStore . getState ( ) . setFeedbackCursor ( prefilledCursor )
935- }
936- }
937-
938- if ( result ?. openPublishMode ) {
939- if ( result . preSelectAgents && result . preSelectAgents . length > 0 ) {
940- // preSelectAgents already sets publishMode: true, so don't call openPublishMode
941- // which would reset the selectedAgentIds
942- preSelectAgents ( result . preSelectAgents )
943- } else {
944- openPublishMode ( )
945- }
946- }
911+ const result = await onSubmitPrompt ( inputValue , agentMode )
912+ handleCommandResult ( result )
947913 } , [
948- abortControllerRef ,
949- agentMode ,
950- inputRef ,
914+ onSubmitPrompt ,
951915 inputValue ,
952- isChainInProgressRef ,
953- isStreaming ,
954- logoutMutation ,
955- streamMessageIdRef ,
956- addToQueue ,
957- clearMessages ,
958- saveToHistory ,
959- scrollToLatest ,
960- sendMessage ,
961- setCanProcessQueue ,
962- setInputFocused ,
963- setInputValue ,
964- setIsAuthenticated ,
965- setMessages ,
966- setUser ,
967- stopStreaming ,
968- ensureQueueActiveBeforeSubmit ,
969- saveCurrentInput ,
970- openFeedbackForMessage ,
971- openPublishMode ,
972- preSelectAgents ,
916+ agentMode ,
917+ handleCommandResult ,
973918 ] )
974919
975920 const totalMentionMatches = agentMatches . length + fileMatches . length
@@ -1074,30 +1019,9 @@ export const Chat = ({
10741019 const commandString = `/${ selected . id } `
10751020 setSlashSelectedIndex ( 0 )
10761021
1077- ensureQueueActiveBeforeSubmit ( )
10781022 const result = await onSubmitPrompt ( commandString , agentMode )
10791023
1080- if ( result ?. openFeedbackMode ) {
1081- // Save the feedback text that was set by the command handler before opening feedback mode
1082- const prefilledText = useFeedbackStore . getState ( ) . feedbackText
1083- const prefilledCursor = useFeedbackStore . getState ( ) . feedbackCursor
1084- saveCurrentInput ( '' , 0 )
1085- openFeedbackForMessage ( null )
1086- // Restore the prefilled text after openFeedbackForMessage resets it
1087- if ( prefilledText ) {
1088- useFeedbackStore . getState ( ) . setFeedbackText ( prefilledText )
1089- useFeedbackStore . getState ( ) . setFeedbackCursor ( prefilledCursor )
1090- }
1091- }
1092- if ( result ?. openPublishMode ) {
1093- if ( result . preSelectAgents && result . preSelectAgents . length > 0 ) {
1094- // preSelectAgents already sets publishMode: true, so don't call openPublishMode
1095- // which would reset the selectedAgentIds
1096- preSelectAgents ( result . preSelectAgents )
1097- } else {
1098- openPublishMode ( )
1099- }
1100- }
1024+ handleCommandResult ( result )
11011025 } ,
11021026 onSlashMenuComplete : ( ) => {
11031027 // Complete the word without executing - same as clicking on the item
@@ -1258,13 +1182,9 @@ export const Chat = ({
12581182 setSlashSelectedIndex ,
12591183 slashMatches ,
12601184 slashSelectedIndex ,
1261- ensureQueueActiveBeforeSubmit ,
12621185 onSubmitPrompt ,
12631186 agentMode ,
1264- saveCurrentInput ,
1265- openFeedbackForMessage ,
1266- openPublishMode ,
1267- preSelectAgents ,
1187+ handleCommandResult ,
12681188 setAgentSelectedIndex ,
12691189 agentMatches ,
12701190 fileMatches ,
0 commit comments