@@ -11,7 +11,11 @@ import { useActiveSessionState } from '../hooks/useActiveSessionState';
1111import { RichTextInput , type MentionState } from './RichTextInput' ;
1212import { FileMentionPicker } from './FileMentionPicker' ;
1313import { globalEventBus } from '../../infrastructure/event-bus' ;
14- import { useSessionDerivedState , useSessionStateMachineActions } from '../hooks/useSessionStateMachine' ;
14+ import {
15+ useSessionDerivedState ,
16+ useSessionStateMachine ,
17+ useSessionStateMachineActions ,
18+ } from '../hooks/useSessionStateMachine' ;
1519import { SessionExecutionEvent } from '../state-machine/types' ;
1620import TokenUsageIndicator from './TokenUsageIndicator' ;
1721import { ModelSelector } from './ModelSelector' ;
@@ -39,6 +43,9 @@ import { resolveSessionRelationship } from '../utils/sessionMetadata';
3943import { useSceneStore } from '@/app/stores/sceneStore' ;
4044import type { SceneTabId } from '@/app/components/SceneBar/types' ;
4145import type { SkillInfo } from '@/infrastructure/config/types' ;
46+ import { aiExperienceConfigService } from '@/infrastructure/config/services/AIExperienceConfigService' ;
47+ import { deriveChatInputPetMood } from '../utils/chatInputPetMood' ;
48+ import { ChatInputPixelPet } from './ChatInputPixelPet' ;
4249import './ChatInput.scss' ;
4350
4451const log = createLogger ( 'ChatInput' ) ;
@@ -136,6 +143,22 @@ export const ChatInput: React.FC<ChatInputProps> = ({
136143 effectiveTargetSessionId ,
137144 inputState . value . trim ( )
138145 ) ;
146+ const sessionMachineSnapshot = useSessionStateMachine ( effectiveTargetSessionId ) ;
147+ const petMood = useMemo (
148+ ( ) => deriveChatInputPetMood ( sessionMachineSnapshot ) ,
149+ [ sessionMachineSnapshot ] ,
150+ ) ;
151+ const [ agentCompanionEnabled , setAgentCompanionEnabled ] = useState (
152+ ( ) => aiExperienceConfigService . getSettings ( ) . enable_agent_companion ,
153+ ) ;
154+ useEffect ( ( ) => {
155+ setAgentCompanionEnabled ( aiExperienceConfigService . getSettings ( ) . enable_agent_companion ) ;
156+ return aiExperienceConfigService . addChangeListener ( settings => {
157+ setAgentCompanionEnabled ( settings . enable_agent_companion ) ;
158+ } ) ;
159+ } , [ ] ) ;
160+ const showCollapsedPet =
161+ agentCompanionEnabled && ! inputState . isActive && ! inputState . value . trim ( ) ;
139162 const { transition, setQueuedInput } = useSessionStateMachineActions ( effectiveTargetSessionId ) ;
140163
141164 const { workspace, workspacePath } = useCurrentWorkspace ( ) ;
@@ -1729,10 +1752,37 @@ export const ChatInput: React.FC<ChatInputProps> = ({
17291752 return ( ) => observer . disconnect ( ) ;
17301753 // eslint-disable-next-line react-hooks/exhaustive-deps
17311754 } , [ ] ) ;
1732-
1755+
1756+ const isCollapsedProcessing = ! inputState . isActive && ! ! derivedState ?. isProcessing ;
1757+ const petReplacesStopChrome = agentCompanionEnabled && isCollapsedProcessing ;
1758+ const petStopClickable = petReplacesStopChrome && ! ! derivedState ?. canCancel ;
1759+ const collapsedPetSplitSend =
1760+ petReplacesStopChrome && derivedState ?. sendButtonMode === 'split' ;
1761+
17331762 const renderActionButton = ( ) => {
17341763 if ( ! derivedState ) return < IconButton className = "bitfun-chat-input__send-button" disabled size = "small" > < ArrowUp size = { 11 } /> </ IconButton > ;
1735-
1764+
1765+ if ( petReplacesStopChrome ) {
1766+ const { sendButtonMode } = derivedState ;
1767+ if ( sendButtonMode === 'cancel' ) {
1768+ return null ;
1769+ }
1770+ if ( sendButtonMode === 'split' ) {
1771+ return (
1772+ < IconButton
1773+ className = "bitfun-chat-input__send-button"
1774+ onClick = { handleSendOrCancel }
1775+ disabled = { ! inputState . value . trim ( ) }
1776+ data-testid = "chat-input-send-btn"
1777+ tooltip = { t ( 'input.sendShortcut' ) }
1778+ size = "small"
1779+ >
1780+ < ArrowUp size = { 11 } />
1781+ </ IconButton >
1782+ ) ;
1783+ }
1784+ }
1785+
17361786 const { sendButtonMode, hasQueuedInput } = derivedState ;
17371787
17381788 if ( sendButtonMode === 'cancel' ) {
@@ -1805,8 +1855,6 @@ export const ChatInput: React.FC<ChatInputProps> = ({
18051855 ) ;
18061856 } ;
18071857
1808- const isCollapsedProcessing = ! inputState . isActive && ! ! derivedState ?. isProcessing ;
1809-
18101858 return (
18111859 < >
18121860 < ContextDropZone
@@ -1827,7 +1875,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
18271875 >
18281876 < div
18291877 ref = { containerRef }
1830- className = { `bitfun-chat-input ${ inputState . isActive ? 'bitfun-chat-input--active' : 'bitfun-chat-input--collapsed' } ${ inputState . isExpanded ? 'bitfun-chat-input--expanded' : '' } ${ derivedState ?. isProcessing ? 'bitfun-chat-input--processing' : '' } ${ className } ` }
1878+ className = { `bitfun-chat-input ${ inputState . isActive ? 'bitfun-chat-input--active' : 'bitfun-chat-input--collapsed' } ${ inputState . isExpanded ? 'bitfun-chat-input--expanded' : '' } ${ derivedState ?. isProcessing ? 'bitfun-chat-input--processing' : '' } ${ showCollapsedPet ? 'bitfun-chat-input--pet-visible' : '' } ${ petReplacesStopChrome ? 'bitfun-chat-input--pet-replaces-stop' : '' } ${ collapsedPetSplitSend ? 'bitfun-chat-input--pet-split-send' : '' } ${ className } ` }
18311879 onClick = { ! inputState . isActive ? handleActivate : undefined }
18321880 data-testid = "chat-input-container"
18331881 >
@@ -1840,6 +1888,41 @@ export const ChatInput: React.FC<ChatInputProps> = ({
18401888
18411889 < div className = "bitfun-chat-input__container" >
18421890 < div className = { `bitfun-chat-input__box ${ inputState . isExpanded ? 'bitfun-chat-input__box--expanded' : '' } ` } >
1891+ { showCollapsedPet && (
1892+ < div
1893+ className = { [
1894+ 'bitfun-chat-input__pet-wrap' ,
1895+ petReplacesStopChrome ? 'bitfun-chat-input__pet-wrap--shift' : '' ,
1896+ collapsedPetSplitSend ? 'bitfun-chat-input__pet-wrap--split' : '' ,
1897+ ]
1898+ . filter ( Boolean )
1899+ . join ( ' ' ) }
1900+ >
1901+ < div className = "bitfun-chat-input__pet-inner" >
1902+ { petStopClickable ? (
1903+ < button
1904+ type = "button"
1905+ className = "bitfun-chat-input__pet-stop-btn"
1906+ onClick = { e => {
1907+ e . stopPropagation ( ) ;
1908+ void transition ( SessionExecutionEvent . USER_CANCEL ) ;
1909+ } }
1910+ aria-label = { t ( 'input.stopGeneration' ) }
1911+ >
1912+ < ChatInputPixelPet
1913+ mood = { petMood }
1914+ layout = { petReplacesStopChrome ? 'stopRight' : 'center' }
1915+ />
1916+ </ button >
1917+ ) : (
1918+ < ChatInputPixelPet
1919+ mood = { petMood }
1920+ layout = { petReplacesStopChrome ? 'stopRight' : 'center' }
1921+ />
1922+ ) }
1923+ </ div >
1924+ </ div >
1925+ ) }
18431926 { showTargetSwitcher && (
18441927 < div className = "bitfun-chat-input__target-switcher" data-testid = "chat-input-target-switcher" >
18451928 < span className = "bitfun-chat-input__target-switcher-label" > { t ( 'chatInput.conversationTarget' ) } </ span >
@@ -1884,7 +1967,10 @@ export const ChatInput: React.FC<ChatInputProps> = ({
18841967 data-testid = "chat-input-textarea"
18851968 />
18861969
1887- { ! inputState . isActive && ! inputState . value . trim ( ) && (
1970+ { ! inputState . isActive &&
1971+ ! inputState . value . trim ( ) &&
1972+ ! agentCompanionEnabled &&
1973+ ! isCollapsedProcessing && (
18881974 < span className = "bitfun-chat-input__space-hint" >
18891975 < Trans
18901976 i18nKey = "input.spaceToActivate"
@@ -2239,7 +2325,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
22392325 ) }
22402326 </ div >
22412327 < div className = "bitfun-chat-input__actions-right" >
2242- { isCollapsedProcessing && (
2328+ { isCollapsedProcessing && ! petReplacesStopChrome && (
22432329 < >
22442330 < span className = "bitfun-chat-input__capsule-divider" />
22452331 < span className = "bitfun-chat-input__cancel-shortcut" >
0 commit comments