@@ -8,7 +8,7 @@ import { AgentModeToggle } from './components/agent-mode-toggle'
88import { Button } from './components/button'
99import { LoginModal } from './components/login-modal'
1010import { MessageWithAgents } from './components/message-with-agents'
11- import { FeedbackModal } from './components/feedback-modal '
11+ import { FeedbackInputMode } from './components/feedback-input-mode '
1212import {
1313 MultilineInput ,
1414 type MultilineInputHandle ,
@@ -451,13 +451,29 @@ export const Chat = ({
451451 } )
452452
453453 // Feedback state and handlers
454- const [ isFeedbackOpen , setIsFeedbackOpen ] = useState ( false )
455454 const [ feedbackMessageId , setFeedbackMessageId ] = useState < string | null > ( null )
455+ const [ feedbackMode , setFeedbackMode ] = useState ( false )
456+ const [ feedbackText , setFeedbackText ] = useState ( '' )
457+ const [ feedbackCursor , setFeedbackCursor ] = useState ( 0 )
458+ const [ feedbackCategory , setFeedbackCategory ] = useState < string > ( 'other' )
459+ const [ savedInputValue , setSavedInputValue ] = useState ( '' )
460+ const [ savedCursorPosition , setSavedCursorPosition ] = useState ( 0 )
461+ const [ showFeedbackConfirmation , setShowFeedbackConfirmation ] = useState ( false )
462+ const [ feedbackSentMessage , setFeedbackSentMessage ] = useState < string | null > ( null )
463+ const [ messagesWithFeedback , setMessagesWithFeedback ] = useState < Set < string > > ( new Set ( ) )
456464
457465 const openFeedbackForMessage = useCallback ( ( id : string ) => {
466+ // Save current input state
467+ setSavedInputValue ( inputValue )
468+ setSavedCursorPosition ( cursorPosition )
469+
470+ // Enter feedback mode
458471 setFeedbackMessageId ( id )
459- setIsFeedbackOpen ( true )
460- } , [ ] )
472+ setFeedbackMode ( true )
473+ setFeedbackText ( '' )
474+ setFeedbackCursor ( 0 )
475+ setFeedbackCategory ( 'other' )
476+ } , [ inputValue , cursorPosition ] )
461477
462478 const openFeedbackForLatestMessage = useCallback ( ( ) => {
463479 const latest = [ ...messages ]
@@ -470,6 +486,66 @@ export const Chat = ({
470486 return true
471487 } , [ messages , openFeedbackForMessage ] )
472488
489+ const handleFeedbackSubmit = useCallback ( ( ) => {
490+ const text = feedbackText . trim ( )
491+ if ( text . length === 0 ) return
492+
493+ const target = messages . find ( ( m ) => m . id === feedbackMessageId )
494+ const recent = messages . slice ( Math . max ( 0 , messages . length - 5 ) ) . map ( ( m ) => ( {
495+ id : m . id ,
496+ variant : m . variant ,
497+ timestamp : m . timestamp ,
498+ hasBlocks : ! ! m . blocks ,
499+ contentPreview : ( m . content || '' ) . slice ( 0 , 400 ) ,
500+ } ) )
501+
502+ logger . info (
503+ {
504+ eventId : AnalyticsEvent . FEEDBACK_SUBMITTED ,
505+ source : 'cli' ,
506+ messageId : target ?. id ,
507+ variant : target ?. variant ,
508+ completionTime : target ?. completionTime ,
509+ credits : target ?. credits ,
510+ agentMode,
511+ sessionCreditsUsed,
512+ recentMessages : recent ,
513+ feedback : {
514+ text,
515+ category : feedbackCategory ,
516+ } ,
517+ } ,
518+ )
519+
520+ // Mark this message as having feedback submitted
521+ if ( feedbackMessageId ) {
522+ setMessagesWithFeedback ( prev => new Set ( prev ) . add ( feedbackMessageId ) )
523+ }
524+
525+ // Show success in status indicator and exit feedback mode
526+ setFeedbackMode ( false )
527+ setFeedbackText ( '' )
528+ setFeedbackCategory ( 'other' )
529+ setFeedbackSentMessage ( 'Feedback sent ✔' )
530+ setTimeout ( ( ) => {
531+ setFeedbackSentMessage ( null )
532+ } , 5000 )
533+ } , [ feedbackText , feedbackCategory , feedbackMessageId , messages , agentMode , sessionCreditsUsed ] )
534+
535+ const handleFeedbackCancel = useCallback ( ( ) => {
536+ // Restore saved input
537+ setInputValue ( ( prev ) => ( {
538+ text : savedInputValue ,
539+ cursorPosition : savedCursorPosition ,
540+ lastEditDueToNav : false
541+ } ) )
542+
543+ // Exit feedback mode
544+ setFeedbackMode ( false )
545+ setFeedbackText ( '' )
546+ setFeedbackCategory ( 'other' )
547+ } , [ savedInputValue , savedCursorPosition , setInputValue ] )
548+
473549 const handleSubmit = useCallback (
474550 ( ) =>
475551 routeUserPrompt ( {
@@ -549,7 +625,7 @@ export const Chat = ({
549625 } ,
550626 historyNavUpEnabled,
551627 historyNavDownEnabled,
552- disabled : isFeedbackOpen ,
628+ disabled : feedbackMode ,
553629 } )
554630
555631 const { tree : messageTree , topLevelMessages } = useMemo (
@@ -636,7 +712,7 @@ export const Chat = ({
636712
637713 const statusIndicatorNode = (
638714 < StatusIndicator
639- clipboardMessage = { clipboardMessage }
715+ clipboardMessage = { feedbackSentMessage ?? clipboardMessage }
640716 streamStatus = { streamStatus }
641717 timerStartTime = { timerStartTime }
642718 nextCtrlCWillExit = { nextCtrlCWillExit }
@@ -655,14 +731,17 @@ export const Chat = ({
655731 useKeyboard (
656732 useCallback (
657733 ( key ) => {
734+ // Don't handle if already in feedback mode
735+ if ( feedbackMode ) return
736+
658737 if ( key ?. ctrl && key . name === 'f' ) {
659738 if ( 'preventDefault' in key && typeof key . preventDefault === 'function' ) {
660739 key . preventDefault ( )
661740 }
662741 openFeedbackForLatestMessage ( )
663742 }
664743 } ,
665- [ openFeedbackForLatestMessage ] ,
744+ [ openFeedbackForLatestMessage , feedbackMode ] ,
666745 ) ,
667746 )
668747
@@ -742,6 +821,10 @@ export const Chat = ({
742821 onBuildFast = { handleBuildFast }
743822 onBuildMax = { handleBuildMax }
744823 onFeedback = { openFeedbackForMessage }
824+ feedbackOpenMessageId = { feedbackMessageId }
825+ feedbackMode = { feedbackMode }
826+ onCloseFeedback = { handleFeedbackCancel }
827+ messagesWithFeedback = { messagesWithFeedback }
745828 />
746829 )
747830 } ) }
@@ -823,6 +906,42 @@ export const Chat = ({
823906 { /* Wrap the input row in a single OpenTUI border so the toggle stays inside the flex layout.
824907 The queue preview is injected via the border title rather than custom text nodes, which
825908 keeps the border coupled to the content height while preserving the inline preview look. */ }
909+ { feedbackMode ? (
910+ < FeedbackInputMode
911+ feedbackText = { feedbackText }
912+ feedbackCursor = { feedbackCursor }
913+ category = { feedbackCategory }
914+ onFeedbackTextChange = { ( text , cursor ) => {
915+ setFeedbackText ( text )
916+ setFeedbackCursor ( cursor )
917+ } }
918+ onCategoryChange = { setFeedbackCategory }
919+ onSubmit = { handleFeedbackSubmit }
920+ onCancel = { handleFeedbackCancel }
921+ width = { terminalWidth - 2 }
922+ terminalWidth = { terminalWidth }
923+ />
924+ ) : showFeedbackConfirmation ? (
925+ < box
926+ border
927+ style = { {
928+ width : '100%' ,
929+ borderStyle : 'single' ,
930+ borderColor : theme . success ,
931+ customBorderChars : BORDER_CHARS ,
932+ paddingLeft : 1 ,
933+ paddingRight : 1 ,
934+ paddingTop : 1 ,
935+ paddingBottom : 1 ,
936+ flexDirection : 'row' ,
937+ justifyContent : 'center' ,
938+ } }
939+ >
940+ < text >
941+ < span fg = { theme . success } > ✓ Feedback sent! Thanks for helping us improve.</ span >
942+ </ text >
943+ </ box >
944+ ) : (
826945 < box
827946 title = { queuePreviewTitle ? ` ${ queuePreviewTitle } ` : undefined }
828947 titleAlignment = "center"
@@ -884,7 +1003,7 @@ export const Chat = ({
8841003 ? 'Enter a coding task'
8851004 : 'Enter a coding task or / for commands'
8861005 }
887- focused = { inputFocused && ! isFeedbackOpen }
1006+ focused = { inputFocused && ! feedbackMode }
8881007 maxHeight = { 5 }
8891008 width = { inputWidth }
8901009 onKeyIntercept = { handleSuggestionMenuKey }
@@ -922,6 +1041,7 @@ export const Chat = ({
9221041 ) }
9231042 </ box >
9241043 </ box >
1044+ ) }
9251045
9261046 { /* Paused queue indicator - fake bottom border continuation */ }
9271047 { pausedQueueText && (
@@ -962,41 +1082,6 @@ export const Chat = ({
9621082 />
9631083 ) }
9641084
965- { isFeedbackOpen && (
966- < FeedbackModal
967- open = { isFeedbackOpen }
968- message = { messages . find ( ( m ) => m . id === feedbackMessageId ) ?? null }
969- onClose = { ( ) => setIsFeedbackOpen ( false ) }
970- onSubmit = { ( data ) => {
971- const target = messages . find ( ( m ) => m . id === feedbackMessageId )
972- const recent = messages . slice ( Math . max ( 0 , messages . length - 5 ) ) . map ( ( m ) => ( {
973- id : m . id ,
974- variant : m . variant ,
975- timestamp : m . timestamp ,
976- hasBlocks : ! ! m . blocks ,
977- contentPreview : ( m . content || '' ) . slice ( 0 , 400 ) ,
978- } ) )
979- logger . info (
980- {
981- eventId : AnalyticsEvent . FEEDBACK_SUBMITTED ,
982- source : 'cli' ,
983- messageId : target ?. id ,
984- variant : target ?. variant ,
985- completionTime : target ?. completionTime ,
986- credits : target ?. credits ,
987- agentMode,
988- sessionCreditsUsed,
989- feedbackCategory : data . category ,
990- feedbackText : data . text ,
991- runState : target ?. metadata ?. runState ,
992- recentMessages : recent ,
993- } ,
994- 'User submitted feedback' ,
995- )
996- setIsFeedbackOpen ( false )
997- } }
998- />
999- ) }
10001085 </ box >
10011086 )
10021087}
0 commit comments