@@ -99,13 +99,9 @@ export default function ChatScreen() {
9999    canvasData, 
100100    replaceMessageAndGenerate, 
101101  }  =  useAppContext ( ) ; 
102-   const  [ inputMsg ,  setInputMsg ]  =  useState ( prefilledMsg . content ( ) ) ; 
103-   const  inputRef  =  useRef < HTMLTextAreaElement > ( null ) ; 
102+   const  textarea  =  useOptimizedTextarea ( prefilledMsg . content ( ) ) ; 
104103
105-   const  {  extraContext,  clearExtraContext }  =  useVSCodeContext ( 
106-     inputRef , 
107-     setInputMsg 
108-   ) ; 
104+   const  {  extraContext,  clearExtraContext }  =  useVSCodeContext ( textarea ) ; 
109105  // TODO: improve this when we have "upload file" feature 
110106  const  currExtra : Message [ 'extra' ]  =  extraContext  ? [ extraContext ]  : undefined ; 
111107
@@ -135,9 +131,10 @@ export default function ChatScreen() {
135131  } ; 
136132
137133  const  sendNewMessage  =  async  ( )  =>  { 
138-     if  ( inputMsg . trim ( ) . length  ===  0  ||  isGenerating ( currConvId  ??  '' ) )  return ; 
139-     const  lastInpMsg  =  inputMsg ; 
140-     setInputMsg ( '' ) ; 
134+     const  lastInpMsg  =  textarea . value ( ) ; 
135+     if  ( lastInpMsg . trim ( ) . length  ===  0  ||  isGenerating ( currConvId  ??  '' ) ) 
136+       return ; 
137+     textarea . setValue ( '' ) ; 
141138    scrollToBottom ( false ) ; 
142139    setCurrNodeId ( - 1 ) ; 
143140    // get the last message node 
@@ -146,13 +143,13 @@ export default function ChatScreen() {
146143      ! ( await  sendMessage ( 
147144        currConvId , 
148145        lastMsgNodeId , 
149-         inputMsg , 
146+         lastInpMsg , 
150147        currExtra , 
151148        onChunk 
152149      ) ) 
153150    )  { 
154151      // restore the input message if failed 
155-       setInputMsg ( lastInpMsg ) ; 
152+       textarea . setValue ( lastInpMsg ) ; 
156153    } 
157154    // OK 
158155    clearExtraContext ( ) ; 
@@ -195,16 +192,13 @@ export default function ChatScreen() {
195192      // send the prefilled message if needed 
196193      sendNewMessage ( ) ; 
197194    }  else  { 
198-       // otherwise, focus on the input and move the cursor to the end 
199-       if  ( inputRef . current )  { 
200-         inputRef . current . focus ( ) ; 
201-         inputRef . current . selectionStart  =  inputRef . current . value . length ; 
202-       } 
195+       // otherwise, focus on the input 
196+       textarea . focus ( ) ; 
203197    } 
204198    prefilledMsg . clear ( ) ; 
205199    // no need to keep track of sendNewMessage 
206200    // eslint-disable-next-line react-hooks/exhaustive-deps 
207-   } ,  [ inputRef ] ) ; 
201+   } ,  [ textarea . ref ] ) ; 
208202
209203  // due to some timing issues of StorageUtils.appendMsg(), we need to make sure the pendingMsg is not duplicated upon rendering (i.e. appears once in the saved conversation and once in the pendingMsg) 
210204  const  pendingMsgDisplay : MessageDisplay [ ]  = 
@@ -258,9 +252,7 @@ export default function ChatScreen() {
258252          < textarea 
259253            className = "textarea textarea-bordered w-full" 
260254            placeholder = "Type a message (Shift+Enter to add a new line)" 
261-             ref = { inputRef } 
262-             value = { inputMsg } 
263-             onChange = { ( e )  =>  setInputMsg ( e . target . value ) } 
255+             ref = { textarea . ref } 
264256            onKeyDown = { ( e )  =>  { 
265257              if  ( e . nativeEvent . isComposing  ||  e . keyCode  ===  229 )  return ; 
266258              if  ( e . key  ===  'Enter'  &&  e . shiftKey )  return ; 
@@ -280,11 +272,7 @@ export default function ChatScreen() {
280272              Stop
281273            </ button > 
282274          )  : ( 
283-             < button 
284-               className = "btn btn-primary ml-2" 
285-               onClick = { sendNewMessage } 
286-               disabled = { inputMsg . trim ( ) . length  ===  0 } 
287-             > 
275+             < button  className = "btn btn-primary ml-2"  onClick = { sendNewMessage } > 
288276              Send
289277            </ button > 
290278          ) } 
@@ -298,3 +286,43 @@ export default function ChatScreen() {
298286    </ div > 
299287  ) ; 
300288} 
289+ 
290+ export  interface  OptimizedTextareaValue  { 
291+   value : ( )  =>  string ; 
292+   setValue : ( value : string )  =>  void ; 
293+   focus : ( )  =>  void ; 
294+   ref : React . RefObject < HTMLTextAreaElement > ; 
295+ } 
296+ 
297+ // This is a workaround to prevent the textarea from re-rendering when the inner content changes 
298+ // See https://github.com/ggml-org/llama.cpp/pull/12299 
299+ function  useOptimizedTextarea ( initValue : string ) : OptimizedTextareaValue  { 
300+   const  [ savedInitValue ,  setSavedInitValue ]  =  useState < string > ( initValue ) ; 
301+   const  textareaRef  =  useRef < HTMLTextAreaElement > ( null ) ; 
302+ 
303+   useEffect ( ( )  =>  { 
304+     if  ( textareaRef . current  &&  savedInitValue )  { 
305+       textareaRef . current . value  =  savedInitValue ; 
306+       setSavedInitValue ( '' ) ; 
307+     } 
308+   } ,  [ textareaRef ,  savedInitValue ,  setSavedInitValue ] ) ; 
309+ 
310+   return  { 
311+     value : ( )  =>  { 
312+       return  textareaRef . current ?. value  ??  savedInitValue ; 
313+     } , 
314+     setValue : ( value : string )  =>  { 
315+       if  ( textareaRef . current )  { 
316+         textareaRef . current . value  =  value ; 
317+       } 
318+     } , 
319+     focus : ( )  =>  { 
320+       if  ( textareaRef . current )  { 
321+         // focus and move the cursor to the end 
322+         textareaRef . current . focus ( ) ; 
323+         textareaRef . current . selectionStart  =  textareaRef . current . value . length ; 
324+       } 
325+     } , 
326+     ref : textareaRef , 
327+   } ; 
328+ } 
0 commit comments