diff --git a/chainforge/react-server/package-lock.json b/chainforge/react-server/package-lock.json
index 8e2498a..3a2c503 100644
--- a/chainforge/react-server/package-lock.json
+++ b/chainforge/react-server/package-lock.json
@@ -4961,20 +4961,20 @@
}
},
"node_modules/@tabler/icons": {
- "version": "2.17.0",
- "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.17.0.tgz",
- "integrity": "sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==",
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.24.0.tgz",
+ "integrity": "sha512-Otv6zrVF3HU54G6FK7OPODcQmKR9KgM6Ppi+ib3gHHB1LZEs2HIdQJYTHP5dGE+yOQWtXS9ZnGmSZDkSFLbkkg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons-react": {
- "version": "2.17.0",
- "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.17.0.tgz",
- "integrity": "sha512-kuEW+qNwRqcK5iMl7qTapzX2NiMOwPg4Az01d+IZ1DIMwaZ7iKPJaIor2ihKFLPYrT9D5BZHXB8R5mSkw0FETg==",
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.24.0.tgz",
+ "integrity": "sha512-0pNc+ffp4HZCsozv9aN/hSDiC/RTGozTmf0MCL4U9NIo8yMQh8q3zEfXRNr18IM2InyIBJL95/1J2kzgU2lYeA==",
"dependencies": {
- "@tabler/icons": "2.17.0",
+ "@tabler/icons": "2.24.0",
"prop-types": "^15.7.2"
},
"funding": {
@@ -5001,11 +5001,11 @@
}
},
"node_modules/@tanstack/react-table": {
- "version": "8.9.1",
- "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.1.tgz",
- "integrity": "sha512-yHs2m6lk5J5RNcu2dNtsDGux66wNXZjEgzxos6MRCX8gL+nqxeW3ZglqP6eANN0bGElPnjvqiUHGQvdACOr3Cw==",
+ "version": "8.9.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.3.tgz",
+ "integrity": "sha512-Ng9rdm3JPoSCi6cVZvANsYnF+UoGVRxflMb270tVj0+LjeT/ZtZ9ckxF6oLPLcKesza6VKBqtdF9mQ+vaz24Aw==",
"dependencies": {
- "@tanstack/table-core": "8.9.1"
+ "@tanstack/table-core": "8.9.3"
},
"engines": {
"node": ">=12"
@@ -5035,9 +5035,9 @@
}
},
"node_modules/@tanstack/table-core": {
- "version": "8.9.1",
- "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.1.tgz",
- "integrity": "sha512-2+R83n8vMZND0q3W1lSiF7co9nFbeWbjAErFf27xwbeA9E0wtUu5ZDfgj+TZ6JzdAEQAgfxkk/QNFAKiS8E4MA==",
+ "version": "8.9.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.9.3.tgz",
+ "integrity": "sha512-NpHZBoHTfqyJk0m/s/+CSuAiwtebhYK90mDuf5eylTvgViNOujiaOaxNDxJkQQAsVvHWZftUGAx1EfO1rkKtLg==",
"engines": {
"node": ">=12"
},
@@ -12790,15 +12790,6 @@
"he": "bin/he"
}
},
- "node_modules/highlight-words": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-1.2.2.tgz",
- "integrity": "sha512-Mf4xfPXYm8Ay1wTibCrHpNWeR2nUMynMVFkXCi4mbl+TEgmNOe+I4hV7W3OCZcSvzGL6kupaqpfHOemliMTGxQ==",
- "engines": {
- "node": ">= 16",
- "npm": ">= 8"
- }
- },
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -16854,14 +16845,13 @@
}
},
"node_modules/mantine-react-table": {
- "version": "1.0.0-beta.8",
- "resolved": "https://registry.npmjs.org/mantine-react-table/-/mantine-react-table-1.0.0-beta.8.tgz",
- "integrity": "sha512-um2yN96NXSh7IBYCOxCY4d+zbPiJDiD2u1jAVPOiaKuLysvR9tP2fw2CdlFhfyM2BQD8xjJv+IlTyL602yo2Hw==",
+ "version": "1.0.0-beta.25",
+ "resolved": "https://registry.npmjs.org/mantine-react-table/-/mantine-react-table-1.0.0-beta.25.tgz",
+ "integrity": "sha512-Bu7uyVFusvfho41nNIgPL3iEpTlrwz1V7Y7Oc6cq3sKaB/fa+zkrh2+Rui6motYjEWW4jXqnTlrn42SkVAEv1g==",
"dependencies": {
"@tanstack/match-sorter-utils": "8.8.4",
- "@tanstack/react-table": "8.9.1",
- "@tanstack/react-virtual": "3.0.0-beta.54",
- "highlight-words": "1.2.2"
+ "@tanstack/react-table": "8.9.3",
+ "@tanstack/react-virtual": "3.0.0-beta.54"
},
"engines": {
"node": ">=14"
@@ -16875,9 +16865,9 @@
"@mantine/core": ">=6",
"@mantine/dates": ">=6",
"@mantine/hooks": ">=6",
- "@tabler/icons-react": ">=2",
- "react": ">=17.0",
- "react-dom": ">=17.0"
+ "@tabler/icons-react": ">=2.23.0",
+ "react": ">=18.0",
+ "react-dom": ">=18.0"
}
},
"node_modules/map-limit": {
diff --git a/chainforge/react-server/src/AreYouSureModal.js b/chainforge/react-server/src/AreYouSureModal.js
index 1f6f8b5..535454b 100644
--- a/chainforge/react-server/src/AreYouSureModal.js
+++ b/chainforge/react-server/src/AreYouSureModal.js
@@ -33,7 +33,7 @@ const AreYouSureModal = forwardRef(({title, message, onConfirm}, ref) => {
wrap="wrap"
>
Cancel
- Confirm
+ Confirm
);
diff --git a/chainforge/react-server/src/LLMResponseInspector.js b/chainforge/react-server/src/LLMResponseInspector.js
index df2b208..4457de0 100644
--- a/chainforge/react-server/src/LLMResponseInspector.js
+++ b/chainforge/react-server/src/LLMResponseInspector.js
@@ -5,7 +5,7 @@
* be deployed in multiple locations.
*/
import React, { useState, useEffect, useRef } from 'react';
-import { Collapse, MultiSelect } from '@mantine/core';
+import { Collapse, Flex, MultiSelect, NativeSelect } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import * as XLSX from 'xlsx';
import useStore from './store';
@@ -127,7 +127,6 @@ const ResponseGroup = ({ header, responseBoxes, responseBoxesWrapperClass, displ
};
-
const LLMResponseInspector = ({ jsonResponses, wideFormat }) => {
const [responses, setResponses] = useState([]);
@@ -172,13 +171,14 @@ const LLMResponseInspector = ({ jsonResponses, wideFormat }) => {
// Functions to associate a color to each LLM in responses
const color_for_llm = (llm) => (getColorForLLMAndSetIfNotFound(llm) + '99');
+ const header_bg_colors = ['#e0f4fa', '#c0def9', '#a9c0f9', '#a6b2ea'];
const response_box_colors = ['#eee', '#fff', '#eee', '#ddd', '#eee', '#ddd', '#eee'];
const rgroup_color = (depth) => response_box_colors[depth % response_box_colors.length];
- const getHeaderBadge = (key, val) => {
+ const getHeaderBadge = (key, val, depth) => {
if (val) {
const s = truncStr(val.trim(), 1024);
- return (
+ return (
{key} = "{s}"
);
} else {
@@ -281,21 +281,25 @@ const LLMResponseInspector = ({ jsonResponses, wideFormat }) => {
: groupResponsesBy(resps, (r => ((group_name in r.vars) ? r.vars[group_name] : null)));
const get_header = (group_name === 'LLM')
? ((key, val) => (
{val}
))
- : ((key, val) => getHeaderBadge(key, val));
+ : ((key, val) => getHeaderBadge(key, val, eatenvars.length));
// Now produce nested divs corresponding to the groups
const remaining_vars = varnames.slice(1);
const updated_eatenvars = eatenvars.concat([group_name]);
+ const defaultOpened = !first_opened || eatenvars.length === 0 || eatenvars[eatenvars.length-1] === 'LLM';
const grouped_resps_divs = Object.keys(grouped_resps).map(g => groupByVars(grouped_resps[g], remaining_vars, updated_eatenvars, get_header(group_name, g)));
const leftover_resps_divs = leftover_resps.length > 0 ? groupByVars(leftover_resps, remaining_vars, updated_eatenvars, get_header(group_name, undefined)) : [];
- return (<>
+ leaf_id += 1;
+
+ return (
{header ?
(
- {header}
-
- {grouped_resps_divs}
-
+
)
:
{grouped_resps_divs}
}
{leftover_resps_divs.length === 0 ? (<>>) : (
@@ -303,7 +307,7 @@ const LLMResponseInspector = ({ jsonResponses, wideFormat }) => {
{leftover_resps_divs}
)}
- >);
+
);
};
// Produce DIV elements grouped by selected vars
@@ -323,16 +327,20 @@ const LLMResponseInspector = ({ jsonResponses, wideFormat }) => {
};
return (
-
Group responses by (order matters):}
- data={multiSelectVars}
- placeholder="Pick vars to group responses, in order of importance"
- size={wideFormat ? 'sm' : 'xs'}
- value={multiSelectValue}
- clearSearchOnChange={true}
- clearSearchOnBlur={true} />
+ {/* */}
+ {/* */}
+ Group responses by (order matters):}
+ data={multiSelectVars}
+ placeholder="Pick vars to group responses, in order of importance"
+ size={wideFormat ? 'sm' : 'xs'}
+ value={multiSelectValue}
+ clearSearchOnChange={true}
+ clearSearchOnBlur={true}
+ w='100%' />
+ {/* */}
{responses}
diff --git a/chainforge/react-server/src/NodeLabelComponent.js b/chainforge/react-server/src/NodeLabelComponent.js
index d5cc4e8..e740be2 100644
--- a/chainforge/react-server/src/NodeLabelComponent.js
+++ b/chainforge/react-server/src/NodeLabelComponent.js
@@ -1,9 +1,11 @@
+import { useRef } from 'react';
import useStore from './store';
import { EditText } from 'react-edit-text';
import 'react-edit-text/dist/index.css';
import StatusIndicator from './StatusIndicatorComponent';
import AlertModal from './AlertModal';
-import { useState, useEffect} from 'react';
+import AreYouSureModal from './AreYouSureModal';
+import { useState, useEffect, useCallback} from 'react';
import { Tooltip } from '@mantine/core';
export default function NodeLabel({ title, nodeId, icon, onEdit, onSave, editable, status, alertModal, customButtons, handleRunClick, handleRunHover, runButtonTooltip }) {
@@ -12,6 +14,12 @@ export default function NodeLabel({ title, nodeId, icon, onEdit, onSave, editabl
const [runButton, setRunButton] = useState('none');
const removeNode = useStore((state) => state.removeNode);
+ // For 'delete node' confirmation popup
+ const deleteConfirmModal = useRef(null);
+ const [deleteConfirmProps, setDeleteConfirmProps] = useState({
+ title: 'Delete node', message: 'Are you sure?', onConfirm: () => {}
+ });
+
const handleNodeLabelChange = (evt) => {
const { value } = evt;
title = value;
@@ -48,13 +56,22 @@ export default function NodeLabel({ title, nodeId, icon, onEdit, onSave, editabl
}
}, [handleRunClick, runButtonTooltip]);
- const handleCloseButtonClick = () => {
- removeNode(nodeId);
- }
+ const handleCloseButtonClick = useCallback(() => {
+ setDeleteConfirmProps({
+ title: 'Delete node',
+ message: 'Are you sure you want to delete this node? This action is irreversible.',
+ onConfirm: () => removeNode(nodeId),
+ });
+
+ // Open the 'are you sure' modal:
+ if (deleteConfirmModal && deleteConfirmModal.current)
+ deleteConfirmModal.current.trigger();
+ }, [deleteConfirmModal]);
return (<>
{icon ? (<>{icon} >) : <>>}
+
{
else if (json.responses && json.errors) {
FINISHED_QUERY = true;
+ // Store and log responses (if any)
+ if (json.responses) {
+ setJSONResponses(json.responses);
+
+ // Log responses for debugging:
+ console.log(json.responses);
+ }
+
// If there was at least one error collecting a response...
const llms_w_errors = Object.keys(json.errors);
if (llms_w_errors.length > 0) {
@@ -509,12 +517,6 @@ const PromptNode = ({ data, id }) => {
setDataPropsForNode(node.id, { refresh: true });
}
});
-
- // Store responses
- setJSONResponses(json.responses);
-
- // Log responses for debugging:
- console.log(json.responses);
} else {
setStatus('error');
triggerAlert(json.error || 'Unknown error when querying LLM');
@@ -544,6 +546,29 @@ const PromptNode = ({ data, id }) => {
if (status !== 'none') { setStatus('none'); }
};
+ // Dynamically update the textareas and position of the template hooks
+ const textAreaRef = useRef(null);
+ const [hooksY, setHooksY] = useState(138);
+ const setRef = useCallback((elem) => {
+ // To listen for resize events of the textarea, we need to use a ResizeObserver.
+ // We initialize the ResizeObserver only once, when the 'ref' is first set, and only on the div wrapping textfields.
+ // NOTE: This won't work on older browsers, but there's no alternative solution.
+ if (!textAreaRef.current && elem && window.ResizeObserver) {
+ let past_hooks_y = 138;
+ const observer = new ResizeObserver(() => {
+ if (!textAreaRef || !textAreaRef.current) return;
+ const new_hooks_y = textAreaRef.current.clientHeight + 68;
+ if (past_hooks_y !== new_hooks_y) {
+ setHooksY(new_hooks_y);
+ past_hooks_y = new_hooks_y;
+ }
+ });
+
+ observer.observe(elem);
+ textAreaRef.current = elem;
+ }
+ }, [textAreaRef]);
+
return (
{
runButtonTooltip={runTooltip}
/>
-
-
-
-
-
+
+
+
@@ -609,7 +630,7 @@ const PromptNode = ({ data, id }) => {
]} />)
: <>>}
- { jsonResponses && jsonResponses.length > 0 && status !== 'error' && status !== 'loading' ?
+ { jsonResponses && jsonResponses.length > 0 && status !== 'loading' ?
(
Inspect responses
) : <>>
diff --git a/chainforge/react-server/src/text-fields-node.css b/chainforge/react-server/src/text-fields-node.css
index 5fd7e75..682ec4f 100644
--- a/chainforge/react-server/src/text-fields-node.css
+++ b/chainforge/react-server/src/text-fields-node.css
@@ -588,6 +588,15 @@
line-height: 1.2;
border-color: #999;
}
+ .prompt-field-fixed .mantine-Textarea-wrapper textarea {
+ resize: vertical;
+ overflow-y: auto;
+ padding: calc(0.5rem / 3);
+ font-size: 10pt;
+ font-family: monospace;
+ line-height: 1.2;
+ border-color: #999;
+ }
.add-text-field-btn {
display: flex;