@@ -39,6 +39,7 @@ import { loadLocalAgents } from './utils/local-agent-registry'
3939import { buildMessageTree } from './utils/message-tree-utils'
4040import { createMarkdownPalette } from './utils/theme-system'
4141import { BORDER_CHARS } from './utils/ui-constants'
42+ import { computeInputLayoutMetrics } from './utils/text-layout'
4243
4344import type { SendMessageTimerEvent } from './hooks/use-send-message'
4445import type { ContentBlock } from './types/chat'
@@ -535,6 +536,36 @@ export const Chat = ({
535536 ) : null
536537
537538 const shouldShowQueuePreview = queuedMessages . length > 0
539+ const queuePreviewTitle = useMemo ( ( ) => {
540+ if ( ! shouldShowQueuePreview ) return undefined
541+ const previewWidth = Math . max ( 30 , separatorWidth - 20 )
542+ return formatQueuedPreview ( queuedMessages , previewWidth )
543+ } , [ queuedMessages , separatorWidth , shouldShowQueuePreview ] )
544+ const hasSlashSuggestions = slashContext . active && slashSuggestionItems . length > 0
545+ const hasMentionSuggestions =
546+ ! slashContext . active && mentionContext . active && agentSuggestionItems . length > 0
547+ const hasSuggestionMenu = hasSlashSuggestions || hasMentionSuggestions
548+ const showAgentStatusLine = showAgentDisplayName && loadedAgentsData
549+
550+ const inputLayoutMetrics = useMemo ( ( ) => {
551+ const text = inputValue ?? ''
552+ const layoutContent = text . length > 0 ? text : ' '
553+ const safeCursor = Math . max ( 0 , Math . min ( cursorPosition , layoutContent . length ) )
554+ const cursorProbe =
555+ safeCursor >= layoutContent . length
556+ ? layoutContent
557+ : layoutContent . slice ( 0 , safeCursor )
558+ const cols = Math . max ( 1 , inputWidth - 4 )
559+ return computeInputLayoutMetrics ( {
560+ layoutContent,
561+ cursorProbe,
562+ cols,
563+ maxHeight : 5 ,
564+ } )
565+ } , [ inputValue , cursorPosition , inputWidth ] )
566+ const isMultilineInput = inputLayoutMetrics . heightLines > 1
567+ const shouldCenterInputVertically =
568+ ! hasSuggestionMenu && ! showAgentStatusLine && ! isMultilineInput
538569 const shouldShowStatusLine =
539570 streamStatus !== 'idle' ||
540571 shouldShowQueuePreview ||
@@ -711,46 +742,35 @@ export const Chat = ({
711742 </ box >
712743 </ box >
713744
714- { /* Queue preview line - separate row */ }
715- { shouldShowQueuePreview && (
716- < box
717- style = { {
718- flexDirection : 'row' ,
719- width : '100%' ,
720- justifyContent : 'center' ,
721- } }
722- >
723- < text style = { { wrapMode : 'none' } } >
724- < span fg = { theme . secondary } bg = { theme . inputFocusedBg } >
725- { ` ${ formatQueuedPreview (
726- queuedMessages ,
727- Math . max ( 30 , terminalWidth - 10 ) ,
728- ) } `}
729- </ span >
730- </ text >
731- </ box >
732- ) }
733745 </ box >
734746 ) }
747+
735748 < box
749+ title = { queuePreviewTitle ? ` ${ queuePreviewTitle } ` : undefined }
750+ titleAlignment = "center"
736751 style = { {
737752 width : '100%' ,
738753 borderStyle : 'single' ,
739754 borderColor : theme . secondary ,
755+ focusedBorderColor : theme . foreground ,
740756 customBorderChars : BORDER_CHARS ,
757+ paddingLeft : 1 ,
758+ paddingRight : 1 ,
759+ paddingTop : 0 ,
760+ paddingBottom : 0 ,
761+ flexDirection : 'column' ,
762+ gap : hasSuggestionMenu ? 1 : 0 ,
741763 } }
742764 >
743- { slashContext . active && slashSuggestionItems . length > 0 ? (
765+ { hasSlashSuggestions ? (
744766 < SuggestionMenu
745767 items = { slashSuggestionItems }
746768 selectedIndex = { slashSelectedIndex }
747769 maxVisible = { 10 }
748770 prefix = "/"
749771 />
750772 ) : null }
751- { ! slashContext . active &&
752- mentionContext . active &&
753- agentSuggestionItems . length > 0 ? (
773+ { hasMentionSuggestions ? (
754774 < SuggestionMenu
755775 items = { agentSuggestionItems }
756776 selectedIndex = { agentSelectedIndex }
@@ -760,55 +780,65 @@ export const Chat = ({
760780 ) : null }
761781 < box
762782 style = { {
763- flexDirection : 'row' ,
764- alignItems : 'center' ,
765- width : '100%' ,
783+ flexDirection : 'column' ,
784+ justifyContent : shouldCenterInputVertically
785+ ? 'center'
786+ : 'flex-start' ,
787+ minHeight : shouldCenterInputVertically ? 3 : undefined ,
788+ gap : showAgentStatusLine ? 1 : 0 ,
766789 } }
767790 >
768- < box style = { { flexGrow : 1 , minWidth : 0 } } >
769- < MultilineInput
770- value = { inputValue }
771- onChange = { setInputValue }
772- onSubmit = { handleSubmit }
773- placeholder = "Enter a coding task or / for commands"
774- focused = { inputFocused }
775- maxHeight = { 5 }
776- width = { inputWidth }
777- onKeyIntercept = { handleSuggestionMenuKey }
778- textAttributes = { theme . messageTextAttributes }
779- ref = { inputRef }
780- cursorPosition = { cursorPosition }
781- />
782- </ box >
783791 < box
784792 style = { {
785- flexShrink : 0 ,
786- paddingLeft : 2 ,
793+ flexDirection : 'row' ,
794+ alignItems : shouldCenterInputVertically ? 'center' : 'flex-start' ,
795+ width : '100%' ,
787796 } }
788797 >
789- < AgentModeToggle
790- mode = { agentMode }
791- onToggle = { toggleAgentMode }
792- onSelectMode = { setAgentMode }
793- />
798+ < box style = { { flexGrow : 1 , minWidth : 0 } } >
799+ < MultilineInput
800+ value = { inputValue }
801+ onChange = { setInputValue }
802+ onSubmit = { handleSubmit }
803+ placeholder = "Enter a coding task or / for commands"
804+ focused = { inputFocused }
805+ maxHeight = { 5 }
806+ width = { inputWidth }
807+ onKeyIntercept = { handleSuggestionMenuKey }
808+ textAttributes = { theme . messageTextAttributes }
809+ ref = { inputRef }
810+ cursorPosition = { cursorPosition }
811+ />
812+ </ box >
813+ < box
814+ style = { {
815+ flexShrink : 0 ,
816+ paddingLeft : 2 ,
817+ } }
818+ >
819+ < AgentModeToggle
820+ mode = { agentMode }
821+ onToggle = { toggleAgentMode }
822+ onSelectMode = { setAgentMode }
823+ />
824+ </ box >
794825 </ box >
826+ { /* Agent status line - right-aligned under toggle */ }
827+ { showAgentStatusLine && (
828+ < box
829+ style = { {
830+ flexDirection : 'row' ,
831+ justifyContent : 'flex-end' ,
832+ paddingTop : 0 ,
833+ } }
834+ >
835+ < text >
836+ < span fg = { theme . muted } > Agent: { agentDisplayName } </ span >
837+ </ text >
838+ </ box >
839+ ) }
795840 </ box >
796841 </ box >
797- { /* Agent status line - right-aligned under toggle */ }
798- { showAgentDisplayName && loadedAgentsData && (
799- < box
800- style = { {
801- flexDirection : 'row' ,
802- justifyContent : 'flex-end' ,
803- paddingRight : 1 ,
804- paddingTop : 0 ,
805- } }
806- >
807- < text >
808- < span fg = { theme . muted } > Agent: { agentDisplayName } </ span >
809- </ text >
810- </ box >
811- ) }
812842 </ box >
813843
814844 { /* Login Modal Overlay - show when not authenticated and done checking */ }
0 commit comments