Skip to content

Commit 8852f34

Browse files
committed
feat: converted to feedback input bar
1 parent 82d0854 commit 8852f34

File tree

7 files changed

+382
-55
lines changed

7 files changed

+382
-55
lines changed

cli/src/chat.tsx

Lines changed: 128 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AgentModeToggle } from './components/agent-mode-toggle'
88
import { Button } from './components/button'
99
import { LoginModal } from './components/login-modal'
1010
import { MessageWithAgents } from './components/message-with-agents'
11-
import { FeedbackModal } from './components/feedback-modal'
11+
import { FeedbackInputMode } from './components/feedback-input-mode'
1212
import {
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
}

cli/src/components/feedback-icon-button.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
99

1010
interface FeedbackIconButtonProps {
1111
onClick?: () => void
12+
onClose?: () => void
13+
isOpen?: boolean
1214
messageId?: string
1315
}
1416

15-
export const FeedbackIconButton: React.FC<FeedbackIconButtonProps> = ({ onClick, messageId }) => {
17+
export const FeedbackIconButton: React.FC<FeedbackIconButtonProps> = ({ onClick, onClose, isOpen, messageId }) => {
1618
const theme = useTheme()
1719
const hover = useHoverToggle()
1820
const hoveredOnceRef = useRef(false)
@@ -34,8 +36,8 @@ export const FeedbackIconButton: React.FC<FeedbackIconButtonProps> = ({ onClick,
3436
}
3537
const handleMouseOut = () => hover.scheduleClose()
3638

37-
const textCollapsed = '[?]'
38-
const textExpanded = '[share feedback]'
39+
const textCollapsed = isOpen ? '[x]' : '[?]'
40+
const textExpanded = isOpen ? '[close x]' : '[share feedback]'
3941

4042
return (
4143
<Button
@@ -45,7 +47,7 @@ export const FeedbackIconButton: React.FC<FeedbackIconButtonProps> = ({ onClick,
4547
paddingLeft: 0,
4648
paddingRight: 0,
4749
}}
48-
onClick={() => onClick?.()}
50+
onClick={() => (isOpen ? onClose?.() : onClick?.())}
4951
onMouseOver={handleMouseOver}
5052
onMouseOut={handleMouseOut}
5153
>

0 commit comments

Comments
 (0)