canvasData,
replaceMessageAndGenerate,
} = useAppContext();
- const [inputMsg, setInputMsg] = useState(prefilledMsg.content());
- const inputRef = useRef<HTMLTextAreaElement>(null);
+ const textarea = useOptimizedTextarea(prefilledMsg.content());
- const { extraContext, clearExtraContext } = useVSCodeContext(
- inputRef,
- setInputMsg
- );
+ const { extraContext, clearExtraContext } = useVSCodeContext(textarea);
// TODO: improve this when we have "upload file" feature
const currExtra: Message['extra'] = extraContext ? [extraContext] : undefined;
};
const sendNewMessage = async () => {
- if (inputMsg.trim().length === 0 || isGenerating(currConvId ?? '')) return;
- const lastInpMsg = inputMsg;
- setInputMsg('');
+ const lastInpMsg = textarea.value();
+ if (lastInpMsg.trim().length === 0 || isGenerating(currConvId ?? ''))
+ return;
+ textarea.setValue('');
scrollToBottom(false);
setCurrNodeId(-1);
// get the last message node
!(await sendMessage(
currConvId,
lastMsgNodeId,
- inputMsg,
+ lastInpMsg,
currExtra,
onChunk
))
) {
// restore the input message if failed
- setInputMsg(lastInpMsg);
+ textarea.setValue(lastInpMsg);
}
// OK
clearExtraContext();
// send the prefilled message if needed
sendNewMessage();
} else {
- // otherwise, focus on the input and move the cursor to the end
- if (inputRef.current) {
- inputRef.current.focus();
- inputRef.current.selectionStart = inputRef.current.value.length;
- }
+ // otherwise, focus on the input
+ textarea.focus();
}
prefilledMsg.clear();
// no need to keep track of sendNewMessage
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [inputRef]);
+ }, [textarea.ref]);
// 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)
const pendingMsgDisplay: MessageDisplay[] =
<textarea
className="textarea textarea-bordered w-full"
placeholder="Type a message (Shift+Enter to add a new line)"
- ref={inputRef}
- value={inputMsg}
- onChange={(e) => setInputMsg(e.target.value)}
+ ref={textarea.ref}
onKeyDown={(e) => {
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
if (e.key === 'Enter' && e.shiftKey) return;
Stop
</button>
) : (
- <button
- className="btn btn-primary ml-2"
- onClick={sendNewMessage}
- disabled={inputMsg.trim().length === 0}
- >
+ <button className="btn btn-primary ml-2" onClick={sendNewMessage}>
Send
</button>
)}
</div>
);
}
+
+export interface OptimizedTextareaValue {
+ value: () => string;
+ setValue: (value: string) => void;
+ focus: () => void;
+ ref: React.RefObject<HTMLTextAreaElement>;
+}
+
+// This is a workaround to prevent the textarea from re-rendering when the inner content changes
+// See https://github.com/ggml-org/llama.cpp/pull/12299
+function useOptimizedTextarea(initValue: string): OptimizedTextareaValue {
+ const [savedInitValue, setSavedInitValue] = useState<string>(initValue);
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
+
+ useEffect(() => {
+ if (textareaRef.current && savedInitValue) {
+ textareaRef.current.value = savedInitValue;
+ setSavedInitValue('');
+ }
+ }, [textareaRef, savedInitValue, setSavedInitValue]);
+
+ return {
+ value: () => {
+ return textareaRef.current?.value ?? savedInitValue;
+ },
+ setValue: (value: string) => {
+ if (textareaRef.current) {
+ textareaRef.current.value = value;
+ }
+ },
+ focus: () => {
+ if (textareaRef.current) {
+ // focus and move the cursor to the end
+ textareaRef.current.focus();
+ textareaRef.current.selectionStart = textareaRef.current.value.length;
+ }
+ },
+ ref: textareaRef,
+ };
+}
import { useEffect, useState } from 'react';
import { MessageExtraContext } from './types';
+import { OptimizedTextareaValue } from '../components/ChatScreen';
// Extra context when using llama.cpp WebUI from llama-vscode, inside an iframe
// Ref: https://github.com/ggml-org/llama.cpp/pull/11940
* window.postMessage({ command: 'setText', text: 'Spot the syntax error', context: 'def test()\n return 123' }, '*');
*/
-export const useVSCodeContext = (
- inputRef: React.RefObject<HTMLTextAreaElement>,
- setInputMsg: (text: string) => void
-) => {
+export const useVSCodeContext = (textarea: OptimizedTextareaValue) => {
const [extraContext, setExtraContext] = useState<MessageExtraContext | null>(
null
);
const handleMessage = (event: MessageEvent) => {
if (event.data?.command === 'setText') {
const data: SetTextEvData = event.data;
- setInputMsg(data?.text);
+ textarea.setValue(data?.text);
if (data?.context && data.context.length > 0) {
setExtraContext({
type: 'context',
content: data.context,
});
}
- inputRef.current?.focus();
+ textarea.focus();
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
- }, [inputRef, setInputMsg]);
+ }, [textarea]);
// Add a keydown listener that sends the "escapePressed" message to the parent window
useEffect(() => {