Debounce text edit callbacks to optimize performance in TFs and Items nodes (#203)

This commit is contained in:
ianarawjo 2024-01-02 15:43:13 -05:00 committed by GitHub
parent 4a45bd6b75
commit 32c62225d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 63 additions and 38 deletions

View File

@ -1,15 +1,15 @@
{
"files": {
"main.css": "/static/css/main.15dfff17.css",
"main.js": "/static/js/main.283b8983.js",
"main.js": "/static/js/main.b9d7b8bf.js",
"static/js/787.4c72bb55.chunk.js": "/static/js/787.4c72bb55.chunk.js",
"index.html": "/index.html",
"main.15dfff17.css.map": "/static/css/main.15dfff17.css.map",
"main.283b8983.js.map": "/static/js/main.283b8983.js.map",
"main.b9d7b8bf.js.map": "/static/js/main.b9d7b8bf.js.map",
"787.4c72bb55.chunk.js.map": "/static/js/787.4c72bb55.chunk.js.map"
},
"entrypoints": [
"static/css/main.15dfff17.css",
"static/js/main.283b8983.js"
"static/js/main.b9d7b8bf.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script async src="https://www.googletagmanager.com/gtag/js?id=G-RN3FDBLMCR"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-RN3FDBLMCR")</script><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="A visual programming environment for prompt engineering"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>ChainForge</title><script defer="defer" src="/static/js/main.283b8983.js"></script><link href="/static/css/main.15dfff17.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><script async src="https://www.googletagmanager.com/gtag/js?id=G-RN3FDBLMCR"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-RN3FDBLMCR")</script><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="A visual programming environment for prompt engineering"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>ChainForge</title><script defer="defer" src="/static/js/main.b9d7b8bf.js"></script><link href="/static/css/main.15dfff17.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { Skeleton, Text } from '@mantine/core';
import useStore from './store';
import NodeLabel from './NodeLabelComponent'
@ -31,20 +31,22 @@ const ItemsNode = ({ data, id }) => {
const [isEditing, setIsEditing] = useState(true);
const [csvInput, setCsvInput] = useState(null);
const [countText, setCountText] = useState(null);
// Only if AI autocomplete is enabled.
// TODO: This is harder to implement; see https://codepen.io/2undercover/pen/oNzyYO
const [autocompletePlaceholders, setAutocompletePlaceholders] = useState([]);
// Whether text field is in a loading state
const [isLoading, setIsLoading] = useState(false);
const [aiSuggestionsManager] = useState(new AISuggestionsManager(
// Do nothing when suggestions are simply updated because we are managing the placeholder state manually here.
undefined,
// When suggestions are refreshed, revise placeholders
setAutocompletePlaceholders
));
// Debounce helpers
const debounceTimeoutRef = useRef(null);
const debounce = (func, delay) => {
return (...args) => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
func(...args);
}, delay);
};
};
// initializing
useEffect(() => {
@ -55,10 +57,13 @@ const ItemsNode = ({ data, id }) => {
// Handle a change in a text fields' input.
const setFieldsFromText = useCallback((text_val) => {
// Update the data for this text fields' id.
let new_data = { text: text_val, fields: processCSV(text_val).map(stripWrappingQuotes).map(escapeBraces) };
setDataPropsForNode(id, new_data);
pingOutputNodes(id);
// Debounce the state change to only run 300 ms after the edit
debounce((_text_val) => {
// Update the data for this text fields' id.
const new_data = { text: _text_val, fields: processCSV(_text_val).map(stripWrappingQuotes).map(escapeBraces) };
setDataPropsForNode(id, new_data);
pingOutputNodes(id);
}, 300)(text_val);
}, [id, pingOutputNodes, setDataPropsForNode]);
const handKeyDown = useCallback((event) => {

View File

@ -15,7 +15,6 @@ import ChatHistoryView from './ChatHistoryView';
import InspectFooter from './InspectFooter';
import { countNumLLMs, setsAreEqual, getLLMsInPulledInputData } from './backend/utils';
import LLMResponseInspectorDrawer from './LLMResponseInspectorDrawer';
import { DuplicateVariableNameError } from './backend/errors';
const getUniqueLLMMetavarKey = (responses) => {
const metakeys = new Set(responses.map(resp_obj => Object.keys(resp_obj.metavars)).flat());
@ -164,7 +163,7 @@ const PromptNode = ({ data, id, type: node_type }) => {
})) {
signalDirty();
}
}, [setDataPropsForNode, signalDirty]);
}, [signalDirty]);
const updateShowContToggle = useCallback((pulled_data) => {
if (node_type === 'chat') return; // always show when chat node
@ -232,16 +231,18 @@ const PromptNode = ({ data, id, type: node_type }) => {
}, []);
// On upstream changes
const refresh = useMemo(() => data.refresh, [data.refresh]);
const refreshLLMList = useMemo(() => data.refreshLLMList, [data.refreshLLMList]);
useEffect(() => {
if (data.refresh === true) {
if (refresh === true) {
setDataPropsForNode(id, { refresh: false });
setStatus('warning');
handleOnConnect();
} else if (data.refreshLLMList === true) {
} else if (refreshLLMList === true) {
llmListContainer?.current?.refreshLLMProviderList();
setDataPropsForNode(id, { refreshLLMList: false });
}
}, [data]);
}, [refresh, refreshLLMList]);
// Chat nodes only. Pulls input data attached to the 'past conversations' handle.
// Returns a tuple (past_chat_llms, __past_chats), where both are undefined if nothing is connected.
@ -685,7 +686,7 @@ const PromptNode = ({ data, id, type: node_type }) => {
setNumGenerations(n);
setDataPropsForNode(id, {n: n});
}
}, [numGenerationsLastRun, setDataPropsForNode, status]);
}, [numGenerationsLastRun, status]);
const hideStatusIndicator = () => {
if (status !== 'none') { setStatus('none'); }

View File

@ -9,7 +9,6 @@ import BaseNode from './BaseNode';
import { setsAreEqual } from './backend/utils';
import AIPopover from './AiPopover';
import AISuggestionsManager from './backend/aiSuggestionsManager';
import DefaultDict from './backend/defaultdict';
// Helper funcs
const union = (setA, setB) => {
@ -48,6 +47,19 @@ const TextFieldsNode = ({ data, id }) => {
// Placeholders to show in the textareas. Object keyed by textarea index.
let [placeholders, setPlaceholders] = useState({});
// Debounce helpers
const debounceTimeoutRef = useRef(null);
const debounce = (func, delay) => {
return (...args) => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
func(...args);
}, delay);
};
};
const getUID = useCallback((textFields) => {
if (textFields) {
return 'f' + (1 + Object.keys(textFields).reduce((acc, key) => (
@ -133,7 +145,7 @@ const TextFieldsNode = ({ data, id }) => {
}, [templateVars]);
// Save the state of a textfield when it changes and update hooks
const handleTextFieldChange = useCallback((field_id, val) => {
const handleTextFieldChange = useCallback((field_id, val, shouldDebounce) => {
// Update the value of the controlled Textarea component
let new_fields = {...textfieldsValues};
new_fields[field_id] = val;
@ -142,8 +154,12 @@ const TextFieldsNode = ({ data, id }) => {
// Update the data for the ReactFlow node
let new_data = updateTemplateVars({ 'fields': new_fields });
if (new_data.vars) setTemplateVars(new_data.vars);
setDataPropsForNode(id, new_data);
pingOutputNodes(id);
// Debounce the global state change to happen only after 300ms, as it forces a costly rerender:
debounce((_id, _new_data) => {
setDataPropsForNode(_id, _new_data);
pingOutputNodes(_id);
}, shouldDebounce ? 300 : 1)(id, new_data);
}, [textfieldsValues, setTextfieldsValues, templateVars, updateTemplateVars, setTemplateVars, pingOutputNodes, setDataPropsForNode, id]);
@ -153,7 +169,7 @@ const TextFieldsNode = ({ data, id }) => {
useEffect(() => {
const node_height = ref.current.clientHeight;
setHooksY(node_height + 68);
}, [textfieldsValues, handleTextFieldChange]);
}, [textfieldsValues]);
const setRef = useCallback((elem) => {
// To listen for resize events of the textarea, we need to use a ResizeObserver.
@ -176,11 +192,13 @@ const TextFieldsNode = ({ data, id }) => {
}, [ref]);
// Pass upstream changes down to later nodes in the chain
const refresh = useMemo(() => data.refresh, [data.refresh]);
useEffect(() => {
if (data.refresh && data.refresh === true) {
if (refresh === true) {
pingOutputNodes(id);
setDataPropsForNode(id, {refresh: false});
}
}, [data, id, pingOutputNodes]);
}, [refresh]);
// Handle keydown events for the text fields
function handleTextAreaKeyDown(event, placeholder, textareaIndex) {
@ -255,7 +273,8 @@ const TextFieldsNode = ({ data, id }) => {
value={textfieldsValues[i]}
placeholder={flags["aiAutocomplete"] ? placeholder : undefined}
disabled={fieldVisibility[i] === false}
onChange={(event) => handleTextFieldChange(i, event.currentTarget.value)}
onBlur={(event) => handleTextFieldChange(i, event.currentTarget.value, false)}
onChange={(event) => handleTextFieldChange(i, event.currentTarget.value, true)}
onKeyDown={(event) => handleTextAreaKeyDown(event, placeholder, i)} />
{Object.keys(textfieldsValues).length > 1 ? (
<div style={{display: 'flex', flexDirection: 'column'}}>

View File

@ -6,7 +6,7 @@ def readme():
setup(
name='chainforge',
version='0.2.8.5',
version='0.2.8.6',
packages=find_packages(),
author="Ian Arawjo",
description="A Visual Programming Environment for Prompt Engineering",