@@ -151,13 +151,28 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
151151 } , [ message . content ] )
152152
153153 // Parse special tags from message content (options, plan)
154- // Only parse after streaming is complete to avoid affecting streaming smoothness
154+ // Parse during streaming to show options/plan as they stream in
155155 const parsedTags = useMemo ( ( ) => {
156- if ( isUser || isStreaming ) return null
156+ if ( isUser ) return null
157+
158+ // Try message.content first
159+ if ( message . content ) {
160+ const parsed = parseSpecialTags ( message . content )
161+ if ( parsed . options || parsed . plan ) return parsed
162+ }
163+
164+ // During streaming, check content blocks for options/plan
165+ if ( isStreaming && message . contentBlocks && message . contentBlocks . length > 0 ) {
166+ for ( const block of message . contentBlocks ) {
167+ if ( block . type === 'text' && block . content ) {
168+ const parsed = parseSpecialTags ( block . content )
169+ if ( parsed . options || parsed . plan ) return parsed
170+ }
171+ }
172+ }
157173
158- // Only parse when not streaming - options should appear after message completes
159174 return message . content ? parseSpecialTags ( message . content ) : null
160- } , [ message . content , isUser , isStreaming ] )
175+ } , [ message . content , message . contentBlocks , isUser , isStreaming ] )
161176
162177 // Get sendMessage from store for continuation actions
163178 const sendMessage = useCopilotStore ( ( s ) => s . sendMessage )
@@ -181,14 +196,12 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
181196 if ( block . type === 'text' ) {
182197 const isLastTextBlock =
183198 index === message . contentBlocks ! . length - 1 && block . type === 'text'
184- // During streaming, use raw content for smooth performance
185- // Only strip special tags after streaming completes
186- const cleanBlockContent = isStreaming
187- ? block . content . replace ( / \n { 3 , } / g, '\n\n' )
188- : parseSpecialTags ( block . content ) . cleanContent . replace ( / \n { 3 , } / g, '\n\n' )
199+ // Always strip special tags from display (they're rendered separately as options/plan)
200+ const parsed = parseSpecialTags ( block . content )
201+ const cleanBlockContent = parsed . cleanContent . replace ( / \n { 3 , } / g, '\n\n' )
189202
190- // Skip if no content after stripping tags (only when not streaming)
191- if ( ! isStreaming && ! cleanBlockContent . trim ( ) ) return null
203+ // Skip if no content after stripping tags
204+ if ( ! cleanBlockContent . trim ( ) ) return null
192205
193206 // Use smooth streaming for the last text block if we're streaming
194207 const shouldUseSmoothing = isStreaming && isLastTextBlock
@@ -495,14 +508,14 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
495508 </ div >
496509 ) }
497510
498- { /* Options selector when agent presents choices - only shown after streaming completes */ }
499- { ! isStreaming && parsedTags ?. options && Object . keys ( parsedTags . options ) . length > 0 && (
511+ { /* Options selector when agent presents choices - streams in but disabled until complete */ }
512+ { parsedTags ?. options && Object . keys ( parsedTags . options ) . length > 0 && (
500513 < OptionsSelector
501514 options = { parsedTags . options }
502515 onSelect = { handleOptionSelect }
503- disabled = { isSendingMessage }
504- enableKeyboardNav = { isLastMessage }
505- streaming = { false }
516+ disabled = { isSendingMessage || isStreaming }
517+ enableKeyboardNav = { isLastMessage && ! isStreaming && parsedTags . optionsComplete === true }
518+ streaming = { isStreaming || ! parsedTags . optionsComplete }
506519 />
507520 ) }
508521 </ div >
0 commit comments