@@ -99,16 +99,9 @@ export default function ChatScreen() {
9999 canvasData,
100100 replaceMessageAndGenerate,
101101 } = useAppContext ( ) ;
102- const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
102+ const textarea = useOptimizedTextarea ( prefilledMsg . content ( ) ) ;
103103
104- const { extraContext, clearExtraContext } = useVSCodeContext (
105- inputRef ,
106- ( msg ) => {
107- if ( inputRef ?. current ) {
108- inputRef . current . value = msg ;
109- }
110- }
111- ) ;
104+ const { extraContext, clearExtraContext } = useVSCodeContext ( textarea ) ;
112105 // TODO: improve this when we have "upload file" feature
113106 const currExtra : Message [ 'extra' ] = extraContext ? [ extraContext ] : undefined ;
114107
@@ -138,14 +131,10 @@ export default function ChatScreen() {
138131 } ;
139132
140133 const sendNewMessage = async ( ) => {
141- if (
142- ! inputRef . current ||
143- inputRef . current . value . trim ( ) . length === 0 ||
144- isGenerating ( currConvId ?? '' )
145- )
134+ const lastInpMsg = textarea . value ( ) ;
135+ if ( lastInpMsg . trim ( ) . length === 0 || isGenerating ( currConvId ?? '' ) )
146136 return ;
147- const inputValue = inputRef . current . value ;
148- inputRef . current . value = '' ;
137+ textarea . setValue ( '' ) ;
149138 scrollToBottom ( false ) ;
150139 setCurrNodeId ( - 1 ) ;
151140 // get the last message node
@@ -154,13 +143,13 @@ export default function ChatScreen() {
154143 ! ( await sendMessage (
155144 currConvId ,
156145 lastMsgNodeId ,
157- inputValue ,
146+ lastInpMsg ,
158147 currExtra ,
159148 onChunk
160149 ) )
161150 ) {
162151 // restore the input message if failed
163- inputRef . current . value = inputValue ;
152+ textarea . setValue ( lastInpMsg ) ;
164153 }
165154 // OK
166155 clearExtraContext ( ) ;
@@ -203,16 +192,13 @@ export default function ChatScreen() {
203192 // send the prefilled message if needed
204193 sendNewMessage ( ) ;
205194 } else {
206- // otherwise, focus on the input and move the cursor to the end
207- if ( inputRef . current ) {
208- inputRef . current . focus ( ) ;
209- inputRef . current . selectionStart = inputRef . current . value . length ;
210- }
195+ // otherwise, focus on the input
196+ textarea . focus ( ) ;
211197 }
212198 prefilledMsg . clear ( ) ;
213199 // no need to keep track of sendNewMessage
214200 // eslint-disable-next-line react-hooks/exhaustive-deps
215- } , [ inputRef ] ) ;
201+ } , [ textarea . ref ] ) ;
216202
217203 // 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)
218204 const pendingMsgDisplay : MessageDisplay [ ] =
@@ -266,8 +252,7 @@ export default function ChatScreen() {
266252 < textarea
267253 className = "textarea textarea-bordered w-full"
268254 placeholder = "Type a message (Shift+Enter to add a new line)"
269- ref = { inputRef }
270- defaultValue = { prefilledMsg . content ( ) }
255+ ref = { textarea . ref }
271256 onKeyDown = { ( e ) => {
272257 if ( e . nativeEvent . isComposing || e . keyCode === 229 ) return ;
273258 if ( e . key === 'Enter' && e . shiftKey ) return ;
@@ -301,3 +286,53 @@ export default function ChatScreen() {
301286 </ div >
302287 ) ;
303288}
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 ] = useState < string > ( initValue ) ;
301+ const textareaRef = useRef < HTMLTextAreaElement > ( null ) ;
302+
303+ useEffect ( ( ) => {
304+ if ( textareaRef . current ) {
305+ textareaRef . current . value = savedInitValue ;
306+ console . log (
307+ 'useOptimizedTextarea: set initValue' ,
308+ savedInitValue ,
309+ textareaRef . current
310+ ) ;
311+ }
312+ } , [ textareaRef , savedInitValue ] ) ;
313+
314+ return {
315+ value : ( ) => {
316+ return textareaRef . current ?. value ?? savedInitValue ;
317+ } ,
318+ setValue : ( value : string ) => {
319+ if ( textareaRef . current ) {
320+ textareaRef . current . value = value ;
321+ console . log (
322+ 'useOptimizedTextarea: set value' ,
323+ value ,
324+ textareaRef . current
325+ ) ;
326+ }
327+ } ,
328+ focus : ( ) => {
329+ if ( textareaRef . current ) {
330+ // focus and move the cursor to the end
331+ textareaRef . current . focus ( ) ;
332+ textareaRef . current . selectionStart = textareaRef . current . value . length ;
333+ console . log ( 'useOptimizedTextarea: focus' , textareaRef . current ) ;
334+ }
335+ } ,
336+ ref : textareaRef ,
337+ } ;
338+ }
0 commit comments