mirror of
https://github.com/ianarawjo/ChainForge.git
synced 2025-03-14 08:16:37 +00:00
Debounce text edit callbacks to optimize performance in TFs and Items nodes (#203)
This commit is contained in:
parent
4a45bd6b75
commit
32c62225d2
@ -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"
|
||||
]
|
||||
}
|
@ -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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
35
chainforge/react-server/src/ItemsNode.js
vendored
35
chainforge/react-server/src/ItemsNode.js
vendored
@ -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) => {
|
||||
|
13
chainforge/react-server/src/PromptNode.js
vendored
13
chainforge/react-server/src/PromptNode.js
vendored
@ -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'); }
|
||||
|
35
chainforge/react-server/src/TextFieldsNode.js
vendored
35
chainforge/react-server/src/TextFieldsNode.js
vendored
@ -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'}}>
|
||||
|
Loading…
x
Reference in New Issue
Block a user