1- import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
22import { Box , Text , useInput } from 'ink' ;
33import Spinner from 'ink-spinner' ;
4+ import { Alert } from '@inkjs/ui' ;
45import ScrollableSelectInput from '../common/ScrollableSelectInput.js' ;
56import {
67 fetchAvailableModels ,
@@ -60,6 +61,10 @@ export const ModelsPanel: React.FC<Props> = ({
6061 const [ manualInputValue , setManualInputValue ] = useState ( '' ) ;
6162 const [ hasStartedLoading , setHasStartedLoading ] = useState ( false ) ;
6263
64+ // 使用 ref 同步追踪选择状态,解决 ESC 键需要按两次的问题
65+ const isSelectingRef = useRef ( false ) ;
66+ const manualInputModeRef = useRef ( false ) ;
67+
6368 // Thinking settings (aligned with ConfigScreen)
6469 const [ requestMethod , setRequestMethod ] = useState < RequestMethod > ( 'chat' ) ;
6570 const [ showThinking , setShowThinking ] = useState ( true ) ;
@@ -98,8 +103,10 @@ export const ModelsPanel: React.FC<Props> = ({
98103
99104 // Reset transient UI state
100105 setIsSelecting ( false ) ;
106+ isSelectingRef . current = false ;
101107 setSearchTerm ( '' ) ;
102108 setManualInputMode ( false ) ;
109+ manualInputModeRef . current = false ;
103110 setManualInputValue ( '' ) ;
104111 setHasStartedLoading ( false ) ;
105112 setThinkingFocusIndex ( 0 ) ;
@@ -130,6 +137,17 @@ export const ModelsPanel: React.FC<Props> = ({
130137 ) ;
131138 } , [ visible , advancedModel , basicModel ] ) ;
132139
140+ // Auto-hide error message after 3 seconds
141+ useEffect ( ( ) => {
142+ if ( errorMessage ) {
143+ const timer = setTimeout ( ( ) => {
144+ setErrorMessage ( '' ) ;
145+ } , 3000 ) ;
146+ return ( ) => clearTimeout ( timer ) ;
147+ }
148+ return undefined ;
149+ } , [ errorMessage ] ) ;
150+
133151 const modelTarget : 'advanced' | 'basic' | 'thinking' =
134152 activeTab === 'basic'
135153 ? 'basic'
@@ -206,8 +224,10 @@ export const ModelsPanel: React.FC<Props> = ({
206224 const handleModelSelect = useCallback (
207225 ( value : string ) => {
208226 if ( value === '__MANUAL_INPUT__' ) {
227+ isSelectingRef . current = false ;
209228 setIsSelecting ( false ) ;
210229 setSearchTerm ( '' ) ;
230+ manualInputModeRef . current = true ;
211231 setManualInputMode ( true ) ;
212232 setManualInputValue ( currentModel ) ;
213233 setHasStartedLoading ( false ) ;
@@ -218,6 +238,7 @@ export const ModelsPanel: React.FC<Props> = ({
218238 if ( modelTarget !== 'thinking' ) {
219239 void applyModel ( value , modelTarget ) ;
220240 }
241+ isSelectingRef . current = false ;
221242 setIsSelecting ( false ) ;
222243 setSearchTerm ( '' ) ;
223244 setHasStartedLoading ( false ) ;
@@ -230,9 +251,11 @@ export const ModelsPanel: React.FC<Props> = ({
230251 if ( cleaned && modelTarget !== 'thinking' ) {
231252 void applyModel ( cleaned , modelTarget ) ;
232253 }
254+ manualInputModeRef . current = false ;
233255 setManualInputMode ( false ) ;
234256 setManualInputValue ( '' ) ;
235257 setSearchTerm ( '' ) ;
258+ setHasStartedLoading ( false ) ;
236259 } , [ applyModel , manualInputValue , modelTarget ] ) ;
237260
238261 const thinkingEnabledValue = useMemo ( ( ) => {
@@ -476,6 +499,7 @@ export const ModelsPanel: React.FC<Props> = ({
476499
477500 if ( key . escape ) {
478501 // 子视图内 ESC 仅收起回到默认视图。
502+ // 使用 ref 同步检查状态,避免 React 状态更新延迟导致需要按两次 ESC
479503 if ( thinkingInputMode ) {
480504 setThinkingInputMode ( null ) ;
481505 setThinkingInputValue ( '' ) ;
@@ -489,14 +513,16 @@ export const ModelsPanel: React.FC<Props> = ({
489513 setIsThinkingEffortSelecting ( false ) ;
490514 return ;
491515 }
492- if ( manualInputMode ) {
516+ if ( manualInputModeRef . current || manualInputMode ) {
517+ manualInputModeRef . current = false ;
493518 setManualInputMode ( false ) ;
494519 setManualInputValue ( '' ) ;
495520 setSearchTerm ( '' ) ;
496521 setHasStartedLoading ( false ) ;
497522 return ;
498523 }
499- if ( isSelecting ) {
524+ if ( isSelectingRef . current || isSelecting ) {
525+ isSelectingRef . current = false ;
500526 setIsSelecting ( false ) ;
501527 setSearchTerm ( '' ) ;
502528 setHasStartedLoading ( false ) ;
@@ -652,15 +678,20 @@ export const ModelsPanel: React.FC<Props> = ({
652678 // 标记已开始加载流程
653679 setHasStartedLoading ( true ) ;
654680 void loadModels ( )
655- . then ( ( ) => setIsSelecting ( true ) )
681+ . then ( ( ) => {
682+ isSelectingRef . current = true ;
683+ setIsSelecting ( true ) ;
684+ } )
656685 . catch ( ( ) => {
686+ manualInputModeRef . current = true ;
657687 setManualInputMode ( true ) ;
658688 setManualInputValue ( currentModel ) ;
659689 } ) ;
660690 return ;
661691 }
662692
663693 if ( ( input === 'm' || input === 'M' ) && isModelTab ) {
694+ manualInputModeRef . current = true ;
664695 setManualInputMode ( true ) ;
665696 setManualInputValue ( currentModel ) ;
666697 }
@@ -737,12 +768,7 @@ export const ModelsPanel: React.FC<Props> = ({
737768 ) }
738769
739770 { errorMessage && ! loading && (
740- < Box flexDirection = "column" >
741- < Text color = { theme . colors . warning } > { t . modelsPanel . tipLabel } </ Text >
742- < Text color = { theme . colors . menuSecondary } dimColor >
743- { errorMessage }
744- </ Text >
745- </ Box >
771+ < Alert variant = "error" > { errorMessage } </ Alert >
746772 ) }
747773
748774 { activeTab === 'thinking' ? (
@@ -950,7 +976,7 @@ export const ModelsPanel: React.FC<Props> = ({
950976 limit = { 10 }
951977 disableNumberShortcuts = { true }
952978 initialIndex = { selectedIndex }
953- isFocused = { true }
979+ isFocused = { isSelecting }
954980 onSelect = { item => handleModelSelect ( item . value ) }
955981 />
956982 </ Box >
0 commit comments